22#
33# WinPython build script
44# Copyright © 2012 Pierre Raybaut
5- # Copyright © 2014-2024+ The Winpython development team https://github.com/winpython/
5+ # Copyright © 2014-2025+ The Winpython development team https://github.com/winpython/
66# Licensed under the terms of the MIT License
77# (see winpython/__init__.py for details)
88
1212import subprocess
1313import sys
1414from pathlib import Path
15-
1615from winpython import wppm , utils
17- # Local imports
16+ # Local import
1817import diff
1918
2019# Define constant paths for clarity
2726
2827def find_7zip_executable () -> str :
2928 """Locates the 7-Zip executable (7z.exe)."""
30- possible_program_files = [
31- Path (r"C:\Program Files" ),
32- Path (r"C:\Program Files (x86)" ),
33- Path (sys .prefix ).parent / "t" ,
34- ]
29+ possible_program_files = [r"C:\Program Files" , r"C:\Program Files (x86)" , Path (sys .prefix ).parent / "t" ]
3530 for base_dir in possible_program_files :
36- if (executable_path := base_dir / "7-Zip" / "7z.exe" ).is_file ():
31+ if (executable_path := Path ( base_dir ) / "7-Zip" / "7z.exe" ).is_file ():
3732 return str (executable_path )
3833 raise RuntimeError ("7ZIP is not installed on this computer." )
3934
4035def replace_lines_in_file (filepath : Path , replacements : list [tuple [str , str ]]):
4136 """
4237 Replaces lines in a file that start with a given prefix.
43-
4438 Args:
4539 filepath: Path to the file to modify.
4640 replacements: A list of tuples, where each tuple contains:
4741 - The prefix of the line to replace (str).
4842 - The new text for the line (str).
4943 """
50- try :
51- with open (filepath , "r" ) as f :
52- lines = f .readlines ()
53- except FileNotFoundError :
54- print (f"Error: File not found: { filepath } " )
55- return
56-
44+ with open (filepath , "r" ) as f :
45+ lines = f .readlines ()
5746 updated_lines = lines .copy () # Create a mutable copy of lines
5847
5948 for index , line in enumerate (lines ):
@@ -62,12 +51,9 @@ def replace_lines_in_file(filepath: Path, replacements: list[tuple[str, str]]):
6251 if line .startswith (start_prefix + "=" ):
6352 updated_lines [index ] = f"{ start_prefix } ={ new_text } \n "
6453
65- try :
66- with open (filepath , "w" ) as f :
54+ with open (filepath , "w" ) as f :
6755 f .writelines (updated_lines )
68- print (f"Updated 7-zip script: { filepath } " )
69- except Exception as e :
70- print (f"Error writing to file { filepath } : { e } " )
56+ print (f"Updated 7-zip script: { filepath } " )
7157
7258def build_installer_7zip (script_template_path : Path , output_script_path : Path , replacements : list [tuple [str , str ]]):
7359 """
@@ -96,7 +82,7 @@ def build_installer_7zip(script_template_path: Path, output_script_path: Path, r
9682 except subprocess .CalledProcessError as e :
9783 print (f"Error executing 7-Zip script: { e } " , file = sys .stderr )
9884
99- def _copy_items (source_directories : list [Path ], target_directory : Path , verbose : bool = False ):
85+ def copy_items (source_directories : list [Path ], target_directory : Path , verbose : bool = False ):
10086 """Copies items from source directories to the target directory."""
10187 target_directory .mkdir (parents = True , exist_ok = True )
10288 for source_dir in source_directories :
@@ -114,7 +100,7 @@ def _copy_items(source_directories: list[Path], target_directory: Path, verbose:
114100 except Exception as e :
115101 print (f"Error copying { source_item } to { target_item } : { e } " )
116102
117- def _parse_list_argument (argument_value : str | list [str ], separator = " " ) -> list [str ]:
103+ def parse_list_argument (argument_value : str | list [str ], separator = " " ) -> list [str ]:
118104 """Parse a separated list argument into a list of strings."""
119105 if argument_value is None :
120106 return []
@@ -125,7 +111,7 @@ def _parse_list_argument(argument_value: str | list[str], separator=" ") -> list
125111class WinPythonDistributionBuilder :
126112 """Builds a WinPython distribution."""
127113
128- NODEJS_RELATIVE_PATH = r"\ n" # Relative path within WinPython dir
114+ NODEJS_RELATIVE_PATH = " n" # Relative path within WinPython dir
129115
130116 def __init__ (self , build_number : int , release_level : str , target_directory : Path , wheels_directory : Path ,
131117 tools_directories : list [Path ] = None , documentation_directories : list [Path ] = None , verbose : bool = False ,
@@ -205,20 +191,16 @@ def get_tool_path(relative_path):
205191 path = self .winpython_directory / relative_path if self .winpython_directory else None
206192 return path if path and (path .is_file () or path .is_dir ()) else None
207193
208- nodejs_path = get_tool_path (self .NODEJS_RELATIVE_PATH )
209- if nodejs_path :
210- node_version = utils .get_nodejs_version (str (nodejs_path ))
211- installed_tools .append (("Nodejs" , node_version ))
212- npm_version = utils .get_npmjs_version (str (nodejs_path ))
213- installed_tools .append (("npmjs" , npm_version ))
214-
215- pandoc_executable = get_tool_path (r"\t\pandoc.exe" )
216- if pandoc_executable :
194+ if nodejs_path := get_tool_path (self .NODEJS_RELATIVE_PATH ):
195+ node_version = utils .get_nodejs_version (nodejs_path )
196+ npm_version = utils .get_npmjs_version (nodejs_path )
197+ installed_tools += [("Nodejs" , node_version ), ("npmjs" , npm_version )]
198+
199+ if pandoc_executable := get_tool_path ("t/pandoc.exe" ):
217200 pandoc_version = utils .get_pandoc_version (str (pandoc_executable .parent ))
218201 installed_tools .append (("Pandoc" , pandoc_version ))
219-
220- vscode_executable = get_tool_path (r"\t\VSCode\Code.exe" )
221- if vscode_executable :
202+
203+ if vscode_executable := get_tool_path ("t/VSCode/Code.exe" ):
222204 vscode_version = utils .getFileProperties (str (vscode_executable ))["FileVersion" ]
223205 installed_tools .append (("VSCode" , vscode_version ))
224206
@@ -278,7 +260,7 @@ def pre_path_entries(self) -> list[str]:
278260 "DLLs" ,
279261 "Scripts" ,
280262 r"..\t" ,
281- r ".." + self .NODEJS_RELATIVE_PATH ,
263+ "..\\ " + self .NODEJS_RELATIVE_PATH ,
282264 ]
283265
284266 def create_installer_7zip (self , installer_type : str = ".exe" ):
@@ -320,23 +302,23 @@ def _extract_python_archive(self):
320302 def _copy_essential_files (self ):
321303 """Copies pre-made objects"""
322304 self ._print_action ("Copying default scripts" )
323- _copy_items ([PORTABLE_DIRECTORY / "scripts" ], self .winpython_directory / "scripts" , self .verbose )
305+ copy_items ([PORTABLE_DIRECTORY / "scripts" ], self .winpython_directory / "scripts" , self .verbose )
324306
325307 self ._print_action ("Copying launchers" )
326- _copy_items ([PORTABLE_DIRECTORY / "launchers_final" ], self .winpython_directory , self .verbose )
308+ copy_items ([PORTABLE_DIRECTORY / "launchers_final" ], self .winpython_directory , self .verbose )
327309
328310 docs_target_directory = self .winpython_directory / "notebooks" / "docs"
329311 self ._print_action (f"Copying documentation to { docs_target_directory } " )
330- _copy_items (self .documentation_directories , docs_target_directory , self .verbose )
312+ copy_items (self .documentation_directories , docs_target_directory , self .verbose )
331313
332314 tools_target_directory = self .winpython_directory / "t"
333315 self ._print_action (f"Copying tools to { tools_target_directory } " )
334- _copy_items (self .tools_directories , tools_target_directory , self .verbose )
316+ copy_items (self .tools_directories , tools_target_directory , self .verbose )
335317
336- # Special handling for Node.js to move it up one level
337318 if (nodejs_current_directory := tools_target_directory / "n" ).is_dir ():
319+ self ._print_action (f"moving tools from { nodejs_current_directory } to { tools_target_directory .parent / self .NODEJS_RELATIVE_PATH } " )
338320 try :
339- shutil .move (nodejs_current_directory , self . winpython_directory / self .NODEJS_RELATIVE_PATH )
321+ shutil .move (nodejs_current_directory , tools_target_directory . parent / self .NODEJS_RELATIVE_PATH )
340322 except Exception as e :
341323 print (f"Error moving Node.js directory: { e } " )
342324
@@ -366,7 +348,6 @@ def _create_initial_batch_scripts(self):
366348 def build (self , rebuild : bool = True , requirements_files_list = None , winpy_dirname : str = None ):
367349 """Make or finalise WinPython distribution in the target directory"""
368350 print (f"Building WinPython with Python archive: { self .python_zip_file .name } " )
369-
370351 if winpy_dirname is None :
371352 raise RuntimeError ("WinPython base directory to create is undefined" )
372353 else :
@@ -392,7 +373,6 @@ def build(self, rebuild: bool = True, requirements_files_list=None, winpy_dirnam
392373 utils .python_execmodule ("ensurepip" , self .distribution .target )
393374 self .distribution .patch_standard_packages ("pip" )
394375
395- # Upgrade essential packages
396376 essential_packages = ["pip" , "setuptools" , "wheel" , "winpython" ]
397377 for package_name in essential_packages :
398378 actions = ["install" , "--upgrade" , "--pre" , package_name ] + self .install_options
@@ -405,33 +385,29 @@ def build(self, rebuild: bool = True, requirements_files_list=None, winpy_dirnam
405385 actions = ["install" , "-r" , req ]
406386 if self .install_options is not None :
407387 actions += self .install_options
408- self ._print_action (f"piping { ' ' .join (actions )} " )
388+ self ._print_action (f"Piping: { ' ' .join (actions )} " )
409389 self .distribution .do_pip_action (actions )
410390 self .distribution .patch_standard_packages ()
411391
412392 self ._print_action ("Cleaning up distribution" )
413393 self .distribution .clean_up () # still usefull ?
414- # Writing package index
415394 self ._print_action ("Writing package index" )
416- # winpyver2 = the version without build part but with self.distribution.architecture
417395 self .winpyver2 = f"{ self .python_full_version } .{ self .build_number } "
418396 output_markdown_filename = str (self .winpython_directory .parent / f"WinPython{ self .flavor } -{ self .distribution .architecture } bit-{ self .winpyver2 } .md" )
419- open (output_markdown_filename , "w" , encoding = 'utf-8' ).write (self .package_index_markdown )
397+ with open (output_markdown_filename , "w" , encoding = 'utf-8' ) as f :
398+ f .write (self .package_index_markdown )
420399
421- # Writing changelog
422400 self ._print_action ("Writing changelog" )
423401 shutil .copyfile (output_markdown_filename , str (Path (CHANGELOGS_DIRECTORY ) / Path (output_markdown_filename ).name ))
424402 diff .write_changelog (self .winpyver2 , None , self .base_directory , self .flavor , self .distribution .architecture )
425403
426-
427404def rebuild_winpython_package (source_directory : Path , target_directory : Path , architecture : int = 64 , verbose : bool = False ):
428405 """Rebuilds the winpython package from source using flit."""
429406 for filename in os .listdir (target_directory ):
430407 if filename .startswith ("winpython-" ) and filename .endswith ((".exe" , ".whl" , ".gz" )):
431408 os .remove (Path (target_directory ) / filename )
432409 utils .buildflit_wininst (source_directory , copy_to = target_directory , verbose = verbose )
433410
434-
435411def make_all (build_number : int , release_level : str , pyver : str , architecture : int , basedir : Path ,
436412 verbose : bool = False , rebuild : bool = True , create_installer : str = "True" , install_options = ["--no-index" ],
437413 flavor : str = "" , requirements : str | list [Path ] = None , find_links : str | list [Path ] = None ,
@@ -461,18 +437,17 @@ def make_all(build_number: int, release_level: str, pyver: str, architecture: in
461437 assert basedir is not None , "The *basedir* directory must be specified"
462438 assert architecture in (32 , 64 )
463439
464- # Parse list arguments
465- tools_dirs_list = _parse_list_argument (toolsdirs , "," )
466- docs_dirs_list = _parse_list_argument (docsdirs , "," )
467- install_options_list = _parse_list_argument (install_options , " " )
468- find_links_dirs_list = _parse_list_argument (find_links , "," )
469- requirements_files_list = [Path (f ) for f in _parse_list_argument (requirements , "," ) if f ] # ensure Path objects
440+ tools_dirs_list = parse_list_argument (toolsdirs , "," )
441+ docs_dirs_list = parse_list_argument (docsdirs , "," )
442+ install_options_list = parse_list_argument (install_options , " " )
443+ find_links_dirs_list = parse_list_argument (find_links , "," )
444+ requirements_files_list = [Path (f ) for f in parse_list_argument (requirements , "," ) if f ]
470445 find_links_options = [f"--find-links={ link } " for link in find_links_dirs_list + [source_dirs ]]
471446 build_directory = str (Path (basedir ) / ("bu" + flavor ))
472447
473448 if rebuild :
474449 utils .print_box (f"Making WinPython { architecture } bits at { Path (basedir ) / ('bu' + flavor )} " )
475- os .makedirs (Path (build_directory ), exist_ok = True )
450+ os .makedirs (Path (build_directory ), exist_ok = True )
476451 # use source_dirs as the directory to re-build Winpython wheel
477452 winpython_source_dir = Path (__file__ ).resolve ().parent
478453 rebuild_winpython_package (winpython_source_dir , source_dirs , architecture , verbose )
0 commit comments