diff --git a/generate_a_winpython_distro.bat b/generate_a_winpython_distro.bat index 0b466928..51c7dad0 100644 --- a/generate_a_winpython_distro.bat +++ b/generate_a_winpython_distro.bat @@ -2,7 +2,7 @@ rem generate_a_winpython_distro.bat: to be launched from a winpython directory, @echo on REM Initialize variables -if "%my_release_level%"=="" set my_release_level=b2 +if "%my_release_level%"=="" set my_release_level=b3 if "%my_create_installer%"=="" set my_create_installer=True rem Set archive directory and log file @@ -101,21 +101,47 @@ echo -------------------------------------- >>%my_archive_log% python -m pip install -r %my_requirements% -c %my_constraints% --pre --no-index --trusted-host=None --find-links=%my_find_links% >>%my_archive_log% python -c "from winpython import wppm;dist=wppm.Distribution(r'%WINPYDIR%');dist.patch_standard_packages('spyder', to_movable=True)" -REM Archive success +REM Add Wheelhouse (to replace per pip lock direct ? would allow paralellism) echo -------------------------------------- >>%my_archive_log% -echo "(%date% %time%) Archive success">>%my_archive_log% +echo "(%date% %time%) Add lockfile wheels">>%my_archive_log% echo -------------------------------------- >>%my_archive_log% -%target_python_exe% -m pip freeze > %my_archive_log%.packages_versions.txt - -REM Generate changelog and binaries -echo "(%date% %time%) Generate changelog and binaries">>%my_archive_log% set path=%my_original_path% -cd /D %~dp0 -call %my_buildenv%\scripts\env.bat -python.exe -c "from make import *;make_all(%my_release%, '%my_release_level%', pyver='%my_pyver%', basedir=r'%my_basedir%', verbose=True, architecture=%my_arch%, flavor='%my_flavor%', install_options=r'%my_install_options%', find_links=r'%my_find_links%', source_dirs=r'%my_source_dirs%', create_installer='%my_create_installer%', rebuild=False, python_target_release='%my_python_target_release%')" >> %my_archive_log% +@echo on +call %my_WINPYDIRBASE%\scripts\env.bat +@echo on +set WINPYVERLOCK=%WINPYVER2:.=_% +set pylockinclude=%my_root_dir_for_builds%\bd%my_python_target%\bu%addlockfile%\pylock.%addlockfile%-%WINPYARCH%bit-%WINPYVERLOCK%.toml +echo pylockinclude="%pylockinclude%" +if not "Z%addlockfile%Z"=="ZZ" if exist "%pylockinclude%" ( +echo %my_WINPYDIRBASE%\python\scripts\wppm.exe "%pylockinclude%" -ws "%my_find_links%" -wd "%my_WINPYDIRBASE%\wheelhouse\included.wheels">>%my_archive_log% +%my_WINPYDIRBASE%\python\scripts\wppm.exe "%pylockinclude%" -ws "%my_find_links%" -wd "%my_WINPYDIRBASE%\wheelhouse\included.wheels" +) -echo -------------------------------------- >>%my_archive_log% -echo "(%date% %time%) generate pylock.tomle files and requirement_with_hash.txt files">>%my_archive_log% +@echo on +echo wheelhousereq=%wheelhousereq% +set LOCKDIR=%WINPYDIRBASE%\..\ +set pip_lock_includedlocal=%LOCKDIR%pylock.%my_flavor%-%WINPYARCH%bit-%WINPYVERLOCK%_includedwheelslocal.toml +set pip_lock_includedweb=%LOCKDIR%pylock.%my_flavor%-%WINPYARCH%bit-%WINPYVERLOCK%_includedwheels.toml +set req_lock_includedlocal=%LOCKDIR%requirement.%my_flavor%-%WINPYARCH%bit-%WINPYVERLOCK%_includedwheelslocal.txt +set req_lock_includedweb=%LOCKDIR%requirement.%my_flavor%-%WINPYARCH%bit-%WINPYVERLOCK%_includedwheels.txt + +if not "Z%wheelhousereq%Z"=="ZZ" if exist "%wheelhousereq%" ( +echo JOYYYwheelhousereq=%wheelhousereq% +echo z%pip_lock_includedlocal%z=%pip_lock_includedlocal% +rem no winpython in it naturally, with deps +python.exe -m pip lock --no-index --trusted-host=None --find-links=%my_find_links% -c C:\WinP\constraints.txt -r "%wheelhousereq%" -o %pip_lock_includedlocal% +rem generating also classic requirement with hash-256, from obtained pylock.toml +python.exe -c "from winpython import wheelhouse as wh;wh.pylock_to_req(r'%pip_lock_includedlocal%', r'%req_lock_includedlocal%')" + +rem same with frozen web from local +python.exe -m pip lock --no-deps --require-hashes -c C:\WinP\constraints.txt -r "%req_lock_includedlocal%" -o %pip_lock_includedweb% + +echo %my_WINPYDIRBASE%\python\scripts\wppm.exe "%pip_lock_includedweb%" -ws "%my_find_links%" -wd "%my_WINPYDIRBASE%\wheelhouse\included.wheels">>%my_archive_log% +%my_WINPYDIRBASE%\python\scripts\wppm.exe "%pip_lock_includedweb%" -ws "%my_find_links%" -wd "%my_WINPYDIRBASE%\wheelhouse\included.wheels" +) + +echo -------------------------------------- >>%my_archive_log%; +echo "(%date% %time%) generate pylock.toml files and requirement.txt with hash files">>%my_archive_log% echo -------------------------------------- >>%my_archive_log% set path=%my_original_path% @@ -125,17 +151,17 @@ rem generate pip freeze requirements echo %date% %time% set LOCKDIR=%WINPYDIRBASE%\..\ -set WINPYVERLOCK=%WINPYVER:.=_% -set req=%LOCKDIR%requirement.%WINPYVERLOCK%_raw.txt -set wanted_req=%LOCKDIR%requirement.%WINPYVERLOCK%.txt -set pip_lock_web=%LOCKDIR%pylock.%WINPYVERLOCK%.toml -set pip_lock_local=%LOCKDIR%pylock.%WINPYVER%_local.toml -set req_lock_web=%LOCKDIR%requirement_with_hash.%WINPYVERLOCK%.txt -set req_lock_local=%LOCKDIR%requirement_with_hash.%WINPYVERLOCK%_local.txt +set WINPYVERLOCK=%WINPYVER2:.=_% +set req=%LOCKDIR%requirement.%my_flavor%-%WINPYARCH%bit-%WINPYVERLOCK%_raw.txt +set wanted_req=%LOCKDIR%requirement.%my_flavor%-%WINPYARCH%bit-%WINPYVERLOCK%.txt +set pip_lock_web=%LOCKDIR%pylock.%my_flavor%-%WINPYARCH%bit-%WINPYVERLOCK%.toml +set pip_lock_local=%LOCKDIR%pylock.%my_flavor%-%WINPYARCH%bit-%WINPYVERLOCK%_local.toml +set req_lock_web=%LOCKDIR%requirement.%my_flavor%-%WINPYARCH%bit-%WINPYVERLOCK%.txt +set req_lock_local=%LOCKDIR%requirement.%my_flavor%-%WINPYARCH%bit-%WINPYVERLOCK%_local.txt -set my_archive_lockfile=%my_archive_dir%\pylock.%WINPYVERLOCK%_%date:/=-%at_%my_time%.toml -set my_archive_lockfile_local=%my_archive_dir%\pylock.%WINPYVERLOCK%_%date:/=-%at_%my_time%.local.toml -set my_changelog_lockfile=%~dp0changelogs\pylock.%WINPYVERLOCK%.toml +set my_archive_lockfile=%my_archive_dir%\pylock.%my_flavor%-%WINPYARCH%bit-%WINPYVERLOCK%_%date:/=-%at_%my_time%.toml +set my_archive_lockfile_local=%my_archive_dir%\pylock.%my_flavor%-%WINPYARCH%bit-%WINPYVERLOCK%_%date:/=-%at_%my_time%.local.toml +set my_changelog_lockfile=%~dp0changelogs\pylock.%my_flavor%-%WINPYARCH%bit-%WINPYVERLOCK%.toml python.exe -m pip freeze>%req% findstr /v "winpython" %req% > %wanted_req% @@ -157,6 +183,25 @@ fc "%req_lock_web%" "%req_lock_local%" copy/Y %pip_lock_web% %my_archive_lockfile% copy/Y %pip_lock_web% %my_changelog_lockfile% + +REM Archive success +echo -------------------------------------- >>%my_archive_log% +echo "(%date% %time%) Archive success">>%my_archive_log% +echo -------------------------------------- >>%my_archive_log% +set path=%my_original_path% +call %my_WINPYDIRBASE%\scripts\env.bat + +%target_python_exe% -m pip freeze > %my_archive_log%.packages_versions.txt + +REM Generate changelog and binaries +echo "(%date% %time%) Generate changelog and binaries">>%my_archive_log% +set path=%my_original_path% +cd /D %~dp0 +call %my_buildenv%\scripts\env.bat + +python.exe -c "from make import *;make_all(%my_release%, '%my_release_level%', pyver='%my_pyver%', basedir=r'%my_basedir%', verbose=True, architecture=%my_arch%, flavor='%my_flavor%', install_options=r'%my_install_options%', find_links=r'%my_find_links%', source_dirs=r'%my_source_dirs%', create_installer='%my_create_installer%', rebuild=False, python_target_release='%my_python_target_release%')" >> %my_archive_log% + + echo -------------------------------------- >>%my_archive_log% echo "(%date% %time%) END OF CREATION">>%my_archive_log% echo -------------------------------------- >>%my_archive_log% diff --git a/generate_winpython_distros313_wheel.bat b/generate_winpython_distros313_wheel.bat new file mode 100644 index 00000000..9ae54ff3 --- /dev/null +++ b/generate_winpython_distros313_wheel.bat @@ -0,0 +1,40 @@ +rem this replace running manually from spyder the make.py +rem to launch from a winpython module 'make' directory + +set my_original_path=%path% + +set my_root_dir_for_builds=C:\Winp +set my_python_target=313 +set my_pyver=3.13 +set my_flavor=whl +set my_arch=64 + +rem settings delegated to generate_a_winpython_distro.bat +set my_release= +set my_release_level= + +rem list of installers to create separated per dot: False=none, .zip=zip, .7z=.7z, 7zip=auto-extractible 7z +set my_create_installer=7zip.7z.zip +set my_create_installer=.7z + +set my_preclear_build_directory=Yes + +set tmp_reqdir=%my_root_dir_for_builds%\bd%my_python_target% + +set my_requirements=C:\Winp\bd313\dot_requirements.txt +set my_source_dirs=C:\Winp\bd313\packages.win-amd64 + +set my_find_links=C:\Winp\packages.srcreq +set my_toolsdirs=C:\Winp\bdTools\Tools.dot +set my_docsdirs=C:\WinP\bdDocs\docs.dot + +set my_install_options=--no-index --pre --trusted-host=None + +rem set addlockfile=dot + +set wheelhousereq=C:\Winp\bd313\requirements64_whl.txt + + +call %~dp0\generate_a_winpython_distro.bat + +pause diff --git a/make.py b/make.py index c1a6a93a..a23d719d 100644 --- a/make.py +++ b/make.py @@ -208,6 +208,7 @@ def _create_initial_batch_scripts(self): # Replacements for batch scripts (PyPy compatibility) executable_name = self.distribution.short_exe if self.distribution else "python.exe" # default to python.exe if distribution is not yet set init_variables = [('WINPYthon_exe', executable_name), ('WINPYthon_subdirectory_name', self.python_directory_name), ('WINPYVER', self.winpython_version_name)] + init_variables += [('WINPYVER2', f"{self.python_full_version}.{self.build_number}"), ('WINPYFLAVOR', self.flavor), ('WINPYARCH', self.architecture_bits)] with open(self.winpython_directory / "scripts" / "env.ini", "w") as f: f.writelines([f'{a}={b}\n' for a, b in init_variables]) @@ -221,10 +222,7 @@ def build(self, rebuild: bool = True, requirements_files_list=None, winpy_dirnam if rebuild: self._print_action(f"Creating WinPython {self.winpython_directory} base directory") if self.winpython_directory.is_dir(): - try: - shutil.rmtree(self.winpython_directory, onexc=utils.onerror) - except TypeError: # before 3.12 - shutil.rmtree(self.winpython_directory, onerror=utils.onerror) + shutil.rmtree(self.winpython_directory) os.makedirs(self.winpython_directory, exist_ok=True) # preventive re-Creation of settings directory (self.winpython_directory / "settings" / "AppData" / "Roaming").mkdir(parents=True, exist_ok=True) @@ -267,7 +265,7 @@ def rebuild_winpython_package(source_directory: Path, target_directory: Path, ar for file in target_directory.glob("winpython-*"): if file.suffix in (".exe", ".whl", ".gz"): file.unlink() - utils.buildflit_wininst(source_directory, copy_to=target_directory, verbose=verbose) + utils.buildflit_wininst(source_directory, copy_to=target_directory, verbose=True) def make_all(build_number: int, release_level: str, pyver: str, architecture: int, basedir: Path, verbose: bool = False, rebuild: bool = True, create_installer: str = "True", install_options=["--no-index"], diff --git a/winpython/__init__.py b/winpython/__init__.py index c8ebca8a..5ab80ad9 100644 --- a/winpython/__init__.py +++ b/winpython/__init__.py @@ -28,6 +28,6 @@ OTHER DEALINGS IN THE SOFTWARE. """ -__version__ = '16.1.20250524' +__version__ = '16.1.20250525' __license__ = __doc__ __project_url__ = 'http://winpython.github.io/' diff --git a/winpython/wheelhouse.py b/winpython/wheelhouse.py index e93e7bc4..1653aded 100644 --- a/winpython/wheelhouse.py +++ b/winpython/wheelhouse.py @@ -93,18 +93,20 @@ def run_pip_command(command: List[str], check: bool = True, capture_output=True) except Exception as e: return False, f"Unexpected error: {e}" -def get_wheels(requirements: Path, wheeldir: Path, from_local: Optional[Path] = None +def get_wheels(requirements: Path, wheeldrain: Path, wheelorigin: Optional[Path] = None , only_check: bool = True,post_install: bool = False) -> bool: """Download or check Python wheels based on requirements.""" added = [] - if from_local: - added += ['--no-index', '--trusted-host=None', f'--find-links={from_local}'] + if wheelorigin: + added = ['--no-index', '--trusted-host=None', f'--find-links={wheelorigin}'] pre_checks = [sys.executable, "-m", "pip", "install", "--dry-run", "--no-deps", "--require-hashes", "-r", str(requirements)] + added - instruction = [sys.executable, "-m", "pip", "download", "--no-deps", "--require-hashes", "-r", str(requirements), "--dest", str(wheeldir)] + added + instruction = [sys.executable, "-m", "pip", "download", "--no-deps", "--require-hashes", "-r", str(requirements), "--dest", str(wheeldrain)] + added + if wheeldrain: + added = ['--no-index', '--trusted-host=None', f'--find-links={wheeldrain}'] post_install_cmd = [sys.executable, "-m", "pip", "install", "--no-deps", "--require-hashes", "-r", str(requirements)] + added - # Run pip dry-run, only if a move of directory - if from_local and from_local != wheeldir: + # Run pip dry-run, only if a move of wheels + if wheelorigin and wheelorigin != wheeldrain: success, output = run_pip_command(pre_checks, check=False) if not success: print("❌ Dry-run failed. Here's the output:\n") @@ -135,15 +137,15 @@ def get_wheels(requirements: Path, wheeldir: Path, from_local: Optional[Path] = return True -def get_pylock_wheels(wheelhouse: Path, lockfile: Path, from_local: Optional[Path] = None) -> None: - """Get wheels for a pylock file.""" +def get_pylock_wheels(wheelhouse: Path, lockfile: Path, wheelorigin: Optional[Path] = None, wheeldrain: Optional[Path] = None) -> None: + """Get wheels asked pylock file.""" filename = Path(lockfile).name wheelhouse.mkdir(parents=True, exist_ok=True) trusted_wheelhouse = wheelhouse / "included.wheels" trusted_wheelhouse.mkdir(parents=True, exist_ok=True) filename_lock = wheelhouse / filename - filename_req = wheelhouse / (Path(lockfile).stem.replace('pylock', 'requirement_with_hash') + '.txt') + filename_req = wheelhouse / (Path(lockfile).stem.replace('pylock', 'requirement') + '.txt') pylock_to_req(Path(lockfile), filename_req) @@ -151,27 +153,29 @@ def get_pylock_wheels(wheelhouse: Path, lockfile: Path, from_local: Optional[Pat shutil.copy2(lockfile, filename_lock) # We create a destination for wheels that is specific, so we can check all is there - destination_wheelhouse = wheelhouse / Path(lockfile).name.replace('.toml', '.wheels') + destination_wheelhouse = Path(wheeldrain) if wheeldrain else wheelhouse / Path(lockfile).name.replace('.toml', '.wheels') + destination_wheelhouse.mkdir(parents=True, exist_ok=True) + # there can be an override + + in_trusted = False - if from_local is None: + if wheelorigin is None: # Try from trusted WheelHouse print(f"\n\n*** Checking if we can install from our Local WheelHouse: ***\n {trusted_wheelhouse}\n\n") - in_trusted = get_wheels(filename_req, destination_wheelhouse, from_local=trusted_wheelhouse, only_check=True) + in_trusted = get_wheels(filename_req, destination_wheelhouse, wheelorigin=trusted_wheelhouse, only_check=True) if in_trusted: print(f"\n\n*** We can install from Local WheelHouse: ***\n {trusted_wheelhouse}\n\n") - user_input = input("Do you want to continue and install from {trusted_wheelhouse} ? (yes/no):") - if user_input.lower() == "yes": - in_installed = get_wheels(filename_req, trusted_wheelhouse, from_local=trusted_wheelhouse, only_check=True, post_install=True) + in_installed = get_wheels(filename_req, trusted_wheelhouse, wheelorigin=trusted_wheelhouse, only_check=False, post_install=True) if not in_trusted: - post_install = True if from_local and Path(from_local).is_dir and Path(from_local).samefile(destination_wheelhouse) else False + post_install = True if wheelorigin and Path(wheelorigin).is_dir and Path(wheelorigin).samefile(destination_wheelhouse) else False if post_install: print(f"\n\n*** Installing from Local WheelHouse: ***\n {destination_wheelhouse}\n\n") else: - print(f"\n\n*** Re-Checking if we can install from: {'pypi.org' if not from_local or from_local == '' else from_local}\n\n") + print(f"\n\n*** Re-Checking if we can install from: {'pypi.org' if not wheelorigin or wheelorigin == '' else wheelorigin}\n\n") - in_pylock = get_wheels(filename_req, destination_wheelhouse, from_local=from_local, only_check=False, post_install=post_install) + in_pylock = get_wheels(filename_req, destination_wheelhouse, wheelorigin=wheelorigin, only_check=False, post_install=post_install) if in_pylock: if not post_install: print(f"\n\n*** You can now install from this dedicated WheelHouse: ***\n {destination_wheelhouse}") diff --git a/winpython/wppm.py b/winpython/wppm.py index 258795e3..71296c72 100644 --- a/winpython/wppm.py +++ b/winpython/wppm.py @@ -15,7 +15,7 @@ from pathlib import Path from argparse import ArgumentParser, RawTextHelpFormatter from winpython import utils, piptree, associate - +from winpython import wheelhouse as wh # Workaround for installing PyVISA on Windows from source: os.environ["HOME"] = os.environ["USERPROFILE"] @@ -24,7 +24,7 @@ class Package: def __init__(self, fname: str, suggested_summary: str = None): self.fname = fname self.description = piptree.sum_up(suggested_summary) if suggested_summary else "" - self.name, self.version = None, None + self.name, self.version = fname, '?.?.?' if fname.lower().endswith((".zip", ".tar.gz", ".whl")): bname = Path(self.fname).name # e.g., "sqlite_bro-1.0.0..." infos = utils.get_source_package_infos(bname) # get name, version @@ -47,6 +47,7 @@ def __init__(self, target: str = None, verbose: bool = False): self.version, self.architecture = utils.get_python_infos(self.target) self.python_exe = utils.get_python_executable(self.target) self.short_exe = Path(self.python_exe).name + self.wheelhouse = Path(self.target).parent / "wheelhouse" def create_file(self, package, name, dstdir, contents): """Generate data file -- path is relative to distribution root dir""" @@ -91,7 +92,8 @@ def patch_all_shebang(self, to_movable: bool = True, max_exe_size: int = 999999, def install(self, package: Package, install_options: list[str] = None): """Install package in distribution.""" - if package.fname.endswith((".whl", ".tar.gz", ".zip")): # Check extension with tuple + if package.fname.endswith((".whl", ".tar.gz", ".zip")) or ( + ' ' not in package.fname and ';' not in package.fname and len(package.fname) >1): # Check extension with tuple self.install_bdist_direct(package, install_options=install_options) self.handle_specific_packages(package) # minimal post-install actions @@ -239,7 +241,8 @@ def main(test=False): # parser.add_argument( "--unregister_forall", action="store_true", help="un-Register distribution for all users") parser.add_argument("--fix", action="store_true", help="make WinPython fix") parser.add_argument("--movable", action="store_true", help="make WinPython movable") - parser.add_argument("-wh", "--wheelhouse", default=None, type=str, help="wheelhouse location to search for wheels: wppm pylock.toml -wh directory_of_wheels") + parser.add_argument("-ws", dest="wheelsource", default=None, type=str, help="location to search wheels: wppm pylock.toml -ws source_of_wheels") + parser.add_argument("-wd", dest="wheeldrain" , default=None, type=str, help="location of found wheels: wppm pylock.toml -wd destination_of_wheels") parser.add_argument("-ls", "--list", action="store_true", help="list installed packages matching the given [optional] package expression: wppm -ls, wppm -ls pand") parser.add_argument("-lsa", dest="all", action="store_true",help=f"list details of package names matching given regular expression: wppm -lsa pandas -l1") parser.add_argument("-p",dest="pipdown",action="store_true",help="show Package dependencies of the given package[option]: wppm -p pandas[test]") @@ -326,7 +329,7 @@ def main(test=False): sys.exit() if not args.install and not args.uninstall: args.install = True - if not Path(args.fname).is_file() and args.install: + if not Path(args.fname).is_file() and not args.install: if args.fname == "": parser.print_help() sys.exit() @@ -335,18 +338,18 @@ def main(test=False): else: try: filename = Path(args.fname).name + install_from_wheelhouse = ["--no-index", "--trusted-host=None", f"--find-links={dist.wheelhouse / 'included.wheels'}"] if filename.split('.')[0] == "pylock" and filename.split('.')[-1] == 'toml': print(' a lock file !', args.fname, dist.target) - from winpython import wheelhouse as wh - wh.get_pylock_wheels(Path(dist.target).parent/ "WheelHouse", Path(args.fname), args.wheelhouse) - sys.exit() + wh.get_pylock_wheels(dist.wheelhouse, Path(args.fname), args.wheelsource, args.wheeldrain) + sys.exit() if args.uninstall: package = dist.find_package(args.fname) dist.uninstall(package) elif args.install: package = Package(args.fname) if args.install: - dist.install(package) + dist.install(package, install_options=install_from_wheelhouse) except NotImplementedError: raise RuntimeError("Package is not (yet) supported by WPPM") else: