Skip to content

Commit 4c51664

Browse files
committed
refactor: enhance CI workflow and build process with improved dependency management and error handling
1 parent 2dd5780 commit 4c51664

File tree

3 files changed

+101
-93
lines changed

3 files changed

+101
-93
lines changed

.github/workflows/ci.yml

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@ jobs:
5151
5252
- name: Install Python dependencies
5353
run: |
54-
python -m pip install --upgrade pip
55-
pip install -r requirements.txt
56-
pip install -r requirements-build.txt
54+
python -m pip install --upgrade pip setuptools wheel
55+
pip install -r requirements.txt --no-cache-dir
56+
pip install -r requirements-build.txt --no-cache-dir
5757
pip install pytest pytest-cov pytest-xdist mock
5858
5959
- name: Run tests
@@ -172,15 +172,41 @@ jobs:
172172
mv icon.icns assets/icon.icns
173173
174174
- name: Install Python dependencies
175+
shell: bash
175176
run: |
176-
python -m pip install --upgrade pip
177-
pip install -r requirements.txt
178-
pip install -r requirements-build.txt
179-
177+
python -m pip install --upgrade pip setuptools wheel
178+
179+
# Install core dependencies separately first to ensure they're available
180+
pip install flask==2.0.0 flask-cors==3.0.0 click==8.0.0 scapy==2.5.0 --no-cache-dir
181+
182+
# Install build requirements
183+
pip install -r requirements-build.txt --no-cache-dir || {
184+
echo "Some build requirements failed but continuing..."
185+
# List what was actually installed
186+
pip list
187+
}
188+
189+
# Install main requirements
190+
pip install -r requirements.txt --no-cache-dir || {
191+
echo "Some requirements failed but continuing..."
192+
# List what was actually installed
193+
pip list
194+
}
195+
196+
# Verify critical packages are installed
197+
python -c "import flask; import click; import scapy; print('Core dependencies verified')" || exit 1
198+
180199
- name: Build application
181200
env:
182201
APP_VERSION: ${{ steps.get_version.outputs.version }}
183-
run: python build.py
202+
run: |
203+
# Verify Python environment before build
204+
python --version
205+
which python
206+
pip list
207+
208+
# Run build with more verbosity for debugging
209+
python -v build.py
184210
185211
- name: Package Windows artifacts
186212
if: runner.os == 'Windows'

build.py

Lines changed: 59 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import shutil
88
import platform
99
from pathlib import Path
10-
from typing import Dict, List, Tuple
10+
import subprocess
1111

1212
def clean_build():
1313
"""Clean previous build artifacts"""
@@ -24,92 +24,68 @@ def check_environment() -> bool:
2424
if sys.version_info >= (3, 12):
2525
print("Warning: Python 3.12+ might have compatibility issues. Using 3.9-3.11 is recommended.")
2626

27-
required_modules = ['PyInstaller', 'Flask', 'click', 'scapy']
28-
missing_modules = []
29-
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}")
27+
try:
28+
import PyInstaller
29+
return True
30+
except ImportError:
31+
print("\nPyInstaller not found. Please install build requirements:")
32+
print("pip install -r requirements-build.txt")
4033
return False
41-
42-
return True
4334

44-
def get_platform_settings() -> Tuple[str, Dict]:
35+
def get_platform_settings():
4536
"""Get platform-specific build settings"""
4637
system = platform.system().lower()
4738

