11# build_winpython.py
22import 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
124
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+ )
1320
14- # --- Utility Functions ---
21+ def log_section (message : str ):
22+ logging .info ("\n " + "-" * 40 )
23+ logging .info (message )
24+ logging .info ("-" * 40 )
1525
1626def delete_folder_if_exists (folder : Path , check_flavor : str = "" ):
1727 check_last = folder .parent .name if not folder .is_dir () else folder .name
1828 expected_name = "bu" + check_flavor
19-
2029 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 } " )
2231 folder_old = folder .with_suffix ('.old' )
2332 if folder_old .exists ():
2433 shutil .rmtree (folder_old )
2534 folder .rename (folder_old )
2635 shutil .rmtree (folder_old )
2736
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 } " )
3139 with subprocess .Popen (
3240 cmd , shell = shell , stdout = subprocess .PIPE ,
3341 stderr = subprocess .STDOUT , universal_newlines = True
3442 ) 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 ())
3945 if check and proc .wait () != 0 :
4046 raise subprocess .CalledProcessError (proc .returncode , cmd )
4147
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 ):
4449 if req_file and Path (req_file ).exists ():
4550 cmd = [
4651 str (python_exe ), "-m" , "pip" , "install" ,
4752 "-r" , req_file , "-c" , constraints ,
4853 "--pre" , "--no-index" , f"--find-links={ find_links } "
4954 ]
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 )
5257 else :
53- log_section (logfile , f"No { label } specified/skipped" )
58+ log_section (f"No { label } specified/skipped" )
5459
55-
56- def patch_winpython (python_exe , logfile ):
60+ def patch_winpython (python_exe ):
5761 cmd = [
5862 str (python_exe ), "-c" ,
5963 "from wppm import wppm; wppm.Distribution().patch_standard_packages('', to_movable=True)"
6064 ]
61- run_command (cmd , log_file = logfile )
62-
65+ run_command (cmd )
6366
6467def check_env_bat (winpydirbase : Path ):
6568 envbat = winpydirbase / "scripts" / "env.bat"
6669 if not envbat .exists ():
6770 raise FileNotFoundError (f"Missing env.bat at { envbat } " )
6871
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 ):
7173 pip_req = winpydirbase .parent / "requirement_temp.txt"
7274 with subprocess .Popen ([str (target_python ), "-m" , "pip" , "freeze" ], stdout = subprocess .PIPE ) as proc :
7375 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
7678 for kind in ("" , "local" ):
7779 out = winpydirbase .parent / f"pylock.{ file_postfix } _{ kind } .toml"
7880 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 )
9086 # 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 )
9989 # check equality
100- from filecmp import cmp
10190 web , local = "" , "local"
10291 if not cmp (winpydirbase .parent / f"requir.{ file_postfix } _{ web } .txt" , winpydirbase .parent / f"requir.{ file_postfix } _{ local } .txt" ):
10392 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
10695 print ("match ok " ,winpydirbase .parent / f"requir.{ file_postfix } _{ web } .txt" , winpydirbase .parent / f"requir.{ file_postfix } _{ local } .txt" )
10796
10897# --- Main Logic ---
109- def run_make_py (build_python , winpydirbase , args , logfile ):
98+ def run_make_py (build_python , winpydirbase , args ):
11099 from . import make
111100 make .make_all (
112101 args .release , args .release_level , basedir_wpy = winpydirbase ,
@@ -136,7 +125,7 @@ def main():
136125
137126 # compute paths (same as Step2)...
138127 build_python = Path (args .buildenv ) / "python.exe"
139- winpydirbase = Path (args .winpydirbase ) # from Step2 logic
128+ winpydirbase = Path (args .winpydirbase )
140129 target_python = winpydirbase / "python" / "python.exe"
141130
142131 # Setup paths and logs
@@ -145,117 +134,107 @@ def main():
145134 log_dir .mkdir (exist_ok = True )
146135 time_str = now .strftime ("%Y-%m-%d_at_%H%M" )
147136 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
150140 z = Path (winpydirbase ).name [(4 + len (args .arch )):- len (args .release_level )]
151141 tada = f"{ z [:1 ]} _{ z [1 :3 ]} _{ z [3 ]} _{ args .release } "
152142 winpyver2 = tada .replace ('_' , '.' )
153143 file_postfix = f"{ args .arch } -{ tada } { args .flavor } { args .release_level } "
154144
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)" )
158146
147+ log_section (f"🙏 Step 0: displace old { Path (winpydirbase )} " )
159148 delete_folder_if_exists (winpydirbase .parent , check_flavor = args .flavor ) #bu{flavor]}
160149
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 )
163152
164153 check_env_bat (winpydirbase )
165154
166- log_section (log_file , "🙏 Step 3: install requirements" )
155+ log_section ("🙏 Step 3: install requirements" )
167156
168157 for label , req in [
169158 ("Mandatory" , args .mandatory_req ),
170159 ("Pre" , args .pre_req ),
171160 ("Main" , args .requirements ),
172161 ]:
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 )
174163
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 )
177166
178167 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 } " )
180169 wheelhousereq = Path (args .wheelhousereq )
181170 kind = "local"
182171 out = winpydirbase .parent / f"pylock.{ file_postfix } _wheels{ kind } .toml"
183172 outreq = winpydirbase .parent / f"requir.{ file_postfix } _wheels{ kind } .txt"
184173 if wheelhousereq .is_file ():
185174 # Generate pylock from wheelhousereq
186175 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 )
190179 # 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 )
196182
197183 kind = ""
198184 outw = winpydirbase .parent / f"pylock.{ file_postfix } _wheels{ kind } .toml"
199185 outreqw = winpydirbase .parent / f"requir.{ file_postfix } _wheels{ kind } .txt"
200186 # Generate web pylock from local frozen hashes
201187 cmd = [str (target_python ), "-m" , "pip" , "lock" ,"--no-deps" , "--require-hashes" ,
202188 "-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 )
209192
210193 # Use wppm to download local from req made with web hashes
211194 wheelhouse = winpydirbase / "wheelhouse" / "included.wheels"
212195 cmd = [str (target_python ), "-X" , "utf8" , "-m" , "wppm" , str (out ), "-ws" , args .find_links ,
213196 "-wd" , str (wheelhouse )
214197 ]
215- print (cmd )
216- subprocess .run (cmd , stdout = open (log_file , 'a' ), stderr = subprocess .STDOUT , check = False )
217-
198+ run_command (cmd , check = False )
218199
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 )
222203
223204 # 6) generate changelog
224205 mdn = f"WinPython{ args .flavor } -{ args .arch } bit-{ winpyver2 } .md"
225206 out = f"WinPython{ args .flavor } -{ args .arch } bit-{ winpyver2 } _History.md"
226207 changelog_dir = log_dir .parent / "changelogs"
227208
228- log_section (log_file , f"🙏 Step 6: generate changelog { mdn } " )
209+ log_section (f"🙏 Step 6: generate changelog { mdn } " )
229210
230211 cmd = ["set" , f"WINPYVER2={ winpyver2 } &" , "set" , f"WINPYFLAVOR={ args .flavor } &" ,
231212 "set" , f"WINPYVER={ winpyver2 } { args .flavor } { args .release_level } &" ,
232- str (target_python ), "-c" ,
213+ str (target_python ), "-X" , "utf8" , "- c" ,
233214 (
234215 "from wppm import wppm;"
235216 "result = wppm.Distribution().generate_package_index_markdown();"
236217 f"open(r'{ winpydirbase .parent / mdn } ', 'w', encoding='utf-8').write(result)"
237218 )]
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 )
240220 shutil .copyfile (winpydirbase .parent / mdn , changelog_dir / mdn )
241221
242- cmd = [str (target_python ), "-c" ,
222+ cmd = [str (target_python ), "-X" , "utf8" , "- c" ,
243223 (
244224 "from wppm import diff;"
245225 f"result = diff.compare_package_indexes('{ winpyver2 } ', searchdir=r'{ changelog_dir } ', flavor=r'{ args .flavor } ', architecture={ args .arch } );"
246226 f"open(r'{ winpydirbase .parent / out } ', 'w', encoding='utf-8').write(result)"
247227 )]
248- subprocess . run (cmd , stdout = open ( log_file , 'a' ), stderr = subprocess . STDOUT , check = True )
228+ run_command (cmd , check = False )
249229 shutil .copyfile (winpydirbase .parent / out , changelog_dir / out )
250- log_section (log_file , "✅ Step 6 complete" )
230+ log_section ("✅ Step 6 complete" )
251231
252232 if args .create_installer != "" :
253- log_section (log_file , "🙏 Step 7 Create Installer" )
233+ log_section ("🙏 Step 7 Create Installer" )
254234 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 )
259238
260239if __name__ == '__main__' :
261240 main ()
0 commit comments