77import shutil
88import platform
99from pathlib import Path
10- import PyInstaller . __main__
10+ from typing import Dict , List , Tuple
1111
1212def clean_build ():
1313 """Clean previous build artifacts"""
1414 dirs_to_clean = ['build' , 'dist' ]
1515 for dir_name in dirs_to_clean :
16- if os .path .exists (dir_name ):
17- shutil .rmtree (dir_name )
18- print (f"Cleaned { dir_name } directory" )
16+ try :
17+ if os .path .exists (dir_name ):
18+ shutil .rmtree (dir_name )
19+ except Exception as e :
20+ print (f"Error cleaning { dir_name } : { e } " )
1921
20- def check_environment ():
22+ def check_environment () -> bool :
2123 """Check if build environment is properly configured"""
2224 if sys .version_info >= (3 , 12 ):
23- print ("\n WARNING: You are using Python 3.12 or higher." )
24- print ("This version may have compatibility issues with PyInstaller." )
25- print ("Recommended: Use Python 3.9-3.11 for more reliable builds." )
26- if input ("\n Continue with build anyway? (y/n): " ).lower () != 'y' :
27- return False
28- return True
29-
30- def create_spec_file ():
31- """Create PyInstaller spec file based on platform"""
32- print ("Generating platform-specific spec file..." )
33-
34- is_windows = platform .system () == "Windows"
35- is_macos = platform .system () == "Darwin"
25+ print ("Warning: Python 3.12+ might have compatibility issues. Using 3.9-3.11 is recommended." )
3626
37- # Get absolute paths and ensure they exist
38- root_dir = os .path .dirname (os .path .abspath (__file__ ))
39- assets_dir = os .path .join (root_dir , 'assets' )
40- cli_path = os .path .join (root_dir , 'networkmonitor' , 'scripts' , 'networkmonitor_cli.py' ) # Fixed path
27+ required_modules = ['PyInstaller' , 'Flask' , 'click' , 'scapy' ]
28+ missing_modules = []
4129
42- # Verify paths exist
43- if not os .path .exists (cli_path ):
44- print (f"Error: networkmonitor_cli.py not found at { cli_path } " )
45- print ("Current directory:" , os .getcwd ())
46- print ("Directory contents:" , os .listdir (os .path .join (root_dir , 'networkmonitor' , 'scripts' )))
30+ for module in required_modules :
31+ try :
32+ __import__ (module )
33+ except ImportError :
34+ missing_modules .append (module )
35+
36+ if missing_modules :
37+ print ("Missing required modules:" )
38+ for module in missing_modules :
39+ print (f" - { module } " )
4740 return False
4841
49- if not os .path .exists (assets_dir ):
50- os .makedirs (assets_dir , exist_ok = True )
51- print (f"Created assets directory at { assets_dir } " )
52-
53- # Common options for all platforms
54- datas = [
55- (os .path .join (root_dir , 'assets' ), 'assets' )
56- ]
42+ return True
43+
44+ def get_platform_settings () -> Tuple [str , Dict ]:
45+ """Get platform-specific build settings"""
46+ system = platform .system ().lower ()
5747
58- # Use absolute paths for icons
59- icon_path = None
60- if is_windows :
61- icon_file = os .path .join (assets_dir , 'icon.ico' )
62- if os .path .exists (icon_file ):
63- icon_path = icon_file
64- elif is_macos :
65- icon_file = os .path .join (assets_dir , 'icon.icns' )
66- if os .path .exists (icon_file ):
67- icon_path = icon_file
48+ # Base settings common to all platforms
49+ base_settings = {
50+ 'name' : 'NetworkMonitor' ,
51+ 'console' : True ,
52+ 'debug' : False ,
53+ 'noconfirm' : True ,
54+ 'strip' : True ,
55+ 'clean' : True
56+ }
6857
69- if icon_path :
70- print (f"Using icon from: { icon_path } " )
71- else :
72- print ("Warning: No platform-specific icon found" )
58+ # Platform-specific settings
59+ if system == 'windows' :
60+ icon_file = 'assets/icon.ico'
61+ base_settings .update ({
62+ 'uac_admin' : True ,
63+ 'win_private_assemblies' : True ,
64+ 'win_no_prefer_redirects' : True ,
65+ 'hiddenimports' : ['win32com' , 'win32com.shell' , 'win32api' , 'wmi' ]
66+ })
67+ elif system == 'darwin' :
68+ icon_file = 'assets/icon.icns'
69+ base_settings ['hiddenimports' ] = ['pkg_resources.py2_warn' ]
70+ else : # Linux
71+ icon_file = 'assets/icon.ico' # Use .ico for Linux as well
72+ base_settings ['hiddenimports' ] = ['pkg_resources.py2_warn' ]
7373
74- spec_content = f"""# -*- mode: python ; coding: utf-8 -*-
75- import os
74+ return icon_file , base_settings
7675
77- block_cipher = None
76+ def create_spec_file () -> bool :
77+ """Create PyInstaller spec file based on platform"""
78+ try :
79+ # Get platform-specific settings
80+ icon_file , settings = get_platform_settings ()
81+
82+ if not os .path .exists (icon_file ):
83+ print (f"Warning: Icon file not found at { icon_file } " )
84+ icon_file = None
85+
86+ # Convert settings to spec file content
87+ spec_content = """# -*- mode: python ; coding: utf-8 -*-
7888
79- # Get absolute paths
80- root_dir = os.path.dirname(os.path.abspath(SPECPATH))
89+ from PyInstaller.building.api import PYZ, EXE, COLLECT
90+ from PyInstaller.building.build_main import Analysis
91+
92+ datas = [
93+ ('assets/*', 'assets'),
94+ ('networkmonitor/web/build/*', 'web/build')
95+ ]
8196
8297a = Analysis(
83- [r' { cli_path } '], # Use raw string for Windows compatibility
84- pathex=[root_dir ],
98+ ['networkmonitor/scripts/networkmonitor_cli.py '],
99+ pathex=[],
85100 binaries=[],
86- datas={ datas } ,
87- hiddenimports=['scapy.layers.all', 'engineio.async_drivers.threading'] ,
101+ datas=datas,
102+ hiddenimports={hiddenimports} ,
88103 hookspath=[],
89104 hooksconfig={{}},
90105 runtime_hooks=[],
91- excludes=['networkmonitor.web'], # Exclude web frontend
92- win_no_prefer_redirects=False,
93- win_private_assemblies=False,
94- cipher=block_cipher,
106+ excludes=[],
107+ win_no_prefer_redirects={win_no_prefer_redirects},
108+ win_private_assemblies={win_private_assemblies},
95109 noarchive=False,
96110)
97111
98- pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher )
112+ pyz = PYZ(a.pure, a.zipped_data)
99113
100114exe = EXE(
101115 pyz,
@@ -104,111 +118,68 @@ def create_spec_file():
104118 a.zipfiles,
105119 a.datas,
106120 [],
107- name='NetworkMonitor',
108- debug=False,
109- bootloader_ignore_signals=False,
110- strip=False,
111- upx=True,"""
112-
113- if icon_path :
114- spec_content += f"""
115- icon=[r'{ icon_path } '],"""
116-
117- if is_macos :
118- spec_content += """
119- console=False,
120- disable_windowed_traceback=False,
121- argv_emulation=True,
122- target_arch=None,
123- codesign_identity=None,
124- entitlements_file=None,
125- info_plist={{
126- 'CFBundleShortVersionString': '1.0.0',
127- 'CFBundleVersion': '1.0.0',
128- 'CFBundleIdentifier': 'com.networkmonitor.app',
129- 'CFBundleName': 'NetworkMonitor',
130- 'CFBundleDisplayName': 'NetworkMonitor',
131- 'CFBundlePackageType': 'APPL',
132- 'CFBundleSignature': '????',
133- 'LSMinimumSystemVersion': '10.13',
134- 'NSHighResolutionCapable': True,
135- }}
136- )"""
137- elif is_windows :
138- spec_content += """
139- console=True,
140- disable_windowed_traceback=False,
141- argv_emulation=False,
142- target_arch=None,
143- codesign_identity=None,
144- entitlements_file=None,
145- uac_admin=True,
146- version='file_version_info.txt'
147- )"""
148- else : # Linux
149- spec_content += """
150- console=True,
121+ name='{name}',
122+ debug={debug},
123+ strip={strip},
124+ upx=True,
125+ runtime_tmpdir=None,
126+ console={console},
151127 disable_windowed_traceback=False,
152128 argv_emulation=False,
153129 target_arch=None,
154130 codesign_identity=None,
155- )"""
156-
157- if is_macos :
158- spec_content += """
159- app = BUNDLE(
160- exe,
161- name='NetworkMonitor.app',
162- icon=os.path.join(assets_dir, 'icon.icns'),
163- bundle_identifier='com.networkmonitor.app',
164- info_plist={{
165- 'CFBundleShortVersionString': '1.0.0',
166- 'CFBundleVersion': '1.0.0',
167- 'CFBundleIdentifier': 'com.networkmonitor.app',
168- 'CFBundleName': 'NetworkMonitor',
169- 'CFBundleDisplayName': 'NetworkMonitor',
170- 'CFBundlePackageType': 'APPL',
171- 'CFBundleSignature': '????',
172- 'LSMinimumSystemVersion': '10.13',
173- 'NSHighResolutionCapable': True,
174- }}
175- )"""
176-
177- try :
178- with open ('NetworkMonitor.spec' , 'w' ) as f :
131+ entitlements_file=None,""" .format (
132+ name = settings ['name' ],
133+ hiddenimports = settings .get ('hiddenimports' , []),
134+ win_no_prefer_redirects = settings .get ('win_no_prefer_redirects' , False ),
135+ win_private_assemblies = settings .get ('win_private_assemblies' , False ),
136+ debug = settings ['debug' ],
137+ strip = settings ['strip' ],
138+ console = settings ['console' ]
139+ )
140+
141+ # Add icon if available
142+ if icon_file :
143+ spec_content += f"\n icon=['{ icon_file } '],"
144+
145+ spec_content += "\n )"
146+
147+ # Write spec file
148+ with open ('NetworkMonitor.spec' , 'w' , encoding = 'utf-8' ) as f :
179149 f .write (spec_content )
180- print ("Generated NetworkMonitor.spec file" )
150+
151+ print ("Generated NetworkMonitor.spec file\n " )
181152 return True
153+
182154 except Exception as e :
183155 print (f"Error creating spec file: { e } " )
184156 return False
185157
186- def build_executable ():
187- """Build the executable using PyInstaller with size optimizations"""
188- if not check_environment ():
189- return False
190-
191- # Clean previous builds
192- clean_build ()
193-
158+ def build_executable () -> bool :
159+ """Build the executable using PyInstaller"""
194160 try :
195- # Create spec file if it doesn't exist
196- if not os .path .exists ('NetworkMonitor.spec' ) and not create_spec_file ():
197- print ("Failed to create spec file" )
198- return False
161+ import PyInstaller .__main__
199162
200- print ("\n Building executable with optimized settings..." )
163+ # Clean previous build
164+ clean_build ()
165+
166+ # Create spec file
167+ if not create_spec_file ():
168+ return False
169+
170+ # Build with optimized settings
171+ print ("Building executable with optimized settings..." )
201172 PyInstaller .__main__ .run ([
202173 'NetworkMonitor.spec' ,
174+ '--noconfirm' ,
203175 '--clean' ,
204- '--noconfirm '
176+ '--strip '
205177 ])
206178
207- print ("\n Build completed successfully!" )
208179 return True
209180
210181 except Exception as e :
211- print (f"\n Error during build: { str ( e ) } " )
182+ print (f"Error during build: { e } " )
212183 print ("\n Troubleshooting tips:" )
213184 print ("1. Use Python 3.9-3.11 instead of 3.12+" )
214185 print ("2. Run 'pip install -r requirements.txt' to update dependencies" )
@@ -217,5 +188,16 @@ def build_executable():
217188 return False
218189
219190if __name__ == '__main__' :
220- success = build_executable ()
221- sys .exit (0 if success else 1 )
191+ try :
192+ if not check_environment ():
193+ sys .exit (1 )
194+
195+ if not build_executable ():
196+ sys .exit (1 )
197+
198+ print ("\n Build completed successfully!" )
199+ sys .exit (0 )
200+
201+ except Exception as e :
202+ print (f"\n Unexpected error: { e } " )
203+ sys .exit (1 )
0 commit comments