X Tutup
# -*- coding: utf-8 -*- # make.py # WinPython build script # Copyright © 2012 Pierre Raybaut # Copyright © 2014-2025+ The Winpython development team https://github.com/winpython/ # Licensed under the terms of the MIT License # (see wppm/__init__.py for details) import os import re import shutil from pathlib import Path from wppm import wppm, utils PORTABLE_DIRECTORY = Path(__file__).parent / "portable" assert PORTABLE_DIRECTORY.is_dir(), f"Portable directory not found: {PORTABLE_DIRECTORY}" def copy_items(source_directories: list[Path], target_directory: Path, verbose: bool = False): """Copies items from source directories to the target directory.""" target_directory.mkdir(parents=True, exist_ok=True) for source_dir in source_directories: if not source_dir.is_dir(): print(f"Warning: Source directory not found: {source_dir}") continue for source_item in source_dir.iterdir(): target_item = target_directory / source_item.name copy_function = shutil.copytree if source_item.is_dir() else shutil.copy2 try: copy_function(source_item, target_item) if verbose: print(f"Copied: {source_item} -> {target_item}") except Exception as e: print(f"Error copying {source_item} to {target_item}: {e}") def parse_list_argument(argument_value: str | list[str], separator=" ") -> list[str]: """Parse a separated list argument into a list of strings.""" if not argument_value: return [] return argument_value.split(separator) if isinstance(argument_value, str) else list(argument_value) class WinPythonDistributionBuilder: """Builds a WinPython distribution.""" def __init__(self, build_number: int, release_level: str, basedir_wpy: Path, source_dirs: Path, tools_directories: list[Path] = None, verbose: bool = False, flavor: str = ""): """ Initializes the WinPythonDistributionBuilder. Args: build_number: The build number (integer). release_level: The release level (e.g., "beta", ""). basedir_wpy: top directory of the build (c:\...\Wpy...) source_dirs: Directory containing wheel files for packages. tools_directories: List of directories containing development tools to include. verbose: Enable verbose output. flavor: WinPython flavor (e.g., "Barebone"). """ self.build_number = build_number self.release_level = release_level self.winpython_directory = Path(basedir_wpy) self.target_directory = self.winpython_directory.parent self.source_dirs = Path(source_dirs) self.tools_directories = tools_directories or [] self.verbose = verbose self.distribution: wppm.Distribution | None = None self.flavor = flavor self.python_zip_file: Path = self._get_python_zip_file() self.python_name = self.python_zip_file.stem self.python_directory_name = "python" def _get_python_zip_file(self) -> Path: """Finds the Python .zip file in the wheels directory.""" for source_item in self.source_dirs.iterdir(): if re.match(r"(pypy3|python-).*\.zip", source_item.name): return source_item raise RuntimeError(f"Could not find Python zip package in {self.source_dirs}") @property def winpython_version_name(self) -> str: """Returns the full WinPython version string.""" return f"{self.python_full_version}.{self.build_number}{self.flavor}{self.release_level}" @property def python_full_version(self) -> str: """Retrieves the Python full version string from the distribution.""" return utils.get_python_long_version(self.distribution.target) if self.distribution else "0.0.0" def _print_action(self, text: str): """Prints an action message with progress indicator.""" utils.print_box(text) if self.verbose else print(f"{text}...", end="", flush=True) def _extract_python_archive(self): """Extracts the Python zip archive to create the base Python environment.""" self._print_action("Extracting Python archive") utils.extract_archive(self.python_zip_file, self.winpython_directory) # Relocate to /python subfolder if needed (for newer structure) #2024-12-22 to /python expected_python_directory = self.winpython_directory / self.python_directory_name if self.python_directory_name != self.python_name and not expected_python_directory.is_dir(): os.rename(self.winpython_directory / self.python_name, expected_python_directory) def _copy_essential_files(self): """Copies pre-made objects""" self._print_action("Copying launchers") copy_items([PORTABLE_DIRECTORY / "launchers_final"], self.winpython_directory, self.verbose) tools_target_directory = self.winpython_directory / "t" self._print_action(f"Copying tools to {tools_target_directory}") copy_items(self.tools_directories, tools_target_directory, self.verbose) def _create_env_config(self): """Creates environment setup""" executable_name = self.distribution.short_exe if self.distribution else "python.exe" config = { "WINPYthon_exe": executable_name, "WINPYthon_subdirectory_name": self.python_directory_name, "WINPYVER": self.winpython_version_name, "WINPYVER2": f"{self.python_full_version}.{self.build_number}", "WINPYFLAVOR": self.flavor, "WINPYARCH": self.distribution.architecture if self.distribution else 64, } env_path = self.winpython_directory / "scripts" / "env.ini" env_path.parent.mkdir(parents=True, exist_ok=True) self._print_action(f"Creating env.ini environment {env_path}") env_path.write_text("\n".join(f"{k}={v}" for k, v in config.items())) def build(self): """Make or finalise WinPython distribution in the target directory""" print(f"Building WinPython with Python archive: {self.python_zip_file.name}") self._print_action(f"Creating WinPython {self.winpython_directory} base directory") if self.winpython_directory.is_dir() and len(self.winpython_directory.parts)>=4: shutil.rmtree(self.winpython_directory) # preventive re-Creation of settings directory (self.winpython_directory / "settings" / "AppData" / "Roaming").mkdir(parents=True, exist_ok=True) self._extract_python_archive() self.distribution = wppm.Distribution(self.winpython_directory / self.python_directory_name, verbose=self.verbose) self._copy_essential_files() self._create_env_config() def make_all(build_number: int, release_level: str, basedir_wpy: Path = None, source_dirs: Path = None, toolsdirs: str | list[Path] = None, verbose: bool = False, flavor: str = ""): """ Make a WinPython distribution for a given set of parameters: Args: build_number: build number [int] release_level: release level (e.g. 'beta1', '') [str] basedir_wpy: top directory of the build (c:\...\Wpy...) verbose: Enable verbose output (bool). flavor: WinPython flavor (str). source_dirs: the python.zip toolsdirs: Directory with development tools r'D:\WinPython\basedir34\t.Slim' """ assert basedir_wpy is not None, "The *winpython_dirname* directory must be specified" tools_directories = [Path(d) for d in parse_list_argument(toolsdirs, ",")] utils.print_box(f"Making WinPython at {basedir_wpy}") os.makedirs(basedir_wpy, exist_ok=True) builder = WinPythonDistributionBuilder( build_number, release_level, Path(basedir_wpy), verbose=verbose, flavor=flavor, source_dirs=source_dirs, tools_directories=tools_directories) builder.build() if __name__ == "__main__": make_all( build_number=1, release_level="b3", basedir_wpy=r"D:\WinPython\bd314\budot\WPy64-31401b3", verbose=True, flavor="dot", source_dirs=r"D:\WinPython\bd314\packages.win-amd64", toolsdirs=r"D:\WinPython\bd314\t.Slim", )
X Tutup