1
1
# build_winpython.py
2
2
import os , sys , argparse , datetime , subprocess , shutil
3
- from pathlib import Path
4
-
5
- # --- Logging ---
6
- def log_section (logfile , message ):
7
- ts = datetime .datetime .now ().strftime ("%Y-%m-%d %H:%M:%S" )
8
- section = f"\n { '-' * 40 } \n ({ ts } ) { message } \n { '-' * 40 } \n "
9
- print (section )
10
- with open (logfile , 'a' , encoding = 'utf-8' ) as f :
11
- f .write (section )
3
+ import logging
12
4
5
+ from pathlib import Path
6
+ from filecmp import cmp
7
+
8
+ LOG_FORMAT = "%(asctime)s %(levelname)s: %(message)s"
9
+
10
+ def setup_logging (log_file : Path ):
11
+ """Initialize logging to both file and stdout."""
12
+ logging .basicConfig (
13
+ level = logging .INFO ,
14
+ format = LOG_FORMAT ,
15
+ handlers = [
16
+ logging .StreamHandler (sys .stdout ),
17
+ logging .FileHandler (str (log_file ), encoding = "utf-8" , mode = 'a' )
18
+ ]
19
+ )
13
20
14
- # --- Utility Functions ---
21
+ def log_section (message : str ):
22
+ logging .info ("\n " + "-" * 40 )
23
+ logging .info (message )
24
+ logging .info ("-" * 40 )
15
25
16
26
def delete_folder_if_exists (folder : Path , check_flavor : str = "" ):
17
27
check_last = folder .parent .name if not folder .is_dir () else folder .name
18
28
expected_name = "bu" + check_flavor
19
-
20
29
if folder .exists () and folder .is_dir () and check_last == expected_name :
21
- print ( "Removing old backup:" , folder )
30
+ logging . info ( f "Removing old backup: { folder } " )
22
31
folder_old = folder .with_suffix ('.old' )
23
32
if folder_old .exists ():
24
33
shutil .rmtree (folder_old )
25
34
folder .rename (folder_old )
26
35
shutil .rmtree (folder_old )
27
36
28
-
29
- def run_command (cmd , log_file = None , shell = False , check = True ):
30
- print (f"[RUNNING] { ' ' .join (cmd ) if isinstance (cmd , list ) else cmd } " )
37
+ def run_command (cmd , shell = False , check = True ):
38
+ logging .info (f"[RUNNING] { ' ' .join (cmd ) if isinstance (cmd , list ) else cmd } " )
31
39
with subprocess .Popen (
32
40
cmd , shell = shell , stdout = subprocess .PIPE ,
33
41
stderr = subprocess .STDOUT , universal_newlines = True
34
42
) as proc :
35
- with open (log_file , 'a' , encoding = 'utf-8' ) if log_file else open (os .devnull , 'w' ) as logf :
36
- for line in proc .stdout :
37
- print (line , end = "" )
38
- logf .write (line )
43
+ for line in proc .stdout :
44
+ logging .info (line .rstrip ())
39
45
if check and proc .wait () != 0 :
40
46
raise subprocess .CalledProcessError (proc .returncode , cmd )
41
47
42
-
43
- def pip_install (python_exe : Path , req_file : str , constraints : str , find_links : str , logfile : Path , label : str ):
48
+ def pip_install (python_exe : Path , req_file : str , constraints : str , find_links : str , label : str ):
44
49
if req_file and Path (req_file ).exists ():
45
50
cmd = [
46
51
str (python_exe ), "-m" , "pip" , "install" ,
47
52
"-r" , req_file , "-c" , constraints ,
48
53
"--pre" , "--no-index" , f"--find-links={ find_links } "
49
54
]
50
- log_section (logfile , f"Pip-install { label } " )
51
- run_command (cmd , log_file = logfile )
55
+ log_section (f"Pip-install { label } " )
56
+ run_command (cmd )
52
57
else :
53
- log_section (logfile , f"No { label } specified/skipped" )
58
+ log_section (f"No { label } specified/skipped" )
54
59
55
-
56
- def patch_winpython (python_exe , logfile ):
60
+ def patch_winpython (python_exe ):
57
61
cmd = [
58
62
str (python_exe ), "-c" ,
59
63
"from wppm import wppm; wppm.Distribution().patch_standard_packages('', to_movable=True)"
60
64
]
61
- run_command (cmd , log_file = logfile )
62
-
65
+ run_command (cmd )
63
66
64
67
def check_env_bat (winpydirbase : Path ):
65
68
envbat = winpydirbase / "scripts" / "env.bat"
66
69
if not envbat .exists ():
67
70
raise FileNotFoundError (f"Missing env.bat at { envbat } " )
68
71
69
-
70
- def generate_lockfiles (target_python : Path , winpydirbase : Path , constraints : str , find_links : str , logfile : Path , file_postfix : str ):
72
+ def generate_lockfiles (target_python : Path , winpydirbase : Path , constraints : str , find_links : str , file_postfix : str ):
71
73
pip_req = winpydirbase .parent / "requirement_temp.txt"
72
74
with subprocess .Popen ([str (target_python ), "-m" , "pip" , "freeze" ], stdout = subprocess .PIPE ) as proc :
73
75
packages = [l for l in proc .stdout if b"winpython" not in l ]
@@ -76,28 +78,15 @@ def generate_lockfiles(target_python: Path, winpydirbase: Path, constraints: str
76
78
for kind in ("" , "local" ):
77
79
out = winpydirbase .parent / f"pylock.{ file_postfix } _{ kind } .toml"
78
80
outreq = winpydirbase .parent / f"requir.{ file_postfix } _{ kind } .txt"
79
- print (
80
- [str (target_python ), "-m" , "pip" , "lock" , "--no-deps" , "-c" , constraints ] +
81
- (["--find-links" ,find_links ] if kind == "local" else []) +
82
- ["-r" , str (pip_req ), "-o" , str (out )]
83
- )
84
- subprocess .run (
85
- [str (target_python ), "-m" , "pip" , "lock" , "--no-deps" , "-c" , constraints ] +
86
- (["--find-links" ,find_links ] if kind == "local" else []) +
87
- ["-r" , str (pip_req ), "-o" , str (out )],
88
- stdout = open (logfile , 'a' ), stderr = subprocess .STDOUT , check = True
89
- )
81
+ cmd = [str (target_python ), "-m" , "pip" , "lock" , "--no-deps" , "-c" , constraints ]
82
+ if kind == "local" :
83
+ cmd += ["--find-links" , find_links ]
84
+ cmd += ["-r" , str (pip_req ), "-o" , str (out )]
85
+ run_command (cmd )
90
86
# Convert both locks to requirement.txt with hash256
91
- cmd = f"from wppm import wheelhouse as wh; wh.pylock_to_req(r'{ out } ', r'{ outreq } ')"
92
- print (
93
- [str (target_python ), "-c" , cmd ]
94
- )
95
- subprocess .run (
96
- [str (target_python ), "-c" , cmd ],
97
- stdout = open (logfile , 'a' ), stderr = subprocess .STDOUT , check = False
98
- )
87
+ cmd = [str (target_python ), "-X" , "utf8" , "-c" , f"from wppm import wheelhouse as wh; wh.pylock_to_req(r'{ out } ', r'{ outreq } ')" ]
88
+ run_command (cmd )
99
89
# check equality
100
- from filecmp import cmp
101
90
web , local = "" , "local"
102
91
if not cmp (winpydirbase .parent / f"requir.{ file_postfix } _{ web } .txt" , winpydirbase .parent / f"requir.{ file_postfix } _{ local } .txt" ):
103
92
print ("ALARM differences in " , winpydirbase .parent / f"requir.{ file_postfix } _{ web } .txt" , winpydirbase .parent / f"requir.{ file_postfix } _{ local } .txt" )
@@ -106,7 +95,7 @@ def generate_lockfiles(target_python: Path, winpydirbase: Path, constraints: str
106
95
print ("match ok " ,winpydirbase .parent / f"requir.{ file_postfix } _{ web } .txt" , winpydirbase .parent / f"requir.{ file_postfix } _{ local } .txt" )
107
96
108
97
# --- Main Logic ---
109
- def run_make_py (build_python , winpydirbase , args , logfile ):
98
+ def run_make_py (build_python , winpydirbase , args ):
110
99
from . import make
111
100
make .make_all (
112
101
args .release , args .release_level , basedir_wpy = winpydirbase ,
@@ -136,7 +125,7 @@ def main():
136
125
137
126
# compute paths (same as Step2)...
138
127
build_python = Path (args .buildenv ) / "python.exe"
139
- winpydirbase = Path (args .winpydirbase ) # from Step2 logic
128
+ winpydirbase = Path (args .winpydirbase )
140
129
target_python = winpydirbase / "python" / "python.exe"
141
130
142
131
# Setup paths and logs
@@ -145,117 +134,107 @@ def main():
145
134
log_dir .mkdir (exist_ok = True )
146
135
time_str = now .strftime ("%Y-%m-%d_at_%H%M" )
147
136
log_file = log_dir / f"build_{ args .python_target } _{ args .flavor } _{ args .release_level } _{ time_str } .txt"
148
-
149
- #logs termination
137
+ setup_logging (log_file )
138
+
139
+ # Logs termination and version naming
150
140
z = Path (winpydirbase ).name [(4 + len (args .arch )):- len (args .release_level )]
151
141
tada = f"{ z [:1 ]} _{ z [1 :3 ]} _{ z [3 ]} _{ args .release } "
152
142
winpyver2 = tada .replace ('_' , '.' )
153
143
file_postfix = f"{ args .arch } -{ tada } { args .flavor } { args .release_level } "
154
144
155
- log_section (log_file , f"Preparing build for Python { args .python_target } ({ args .arch } -bit)" )
156
-
157
- log_section (log_file , f"🙏 Step 0: displace old { Path (winpydirbase )} " )
145
+ log_section (f"Preparing build for Python { args .python_target } ({ args .arch } -bit)" )
158
146
147
+ log_section (f"🙏 Step 0: displace old { Path (winpydirbase )} " )
159
148
delete_folder_if_exists (winpydirbase .parent , check_flavor = args .flavor ) #bu{flavor]}
160
149
161
- log_section (log_file , f"🙏 Step 1: make.py Python with { str (build_python )} at ({ winpydirbase } " )
162
- run_make_py (str (build_python ), winpydirbase , args , log_file )
150
+ log_section (f"🙏 Step 1: make.py Python with { str (build_python )} at ({ winpydirbase } " )
151
+ run_make_py (str (build_python ), winpydirbase , args )
163
152
164
153
check_env_bat (winpydirbase )
165
154
166
- log_section (log_file , "🙏 Step 3: install requirements" )
155
+ log_section ("🙏 Step 3: install requirements" )
167
156
168
157
for label , req in [
169
158
("Mandatory" , args .mandatory_req ),
170
159
("Pre" , args .pre_req ),
171
160
("Main" , args .requirements ),
172
161
]:
173
- pip_install (target_python , req , args .constraints , args .find_links , log_file , label )
162
+ pip_install (target_python , req , args .constraints , args .find_links , label )
174
163
175
- log_section (log_file , "🙏 Step 4: Patch Winpython" )
176
- patch_winpython (target_python , log_file )
164
+ log_section ("🙏 Step 4: Patch Winpython" )
165
+ patch_winpython (target_python )
177
166
178
167
if args .wheelhousereq :
179
- log_section (log_file , f"🙏 Step 5: install wheelhouse requirements { args .wheelhousereq } " )
168
+ log_section (f"🙏 Step 5: install wheelhouse requirements { args .wheelhousereq } " )
180
169
wheelhousereq = Path (args .wheelhousereq )
181
170
kind = "local"
182
171
out = winpydirbase .parent / f"pylock.{ file_postfix } _wheels{ kind } .toml"
183
172
outreq = winpydirbase .parent / f"requir.{ file_postfix } _wheels{ kind } .txt"
184
173
if wheelhousereq .is_file ():
185
174
# Generate pylock from wheelhousereq
186
175
cmd = [str (target_python ), "-m" , "pip" , "lock" ,"--no-index" , "--trusted-host=None" ,
187
- "--find-links" , args .find_links , "-c" , args .constraints , "-r" , wheelhousereq ,
188
- "-o" , out ]
189
- subprocess . run (cmd , stdout = open ( log_file , 'a' ), stderr = subprocess . STDOUT , check = True )
176
+ "--find-links" , args .find_links , "-c" , str ( args .constraints ) , "-r" , str ( wheelhousereq ) ,
177
+ "-o" , str ( out ) ]
178
+ run_command (cmd )
190
179
# Convert pylock to requirements with hash
191
- cmd = f"from wppm import wheelhouse as wh; wh.pylock_to_req(r'{ out } ', r'{ outreq } ')"
192
- print ( [str (target_python ), "-c" , cmd ] )
193
- subprocess .run ([str (target_python ), "-c" , cmd ],
194
- stdout = open (log_file , 'a' ), stderr = subprocess .STDOUT , check = False
195
- )
180
+ cmd = [str (target_python ), "-X" , "utf8" , "-c" , f"from wppm import wheelhouse as wh; wh.pylock_to_req(r'{ out } ', r'{ outreq } ')" ]
181
+ run_command (cmd , check = False )
196
182
197
183
kind = ""
198
184
outw = winpydirbase .parent / f"pylock.{ file_postfix } _wheels{ kind } .toml"
199
185
outreqw = winpydirbase .parent / f"requir.{ file_postfix } _wheels{ kind } .txt"
200
186
# Generate web pylock from local frozen hashes
201
187
cmd = [str (target_python ), "-m" , "pip" , "lock" ,"--no-deps" , "--require-hashes" ,
202
188
"-r" , str (outreq ), "-o" , str (outw ) ]
203
- subprocess .run (cmd , stdout = open (log_file , 'a' ), stderr = subprocess .STDOUT , check = True )
204
- cmd = f"from wppm import wheelhouse as wh; wh.pylock_to_req(r'{ outw } ', r'{ outreqw } ')"
205
- print ( [str (target_python ), "-c" , cmd ] )
206
- subprocess .run ([str (target_python ), "-c" , cmd ],
207
- stdout = open (log_file , 'a' ), stderr = subprocess .STDOUT , check = False
208
- )
189
+ run_command (cmd )
190
+ cmd = [str (target_python ), "-X" , "utf8" , "-c" , f"from wppm import wheelhouse as wh; wh.pylock_to_req(r'{ outw } ', r'{ outreqw } ')" ]
191
+ run_command (cmd , check = False )
209
192
210
193
# Use wppm to download local from req made with web hashes
211
194
wheelhouse = winpydirbase / "wheelhouse" / "included.wheels"
212
195
cmd = [str (target_python ), "-X" , "utf8" , "-m" , "wppm" , str (out ), "-ws" , args .find_links ,
213
196
"-wd" , str (wheelhouse )
214
197
]
215
- print (cmd )
216
- subprocess .run (cmd , stdout = open (log_file , 'a' ), stderr = subprocess .STDOUT , check = False )
217
-
198
+ run_command (cmd , check = False )
218
199
219
- log_section (log_file , "🙏 Step 6: install lockfiles" )
220
- print (target_python , winpydirbase , args .constraints , args .find_links , log_file )
221
- generate_lockfiles (target_python , winpydirbase , args .constraints , args .find_links , log_file , file_postfix )
200
+ log_section ("🙏 Step 6: install lockfiles" )
201
+ print (target_python , winpydirbase , args .constraints , args .find_links , file_postfix )
202
+ generate_lockfiles (target_python , winpydirbase , args .constraints , args .find_links , file_postfix )
222
203
223
204
# 6) generate changelog
224
205
mdn = f"WinPython{ args .flavor } -{ args .arch } bit-{ winpyver2 } .md"
225
206
out = f"WinPython{ args .flavor } -{ args .arch } bit-{ winpyver2 } _History.md"
226
207
changelog_dir = log_dir .parent / "changelogs"
227
208
228
- log_section (log_file , f"🙏 Step 6: generate changelog { mdn } " )
209
+ log_section (f"🙏 Step 6: generate changelog { mdn } " )
229
210
230
211
cmd = ["set" , f"WINPYVER2={ winpyver2 } &" , "set" , f"WINPYFLAVOR={ args .flavor } &" ,
231
212
"set" , f"WINPYVER={ winpyver2 } { args .flavor } { args .release_level } &" ,
232
- str (target_python ), "-c" ,
213
+ str (target_python ), "-X" , "utf8" , "- c" ,
233
214
(
234
215
"from wppm import wppm;"
235
216
"result = wppm.Distribution().generate_package_index_markdown();"
236
217
f"open(r'{ winpydirbase .parent / mdn } ', 'w', encoding='utf-8').write(result)"
237
218
)]
238
- print (cmd )
239
- subprocess .run (cmd , stdout = open (log_file , 'a' ), stderr = subprocess .STDOUT , check = True , shell = True )
219
+ run_command (cmd , shell = True )
240
220
shutil .copyfile (winpydirbase .parent / mdn , changelog_dir / mdn )
241
221
242
- cmd = [str (target_python ), "-c" ,
222
+ cmd = [str (target_python ), "-X" , "utf8" , "- c" ,
243
223
(
244
224
"from wppm import diff;"
245
225
f"result = diff.compare_package_indexes('{ winpyver2 } ', searchdir=r'{ changelog_dir } ', flavor=r'{ args .flavor } ', architecture={ args .arch } );"
246
226
f"open(r'{ winpydirbase .parent / out } ', 'w', encoding='utf-8').write(result)"
247
227
)]
248
- subprocess . run (cmd , stdout = open ( log_file , 'a' ), stderr = subprocess . STDOUT , check = True )
228
+ run_command (cmd , check = False )
249
229
shutil .copyfile (winpydirbase .parent / out , changelog_dir / out )
250
- log_section (log_file , "✅ Step 6 complete" )
230
+ log_section ("✅ Step 6 complete" )
251
231
252
232
if args .create_installer != "" :
253
- log_section (log_file , "🙏 Step 7 Create Installer" )
233
+ log_section ("🙏 Step 7 Create Installer" )
254
234
stem = f"WinPython{ args .arch } -{ winpyver2 } { args .flavor } { args .release_level } "
255
- cmd = f"from wppm import utils; utils.command_installer_7zip(r'{ winpydirbase } ', r'{ winpydirbase .parent } ', r'{ stem } ', r'{ args .create_installer } ')"
256
- print ( [str (target_python ), "-c" , cmd ] )
257
- subprocess .run ([str (target_python ), "-c" , cmd ],
258
- stdout = open (log_file , 'a' ), stderr = subprocess .STDOUT , check = False )
235
+ cmd = [str (target_python ), "-c" ,
236
+ f"from wppm import utils; utils.command_installer_7zip(r'{ winpydirbase } ', r'{ winpydirbase .parent } ', r'{ stem } ', r'{ args .create_installer } ')" ]
237
+ run_command (cmd , check = False )
259
238
260
239
if __name__ == '__main__' :
261
240
main ()
0 commit comments