4839
# Base settings common to all platforms
49-
base_settings = {
40+
settings = {
5041
'name': 'NetworkMonitor',
5142
'console': True,
5243
'debug': False,
5344
'noconfirm': True,
54-
'strip': True,
55-
'clean': True
45+
'clean': True,
46+
'icon_path': None,
47+
'hiddenimports': []
5648
}
5749

5850
# Platform-specific settings
5951
if system == 'windows':
60-
icon_file = 'assets/icon.ico'
61-
base_settings.update({
52+
settings.update({
53+
'icon_path': 'assets/icon.ico',
6254
'uac_admin': True,
63-
'win_private_assemblies': True,
64-
'win_no_prefer_redirects': True,
6555
'hiddenimports': ['win32com', 'win32com.shell', 'win32api', 'wmi']
6656
})
6757
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']
58+
settings['icon_path'] = 'assets/icon.icns'
59+
elif system == 'linux':
60+
settings['icon_path'] = 'assets/icon.ico'
7361

74-
return icon_file, base_settings
62+
return settings
7563

76-
def create_spec_file() -> bool:
64+
def create_spec_file(settings) -> bool:
7765
"""Create PyInstaller spec file based on platform"""
7866
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-
8667
# Convert settings to spec file content
87-
spec_content = """# -*- mode: python ; coding: utf-8 -*-
68+
spec_content = f"""# -*- mode: python ; coding: utf-8 -*-
8869
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-
]
70+
block_cipher = None
9671
9772
a = Analysis(
9873
['networkmonitor/scripts/networkmonitor_cli.py'],
9974
pathex=[],
10075
binaries=[],
101-
datas=datas,
102-
hiddenimports={hiddenimports},
76+
datas=[
77+
('assets/*', 'assets')
78+
],
79+
hiddenimports={settings['hiddenimports']},
10380
hookspath=[],
10481
hooksconfig={{}},
10582
runtime_hooks=[],
10683
excludes=[],
107-
win_no_prefer_redirects={win_no_prefer_redirects},
108-
win_private_assemblies={win_private_assemblies},
109-
noarchive=False,
84+
cipher=block_cipher,
85+
noarchive=False
11086
)
11187
112-
pyz = PYZ(a.pure, a.zipped_data)
88+
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
11389
11490
exe = EXE(
11591
pyz,
@@ -118,37 +94,29 @@ def create_spec_file() -> bool:
11894
a.zipfiles,
11995
a.datas,
12096
[],
121-
name='{name}',
122-
debug={debug},
123-
strip={strip},
124-
upx=True,
97+
name='NetworkMonitor',
98+
debug=False,
99+
bootloader_ignore_signals=False,
100+
upx=False,
125101
runtime_tmpdir=None,
126-
console={console},
102+
console=True,
127103
disable_windowed_traceback=False,
128104
argv_emulation=False,
129105
target_arch=None,
130106
codesign_identity=None,
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-
107+
entitlements_file=None"""
108+
141109
# Add icon if available
142-
if icon_file:
143-
spec_content += f"\n icon=['{icon_file}'],"
110+
if settings.get('icon_path'):
111+
spec_content += f",\n icon=['{settings['icon_path']}']"
144112

145113
spec_content += "\n)"
146114

147115
# Write spec file
148116
with open('NetworkMonitor.spec', 'w', encoding='utf-8') as f:
149117
f.write(spec_content)
150118

151-
print("Generated NetworkMonitor.spec file\n")
119+
print("Generated NetworkMonitor.spec file with platform-specific settings\n")
152120
return True
153121

154122
except Exception as e:
@@ -158,26 +126,34 @@ def create_spec_file() -> bool:
158126
def build_executable() -> bool:
159127
"""Build the executable using PyInstaller"""
160128
try:
161-
import PyInstaller.__main__
162-
163129
# Clean previous build
164130
clean_build()
165131

132+
# Get platform-specific settings
133+
settings = get_platform_settings()
134+
166135
# Create spec file
167-
if not create_spec_file():
136+
if not create_spec_file(settings):
168137
return False
169-
170-
# Build with optimized settings
138+
171139
print("Building executable with optimized settings...")
172-
PyInstaller.__main__.run([
140+
pyinstaller_args = [
141+
sys.executable,
142+
'-m',
143+
'PyInstaller',
173144
'NetworkMonitor.spec',
174-
'--noconfirm',
175-
'--clean',
176-
'--strip'
177-
])
178-
179-
return True
145+
'--noconfirm'
146+
]
147+
148+
result = subprocess.run(pyinstaller_args, check=True)
149+
return result.returncode == 0
180150

151+
except subprocess.CalledProcessError as e:
152+
print(f"PyInstaller build failed with return code {e.returncode}")
153+
if hasattr(e, 'stderr') and e.stderr:
154+
print("Error output:")
155+
print(e.stderr.decode() if isinstance(e.stderr, bytes) else e.stderr)
156+
return False
181157
except Exception as e:
182158
print(f"Error during build: {e}")
183159
print("\nTroubleshooting tips:")
@@ -189,6 +165,7 @@ def build_executable() -> bool:
189165

190166
if __name__ == '__main__':
191167
try:
168+
print("Building NetworkMonitor executable...")
192169
if not check_environment():
193170
sys.exit(1)
194171

requirements-build.txt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1+
# Core dependencies - install these first
2+
flask>=2.0.0
3+
flask-cors>=3.0.0
4+
click>=8.0.0
5+
scapy>=2.5.0
6+
17
# Build dependencies
28
pyinstaller>=5.13.0
39
wheel>=0.40.0
410
setuptools>=68.0.0
5-
flask>=2.0.0
6-
flask-cors>=3.0.0
11+
712
# Test dependencies
813
pytest>=7.0.0
914
pytest-cov>=4.0.0
@@ -13,7 +18,7 @@ mock>=5.0.0
1318
# Platform-specific dependencies
1419
pynsist>=2.6.0; sys_platform == 'win32'
1520

16-
# Other dependencies
21+
# Documentation and UI dependencies
1722
cairosvg>=2.5.0
1823
Pillow>=10.0.0
1924
mkdocs>=1.4.0

0 commit comments

Comments
 (0)