Skip to content

Commit 0a99ef4

Browse files
authored
Merge pull request winpython#1697 from stonebig/master
re-organize as a WinPython Module containing make.py and portable dir
2 parents ca85133 + 2b13a0b commit 0a99ef4

File tree

94 files changed

+181
-1
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

94 files changed

+181
-1
lines changed

generate_a_winpython_distro.bat

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ REM === Begin Build ===
5050
call :log_section Creating Build Infrastructure
5151
call :activate_env "%my_buildenv%"
5252

53-
python.exe -c "from make import make_all; make_all(%my_release%, '%my_release_level%', basedir_wpy=r'%my_WINPYDIRBASE%', verbose=True, flavor='%my_flavor%', source_dirs=r'%my_source_dirs%', toolsdirs=r'%my_toolsdirs%')" >>"%my_archive_log%"
53+
python.exe -c "from winpython import make;make.make_all(%my_release%, '%my_release_level%', basedir_wpy=r'%my_WINPYDIRBASE%', verbose=True, flavor='%my_flavor%', source_dirs=r'%my_source_dirs%', toolsdirs=r'%my_toolsdirs%')" >>"%my_archive_log%"
5454

5555

5656
REM === Check env.bat has been created ===

winpython/__init__.py

Whitespace-only changes.

winpython/__main__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import os
2+
import sys
3+
if __name__ == "__main__":
4+
from winpython import make
5+
6+
sys.exit(make.main())

