2
2
#
3
3
# WinPython build script
4
4
# 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/
6
6
# Licensed under the terms of the MIT License
7
7
# (see winpython/__init__.py for details)
8
8
12
12
import subprocess
13
13
import sys
14
14
from pathlib import Path
15
-
16
15
from winpython import wppm , utils
17
- # Local imports
16
+ # Local import
18
17
import diff
19
18
20
19
# Define constant paths for clarity
27
26
28
27
def find_7zip_executable () -> str :
29
28
"""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" ]
35
30
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 ():
37
32
return str (executable_path )
38
33
raise RuntimeError ("7ZIP is not installed on this computer." )
39
34
40
35
def replace_lines_in_file (filepath : Path , replacements : list [tuple [str , str ]]):
41
36
"""
42
37
Replaces lines in a file that start with a given prefix.
43
-
44
38
Args:
45
39
filepath: Path to the file to modify.
46
40
replacements: A list of tuples, where each tuple contains:
47
41
- The prefix of the line to replace (str).
48
42
- The new text for the line (str).
49
43
"""
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 ()
57
46
updated_lines = lines .copy () # Create a mutable copy of lines
58
47
59
48
for index , line in enumerate (lines ):
@@ -62,12 +51,9 @@ def replace_lines_in_file(filepath: Path, replacements: list[tuple[str, str]]):
62
51
if line .startswith (start_prefix + "=" ):
63
52
updated_lines [index ] = f"{ start_prefix } ={ new_text } \n "
64
53
65
- try :
66
- with open (filepath , "w" ) as f :
54
+ with open (filepath , "w" ) as f :
67
55
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 } " )
71
57
72
58
def build_installer_7zip (script_template_path : Path , output_script_path : Path , replacements : list [tuple [str , str ]]):
73
59
"""
@@ -96,7 +82,7 @@ def build_installer_7zip(script_template_path: Path, output_script_path: Path, r
96
82
except subprocess .CalledProcessError as e :
97
83
print (f"Error executing 7-Zip script: { e } " , file = sys .stderr )
98
84
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 ):
100
86
"""Copies items from source directories to the target directory."""
101
87
target_directory .mkdir (parents = True , exist_ok = True )
102
88
for source_dir in source_directories :
@@ -114,7 +100,7 @@ def _copy_items(source_directories: list[Path], target_directory: Path, verbose:
114
100
except Exception as e :
115
101
print (f"Error copying { source_item } to { target_item } : { e } " )
116
102
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 ]:
118
104
"""Parse a separated list argument into a list of strings."""
119
105
if argument_value is None :
120
106
return []
@@ -125,7 +111,7 @@ def _parse_list_argument(argument_value: str | list[str], separator=" ") -> list
125
111
class WinPythonDistributionBuilder :
126
112
"""Builds a WinPython distribution."""
127
113
128
- NODEJS_RELATIVE_PATH = r"\ n" # Relative path within WinPython dir
114
+ NODEJS_RELATIVE_PATH = " n" # Relative path within WinPython dir
129
115
130
116
def __init__ (self , build_number : int , release_level : str , target_directory : Path , wheels_directory : Path ,
131
117
tools_directories : list [Path ] = None , documentation_directories : list [Path ] = None , verbose : bool = False ,
@@ -205,20 +191,16 @@ def get_tool_path(relative_path):
205
191
path = self .winpython_directory / relative_path if self .winpython_directory else None
206
192
return path if path and (path .is_file () or path .is_dir ()) else None
207
193
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" ):
217
200
pandoc_version = utils .get_pandoc_version (str (pandoc_executable .parent ))
218
201
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" ):
222
204
vscode_version = utils .getFileProperties (str (vscode_executable ))["FileVersion" ]
223
205
installed_tools .append (("VSCode" , vscode_version ))
224
206
@@ -278,7 +260,7 @@ def pre_path_entries(self) -> list[str]:
278
260
"DLLs" ,
279
261
"Scripts" ,
280
262
r"..\t" ,
281
- r ".." + self .NODEJS_RELATIVE_PATH ,
263
+ "..\\ " + self .NODEJS_RELATIVE_PATH ,
282
264
]
283
265
284
266
def create_installer_7zip (self , installer_type : str = ".exe" ):
@@ -320,23 +302,23 @@ def _extract_python_archive(self):
320
302
def _copy_essential_files (self ):
321
303
"""Copies pre-made objects"""
322
304
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 )
324
306
325
307
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 )
327
309
328
310
docs_target_directory = self .winpython_directory / "notebooks" / "docs"
329
311
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 )
331
313
332
314
tools_target_directory = self .winpython_directory / "t"
333
315
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 )
335
317
336
- # Special handling for Node.js to move it up one level
337
318
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 } " )
338
320
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 )
340
322
except Exception as e :
341
323
print (f"Error moving Node.js directory: { e } " )
342
324
@@ -366,7 +348,6 @@ def _create_initial_batch_scripts(self):
366
348
def build (self , rebuild : bool = True , requirements_files_list = None , winpy_dirname : str = None ):
367
349
"""Make or finalise WinPython distribution in the target directory"""
368
350
print (f"Building WinPython with Python archive: { self .python_zip_file .name } " )
369
-
370
351
if winpy_dirname is None :
371
352
raise RuntimeError ("WinPython base directory to create is undefined" )
372
353
else :
@@ -392,7 +373,6 @@ def build(self, rebuild: bool = True, requirements_files_list=None, winpy_dirnam
392
373
utils .python_execmodule ("ensurepip" , self .distribution .target )
393
374
self .distribution .patch_standard_packages ("pip" )
394
375
395
- # Upgrade essential packages
396
376
essential_packages = ["pip" , "setuptools" , "wheel" , "winpython" ]
397
377
for package_name in essential_packages :
398
378
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
405
385
actions = ["install" , "-r" , req ]
406
386
if self .install_options is not None :
407
387
actions += self .install_options
408
- self ._print_action (f"piping { ' ' .join (actions )} " )
388
+ self ._print_action (f"Piping: { ' ' .join (actions )} " )
409
389
self .distribution .do_pip_action (actions )
410
390
self .distribution .patch_standard_packages ()
411
391
412
392
self ._print_action ("Cleaning up distribution" )
413
393
self .distribution .clean_up () # still usefull ?
414
- # Writing package index
415
394
self ._print_action ("Writing package index" )
416
- # winpyver2 = the version without build part but with self.distribution.architecture
417
395
self .winpyver2 = f"{ self .python_full_version } .{ self .build_number } "
418
396
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 )
420
399
421
- # Writing changelog
422
400
self ._print_action ("Writing changelog" )
423
401
shutil .copyfile (output_markdown_filename , str (Path (CHANGELOGS_DIRECTORY ) / Path (output_markdown_filename ).name ))
424
402
diff .write_changelog (self .winpyver2 , None , self .base_directory , self .flavor , self .distribution .architecture )
425
403
426
-
427
404
def rebuild_winpython_package (source_directory : Path , target_directory : Path , architecture : int = 64 , verbose : bool = False ):
428
405
"""Rebuilds the winpython package from source using flit."""
429
406
for filename in os .listdir (target_directory ):
430
407
if filename .startswith ("winpython-" ) and filename .endswith ((".exe" , ".whl" , ".gz" )):
431
408
os .remove (Path (target_directory ) / filename )
432
409
utils .buildflit_wininst (source_directory , copy_to = target_directory , verbose = verbose )
433
410
434
-
435
411
def make_all (build_number : int , release_level : str , pyver : str , architecture : int , basedir : Path ,
436
412
verbose : bool = False , rebuild : bool = True , create_installer : str = "True" , install_options = ["--no-index" ],
437
413
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
461
437
assert basedir is not None , "The *basedir* directory must be specified"
462
438
assert architecture in (32 , 64 )
463
439
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 ]
470
445
find_links_options = [f"--find-links={ link } " for link in find_links_dirs_list + [source_dirs ]]
471
446
build_directory = str (Path (basedir ) / ("bu" + flavor ))
472
447
473
448
if rebuild :
474
449
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 )
476
451
# use source_dirs as the directory to re-build Winpython wheel
477
452
winpython_source_dir = Path (__file__ ).resolve ().parent
478
453
rebuild_winpython_package (winpython_source_dir , source_dirs , architecture , verbose )
0 commit comments