-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbuild_windows_cross.py
More file actions
362 lines (301 loc) · 12.8 KB
/
build_windows_cross.py
File metadata and controls
362 lines (301 loc) · 12.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
#!/usr/bin/env python3
"""
Cross-compilation script for creating Windows .exe from Linux
This requires wine to be installed on the Linux system
Usage:
python3 build_windows_cross.py # Quick build (DEFAULT - fastest)
python3 build_windows_cross.py --clean # Clean build (removes cache, slower)
Build Speed Tips:
- DEFAULT: Quick build ~30-60 seconds (uses cache)
- Clean build: ~2-3 minutes (removes cache, fresh dependencies)
- Use --clean when you have dependency issues
- Quick build is now the default for faster daily development
Settings Sync:
- Build script automatically syncs settings from settings.json to config/config.py
- This ensures the built executable always uses your current settings as defaults
- No need to manually update config files - settings.json is the source of truth
"""
import os
import sys
import subprocess
import shutil
import time
import json
from pathlib import Path
def verify_build_settings():
"""Verify and display the current build settings"""
print("🔍 Verifying current build settings...")
try:
# Check settings.json for current defaults (this is the source of truth)
settings_path = 'settings.json'
if not os.path.exists(settings_path):
print("⚠️ settings.json not found")
return False
with open(settings_path, 'r') as f:
settings_data = json.load(f)
# Extract grids from settings.json
grids = settings_data.get('grids', [])
if grids and len(grids) >= 3:
print("✅ Current build will use these default settings from settings.json:")
for i, grid in enumerate(grids, 1):
range_val = grid.get('grid_range', 0)
spacing = grid.get('spacing_usd', 0)
position = grid.get('position_btc', 0)
target = grid.get('sell_target_usd', 0)
print(f" 📊 Grid {i}: Range={range_val}, Spacing={spacing}, Position={position}, Target={target}")
return True
else:
print("⚠️ Could not parse grids from settings.json")
return False
except Exception as e:
print(f"❌ Error verifying build settings: {str(e)}")
return False
def sync_settings_from_json():
"""Sync settings from settings.json to config/config.py to ensure build uses current settings"""
print("🔄 Syncing settings from settings.json...")
try:
# Read current settings.json
if not os.path.exists('settings.json'):
print("⚠️ settings.json not found, skipping sync")
return False
with open('settings.json', 'r') as f:
current_settings = json.load(f)
# Read current config.py
config_path = 'config/config.py'
if not os.path.exists(config_path):
print("⚠️ config/config.py not found, skipping sync")
return False
with open(config_path, 'r') as f:
config_content = f.read()
# Extract grids from settings.json
grids = current_settings.get('grids', [])
if not grids or len(grids) < 3:
print("⚠️ Invalid grids configuration in settings.json, skipping sync")
return False
# Format the grids for config.py fallback defaults
grid_lines = []
for grid in grids:
grid_line = f" {{'grid_range': {grid['grid_range']}, 'spacing_usd': {grid['spacing_usd']}, 'position_btc': {grid['position_btc']}, 'sell_target_usd': {grid['sell_target_usd']}}}"
grid_lines.append(grid_line)
# Create the new grids array content for the fallback section
new_grids_content = f""" # If no grids in file, create minimal default structure
# This should only happen on first run
self.grids = [
{','.join(grid_lines)}
]"""
# Replace the fallback grids section in config.py
import re
pattern = r' # If no grids in file, create minimal default structure\s+ # This should only happen on first run\s+ self\.grids = \[\s+.*?\s+\]'
replacement = new_grids_content
if re.search(pattern, config_content, re.DOTALL):
new_config_content = re.sub(pattern, replacement, config_content, flags=re.DOTALL)
# Write updated config.py
with open(config_path, 'w') as f:
f.write(new_config_content)
print("✅ Settings synced from settings.json to config/config.py fallback defaults")
print(f" 📊 Grid 1: Range={grids[0]['grid_range']}, Spacing={grids[0]['spacing_usd']}, Position={grids[0]['position_btc']}, Target={grids[0]['sell_target_usd']}")
print(f" 📊 Grid 2: Range={grids[1]['grid_range']}, Spacing={grids[1]['spacing_usd']}, Position={grids[1]['position_btc']}, Target={grids[1]['sell_target_usd']}")
print(f" 📊 Grid 3: Range={grids[2]['grid_range']}, Spacing={grids[2]['spacing_usd']}, Position={grids[2]['position_btc']}, Target={grids[2]['sell_target_usd']}")
return True
else:
print("⚠️ Could not find fallback grids section in config.py, skipping sync")
return False
except Exception as e:
print(f"❌ Error syncing settings: {str(e)}")
return False
def check_wine():
"""Check if wine is installed"""
try:
subprocess.run(['wine', '--version'], capture_output=True, check=True)
print("✅ Wine found")
return True
except (subprocess.CalledProcessError, FileNotFoundError):
print("❌ Wine not found. Install with: sudo apt install wine")
return False
def install_windows_python():
"""Install Python for Windows using wine"""
print("📦 Installing Windows Python...")
# Check if Windows Python is already installed
wine_python_path = Path.home() / ".wine/drive_c/users/samuel/AppData/Local/Programs/Python/Python39/python.exe"
if wine_python_path.exists():
print("✅ Windows Python found")
return str(wine_python_path)
print("❌ Windows Python not found. Please install Python for Windows manually:")
print(" 1. Download Python 3.9 from https://python.org")
print(" 2. Install it using wine: wine python-3.9.x-amd64.exe")
print(" 3. Run this script again")
return None
def check_pyinstaller():
"""Check if PyInstaller is already installed in Windows Python"""
wine_python = install_windows_python()
if not wine_python:
return False, None
try:
result = subprocess.run([
'wine', wine_python, '-c', 'import PyInstaller; print("OK")'
], capture_output=True, text=True)
if result.returncode == 0 and "OK" in result.stdout:
print("✅ PyInstaller already installed in Windows Python")
return True, wine_python
else:
print("❌ PyInstaller not found in Windows Python")
return False, wine_python
except Exception:
return False, wine_python
def create_windows_spec():
"""Create PyInstaller spec for Windows cross-compilation"""
print("📝 Creating Windows PyInstaller spec...")
spec_content = '''# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(
['main.py'],
pathex=[],
binaries=[],
datas=[
('translations_de.qm', '.'),
('settings.json', '.'),
],
hiddenimports=[
'PyQt5.QtCore',
'PyQt5.QtGui',
'PyQt5.QtWidgets',
'PyQt5.QtMultimedia',
'PyQt5.QtNetwork',
'ccxt',
'json',
'sqlite3',
'threading',
'time',
'datetime',
'decimal'
],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='GridBot',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=None,
)
'''
with open('gridbot_windows.spec', 'w', encoding='utf-8') as f:
f.write(spec_content)
print("✅ Windows spec file created")
def build_windows_exe(quick_build=True):
"""Build Windows .exe using wine and pyinstaller"""
print("🔨 Building Windows executable...")
# Check if PyInstaller is already installed
pyinstaller_installed, wine_python = check_pyinstaller()
if not wine_python:
return False
try:
# Only install PyInstaller if not already installed
if not pyinstaller_installed:
print("📦 Installing PyInstaller in Windows Python...")
subprocess.run([
'wine', wine_python, '-m', 'pip', 'install', 'pyinstaller'
], check=True)
print("✅ PyInstaller installed in Windows Python")
else:
print("⚡ Using cached PyInstaller installation")
# Check if we can use incremental build (only in quick mode)
if quick_build:
dist_path = Path('dist/GridBot.exe')
if dist_path.exists():
print("🔄 Previous build found, using incremental build...")
build_flags = ['--clean', '--noconfirm']
else:
print("🆕 First build, doing full compilation...")
build_flags = ['--clean']
else:
# Clean build mode - always do full compilation
print("🧹 Clean build mode - doing full compilation...")
build_flags = ['--clean']
# Build the executable with optimized flags
build_command = [
'wine', wine_python, '-m', 'PyInstaller'
] + build_flags + [
'--log-level=WARN', # Reduce log verbosity for speed
'gridbot_windows.spec'
]
# Set environment variables for faster builds
env = os.environ.copy()
env['PYTHONOPTIMIZE'] = '1' # Optimize Python bytecode
env['PYTHONDONTWRITEBYTECODE'] = '1' # Don't write .pyc files
subprocess.run(build_command, env=env, check=True)
print("✅ Windows executable built successfully!")
return True
except subprocess.CalledProcessError as e:
print(f"❌ Build failed: {e}")
return False
def main():
"""Main cross-compilation process"""
start_time = time.time()
# Parse command line arguments
clean_build = '--clean' in sys.argv
# Quick build is now DEFAULT - only disable if --clean is specified
quick_build = not clean_build
if quick_build:
print("⚡ QUICK BUILD MODE (DEFAULT) - Using cached dependencies and incremental build")
if clean_build:
print("🧹 CLEAN BUILD MODE - Removing previous build artifacts")
if Path('dist').exists():
shutil.rmtree('dist')
if Path('build').exists():
shutil.rmtree('build')
print("🚀 Starting Windows cross-compilation...")
print("=" * 60)
if not check_wine():
print("❌ Wine is required for cross-compilation")
return False
# Sync settings from settings.json to config/config.py
if not sync_settings_from_json():
print("⚠️ Could not sync settings. Skipping grid configuration update.")
# Verify the current build settings
verify_build_settings()
# Skip database clearing since app generates its own DB on first run
print("ℹ️ Skipping database clearing - app generates its own DB on first run")
# Note about new default behavior
if quick_build:
print("⚡ Quick build is now the DEFAULT behavior for faster builds!")
# Create Windows spec
create_windows_spec()
# Build Windows executable
if not build_windows_exe(quick_build):
print("❌ Cross-compilation failed!")
return False
build_time = time.time() - start_time
print("=" * 60)
print("🎉 Windows .exe created successfully!")
print(f"⏱️ Build completed in {build_time:.1f} seconds")
print("📁 Your executable is ready in: dist/GridBot.exe")
if quick_build:
print("💡 Tip: Use --clean for a fresh build when you have dependency issues")
return True
if __name__ == "__main__":
success = main()
sys.exit(0 if success else 1)