winpython/make.py

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# WinPython build script
4+
# Copyright © 2012 Pierre Raybaut
5+
# Copyright © 2014-2025+ The Winpython development team https://github.com/winpython/
6+
# Licensed under the terms of the MIT License
7+
# (see wppm/__init__.py for details)
8+
9+
import os
10+
import re
11+
import shutil
12+
from pathlib import Path
13+
from wppm import wppm, utils
14+
15+
PORTABLE_DIRECTORY = Path(__file__).parent / "portable"
16+
assert PORTABLE_DIRECTORY.is_dir(), f"Portable directory not found: {PORTABLE_DIRECTORY}"
17+
18+
def copy_items(source_directories: list[Path], target_directory: Path, verbose: bool = False):
19+
"""Copies items from source directories to the target directory."""
20+
target_directory.mkdir(parents=True, exist_ok=True)
21+
for source_dir in source_directories:
22+
if not source_dir.is_dir():
23+
print(f"Warning: Source directory not found: {source_dir}")
24+
continue
25+
for source_item in source_dir.iterdir():
26+
target_item = target_directory / source_item.name
27+
copy_function = shutil.copytree if source_item.is_dir() else shutil.copy2
28+
try:
29+
copy_function(source_item, target_item)
30+
if verbose:
31+
print(f"Copied: {source_item} -> {target_item}")
32+
except Exception as e:
33+
print(f"Error copying {source_item} to {target_item}: {e}")
34+
35+
def parse_list_argument(argument_value: str | list[str], separator=" ") -> list[str]:
36+
"""Parse a separated list argument into a list of strings."""
37+
if not argument_value: return []
38+
return argument_value.split(separator) if isinstance(argument_value, str) else list(argument_value)
39+
40+
class WinPythonDistributionBuilder:
41+
"""Builds a WinPython distribution."""
42+
43+
def __init__(self, build_number: int, release_level: str, basedir_wpy: Path,
44+
source_dirs: Path, tools_directories: list[Path] = None,
45+
verbose: bool = False, flavor: str = ""):
46+
"""
47+
Initializes the WinPythonDistributionBuilder.
48+
Args:
49+
build_number: The build number (integer).
50+
release_level: The release level (e.g., "beta", "").
51+
basedir_wpy: top directory of the build (c:\...\Wpy...)
52+
source_dirs: Directory containing wheel files for packages.
53+
tools_directories: List of directories containing development tools to include.
54+
verbose: Enable verbose output.
55+
flavor: WinPython flavor (e.g., "Barebone").
56+
"""
57+
self.build_number = build_number
58+
self.release_level = release_level
59+
self.winpython_directory = Path(basedir_wpy)
60+
self.target_directory = self.winpython_directory.parent
61+
self.source_dirs = Path(source_dirs)
62+
self.tools_directories = tools_directories or []
63+
self.verbose = verbose
64+
self.distribution: wppm.Distribution | None = None
65+
self.flavor = flavor
66+
self.python_zip_file: Path = self._get_python_zip_file()
67+
self.python_name = self.python_zip_file.stem
68+
self.python_directory_name = "python"
69+
70+
def _get_python_zip_file(self) -> Path:
71+
"""Finds the Python .zip file in the wheels directory."""
72+
for source_item in self.source_dirs.iterdir():
73+
if re.match(r"(pypy3|python-).*\.zip", source_item.name):
74+
return source_item
75+
raise RuntimeError(f"Could not find Python zip package in {self.source_dirs}")
76+
77+
@property
78+
def winpython_version_name(self) -> str:
79+
"""Returns the full WinPython version string."""
80+
return f"{self.python_full_version}.{self.build_number}{self.flavor}{self.release_level}"
81+
82+
@property
83+
def python_full_version(self) -> str:
84+
"""Retrieves the Python full version string from the distribution."""
85+
return utils.get_python_long_version(self.distribution.target) if self.distribution else "0.0.0"
86+
87+
def _print_action(self, text: str):
88+
"""Prints an action message with progress indicator."""
89+
utils.print_box(text) if self.verbose else print(f"{text}...", end="", flush=True)
90+
91+
def _extract_python_archive(self):
92+
"""Extracts the Python zip archive to create the base Python environment."""
93+
self._print_action("Extracting Python archive")
94+
utils.extract_archive(self.python_zip_file, self.winpython_directory)
95+
# Relocate to /python subfolder if needed (for newer structure) #2024-12-22 to /python
96+
expected_python_directory = self.winpython_directory / self.python_directory_name
97+
if self.python_directory_name != self.python_name and not expected_python_directory.is_dir():
98+
os.rename(self.winpython_directory / self.python_name, expected_python_directory)
99+
100+
def _copy_essential_files(self):
101+
"""Copies pre-made objects"""
102+
self._print_action("Copying launchers")
103+
copy_items([PORTABLE_DIRECTORY / "launchers_final"], self.winpython_directory, self.verbose)
104+
105+
tools_target_directory = self.winpython_directory / "t"
106+
self._print_action(f"Copying tools to {tools_target_directory}")
107+
copy_items(self.tools_directories, tools_target_directory, self.verbose)
108+
109+
def _create_env_config(self):
110+
"""Creates environment setup"""
111+
executable_name = self.distribution.short_exe if self.distribution else "python.exe"
112+
config = {
113+
"WINPYthon_exe": executable_name,
114+
"WINPYthon_subdirectory_name": self.python_directory_name,
115+
"WINPYVER": self.winpython_version_name,
116+
"WINPYVER2": f"{self.python_full_version}.{self.build_number}",
117+
"WINPYFLAVOR": self.flavor,
118+
"WINPYARCH": self.distribution.architecture if self.distribution else 64,
119+
}
120+
env_path = self.winpython_directory / "scripts" / "env.ini"
121+
env_path.parent.mkdir(parents=True, exist_ok=True)
122+
self._print_action(f"Creating env.ini environment {env_path}")
123+
env_path.write_text("\n".join(f"{k}={v}" for k, v in config.items()))
124+
125+
def build(self):
126+
"""Make or finalise WinPython distribution in the target directory"""
127+
print(f"Building WinPython with Python archive: {self.python_zip_file.name}")
128+
self._print_action(f"Creating WinPython {self.winpython_directory} base directory")
129+
if self.winpython_directory.is_dir() and len(self.winpython_directory.parts)>=4:
130+
shutil.rmtree(self.winpython_directory)
131+
# preventive re-Creation of settings directory
132+
(self.winpython_directory / "settings" / "AppData" / "Roaming").mkdir(parents=True, exist_ok=True)
133+
134+
self._extract_python_archive()
135+
self.distribution = wppm.Distribution(self.winpython_directory / self.python_directory_name, verbose=self.verbose)
136+
self._copy_essential_files()
137+
self._create_env_config()
138+
139+
def make_all(build_number: int, release_level: str, basedir_wpy: Path = None,
140+
source_dirs: Path = None, toolsdirs: str | list[Path] = None,
141+
verbose: bool = False, flavor: str = ""):
142+
"""
143+
Make a WinPython distribution for a given set of parameters:
144+
Args:
145+
build_number: build number [int]
146+
release_level: release level (e.g. 'beta1', '') [str]
147+
basedir_wpy: top directory of the build (c:\...\Wpy...)
148+
verbose: Enable verbose output (bool).
149+
flavor: WinPython flavor (str).
150+
source_dirs: the python.zip
151+
toolsdirs: Directory with development tools r'D:\WinPython\basedir34\t.Slim'
152+
"""
153+
assert basedir_wpy is not None, "The *winpython_dirname* directory must be specified"
154+
155+
tools_directories = [Path(d) for d in parse_list_argument(toolsdirs, ",")]
156+
utils.print_box(f"Making WinPython at {basedir_wpy}")
157+
os.makedirs(basedir_wpy, exist_ok=True)
158+
159+
builder = WinPythonDistributionBuilder(
160+
build_number, release_level, Path(basedir_wpy),
161+
verbose=verbose, flavor=flavor,
162+
source_dirs=source_dirs, tools_directories=tools_directories)
163+
builder.build()
164+
165+
if __name__ == "__main__":
166+
make_all(
167+
build_number=1,
168+
release_level="b3",
169+
basedir_wpy=r"D:\WinPython\bd314\budot\WPy64-31401b3",
170+
verbose=True,
171+
flavor="dot",
172+
source_dirs=r"D:\WinPython\bd314\packages.win-amd64",
173+
toolsdirs=r"D:\WinPython\bd314\t.Slim",
174+
)

0 commit comments

Comments
 (0)