From d6795d315e57bab4306be2bd1e8cb34dc7686742 Mon Sep 17 00:00:00 2001 From: John Sirois Date: Thu, 29 Aug 2024 12:31:25 -0700 Subject: [PATCH] Add support for Windows ARM64. (#85) This required expanding the recognized platforms as well as a fair bit of work to support using PBS x86-64 releases for Windows ARM64 under Prism emulation. --- .github/workflows/ci.yml | 13 ++- .github/workflows/release.yml | 13 ++- CHANGES.md | 9 ++ README.md | 2 +- docs/_ext/sphinx_science/directives.py | 4 + lift.toml | 4 + noxfile.py | 104 ++++++++++++++++++- science/__init__.py | 2 +- science/commands/doc.py | 10 +- science/commands/lift.py | 1 + science/exe.py | 16 ++- science/platform.py | 12 ++- science/providers/pypy.py | 10 +- science/providers/python_build_standalone.py | 17 ++- scripts/check-manifest.py | 70 +++++++++++++ tests/data/interpreter-groups.toml | 2 +- tests/data/unrecognized-config-fields.toml | 2 +- tests/test_a_scie.py | 4 +- tests/test_config.py | 2 +- tests/test_exe.py | 15 ++- 20 files changed, 279 insertions(+), 33 deletions(-) create mode 100644 scripts/check-manifest.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d853542..ac24bf8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,20 +23,25 @@ jobs: matrix: # N.B.: macos-12 is the oldest non-deprecated Intel Mac runner and macos-14 is the oldest # non-deprecated ARM Mac runner. - os: [ubuntu-22.04, linux-arm64, macos-12, macos-14, windows-2022] + os: [ ubuntu-22.04, linux-arm64, macos-12, macos-14, windows-2022, windows-arm64 ] env: SCIENCE_AUTH_API_GITHUB_COM_BEARER: ${{ secrets.GITHUB_TOKEN }} steps: - name: Setup Python 3.12 - if: ${{ matrix.os != 'linux-arm64' }} + if: matrix.os != 'linux-arm64' && matrix.os != 'windows-arm64' uses: actions/setup-python@v5 with: python-version: 3.12 - name: Setup Python 3.12 - if: ${{ matrix.os == 'linux-arm64' }} + if: matrix.os == 'linux-arm64' run: | python3.12 -m venv .venv echo "$(pwd)/.venv/bin" >> "${GITHUB_PATH}" + - name: Setup Python 3.12 + if: matrix.os == 'windows-arm64' + run: | + py -3.12 -m venv .venv + echo "$(pwd)/.venv/Scripts" >> "${GITHUB_PATH}" - name: Setup Nox run: pip install nox - name: Checkout Lift @@ -44,7 +49,7 @@ jobs: - name: Check Formatting & Lints run: nox -e lint - name: Configure Windows pytest short tmp dir path - if: matrix.os == 'windows-2022' + if: matrix.os == 'windows-2022' || matrix.os == 'windows-arm64' run: | echo PYTEST_ADDOPTS="--basetemp C:\\tmp\\pytest" >> ${GITHUB_ENV} - name: Unit Tests diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d6a3222..64ad32f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -47,7 +47,7 @@ jobs: matrix: # N.B.: macos-12 is the oldest non-deprecated Intel Mac runner and macos-14 is the oldest # non-deprecated ARM Mac runner. - os: [ ubuntu-22.04, linux-arm64, macos-12, macos-14, windows-2022 ] + os: [ ubuntu-22.04, linux-arm64, macos-12, macos-14, windows-2022, windows-arm64 ] environment: Release env: SCIENCE_AUTH_API_GITHUB_COM_BEARER: ${{ secrets.GITHUB_TOKEN }} @@ -58,15 +58,20 @@ jobs: discussions: write steps: - name: Setup Python 3.12 - if: ${{ matrix.os != 'linux-arm64' }} + if: matrix.os != 'linux-arm64' && matrix.os != 'windows-arm64' uses: actions/setup-python@v5 with: python-version: 3.12 - name: Setup Python 3.12 - if: ${{ matrix.os == 'linux-arm64' }} + if: matrix.os == 'linux-arm64' run: | python3.12 -m venv .venv echo "$(pwd)/.venv/bin" >> "${GITHUB_PATH}" + - name: Setup Python 3.12 + if: matrix.os == 'windows-arm64' + run: | + py -3.12 -m venv .venv + echo "$(pwd)/.venv/Scripts" >> "${GITHUB_PATH}" - name: Setup Nox run: pip install nox - name: Checkout lift ${{ needs.determine-tag.outputs.release-tag }} @@ -85,7 +90,7 @@ jobs: with: changelog-file: ${{ github.workspace }}/CHANGES.md version: ${{ needs.determine-tag.outputs.release-version }} - setup-python: ${{ matrix.os != 'linux-arm64' }} + setup-python: ${{ matrix.os != 'linux-arm64' && matrix.os != 'windows-arm64' }} - name: Create ${{ needs.determine-tag.outputs.release-tag }} Release uses: softprops/action-gh-release@v2 with: diff --git a/CHANGES.md b/CHANGES.md index 88be7d7..2ecb07e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,14 @@ # Release Notes +# 0.7.0 + +This release adds support for Windows ARM64. + +> [!NOTE] +> The `science` binaries shipped for Windows ARM64 are powered by an x86-64 [PBS][PBS] CPython +> that runs under Windows Prism emulation for x86-64 binaries. As such, you will experience a +> significantly slower first run when Prism populates its instruction translation caches. + ## 0.6.1 Upgrade the science internal Python distribution to [PBS][PBS] CPython 3.12.5. diff --git a/README.md b/README.md index f98341a..38829c4 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ You'll need to download the correct binary for your system, mark it as executabl your $PATH somewhere. The binaries are released via [GitHub Releases](https://github.com/a-scie/lift/releases) -for Windows x86_64 and Linux and macOS for both aarch64 and x86_64. For each of these platforms +for Windows, Linux and macOS for both aarch64 and x86-64. For each of these platforms there are two varieties, "thin" and "fat". The "fat" varieties are named `science-fat-*`, include a hermetic CPython 3.12 distribution from the [Python Build Standalone]() project and are larger as a result. The "thin" varieties have the CPython 3.12 distribution gouged out and are smaller as a diff --git a/docs/_ext/sphinx_science/directives.py b/docs/_ext/sphinx_science/directives.py index b6ed144..aa23d9c 100644 --- a/docs/_ext/sphinx_science/directives.py +++ b/docs/_ext/sphinx_science/directives.py @@ -143,6 +143,10 @@ class Synthesized(Directive): optional_arguments = getattr(doc_gen_directive, "optional_arguments", 0) def run(self) -> list[nodes.Node]: + assert self.state.document.current_source is not None, ( + f"We always expect documents we handle to have a source. " + f"This document does not: {self.state.document}" + ) source = Path(self.state.document.current_source) if generated_toc_node := generated_for.get(source): # N.B.: We use the `env-get-outdated` event below to manually read doctrees diff --git a/lift.toml b/lift.toml index fcdc85d..ad467a4 100644 --- a/lift.toml +++ b/lift.toml @@ -7,9 +7,12 @@ description = "Ship your interpreted executables using science." [[lift.interpreters]] id = "cpython" provider = "PythonBuildStandalone" + +# N.B.: When updating these, update the corresponding PBS_* constants in noxfile.py. release = "20240814" version = "3.12.5" flavor = "install_only_stripped" + # By default science ships as a "thin" scie that fetches CPython 3.12 on first run. # We use `science lift --invert-lazy cpython ...` when producing "fat" scies. lazy = true @@ -42,3 +45,4 @@ remove_re = [ [lift.commands.env.replace] SHIV_ROOT = "{scie.bindings}/shiv_root" SCIENCE_DOC_LOCAL = "{docsite}" +__SCIENCE_CURRENT_PLATFORM__ = "{scie.platform}" diff --git a/noxfile.py b/noxfile.py index 98147cc..51e9b6c 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1,15 +1,23 @@ # Copyright 2023 Science project contributors. # Licensed under the Apache License, Version 2.0 (see LICENSE). +from __future__ import annotations + import glob import hashlib +import io import itertools import json import os import shutil import subprocess +import sys +import tarfile +import tempfile +import urllib.request from functools import wraps from pathlib import Path +from textwrap import dedent from typing import Any, Callable, Collection, Iterable, TypeVar, cast import nox @@ -19,7 +27,32 @@ nox.options.stop_on_first_error = True nox.options.sessions = ["fmt", "lint", "check", "test"] -REQUIRES_PYTHON_VERSION = "3.12" +# N.B.: Our nox version specifier above admits nox releases that support Python >=3.7, but we set +# the floor to the oldest Python supported by python.org. +MIN_NOX_PYTHON = (3, 8) +MIN_NOX_PYTHON_VER = ".".join(map(str, MIN_NOX_PYTHON)) + +if sys.version_info[:2] < MIN_NOX_PYTHON: + sys.exit( + dedent( + f"""\ + This project requires nox running on Python>={MIN_NOX_PYTHON_VER}. + + Your nox is currently running under Python: + version info: {sys.version} + path: {sys.executable} + """ + ) + ) + + +# N.B.: When updating these, update the corresponding values for the PythonBuildStandalone provider +# in lift.toml. +PBS_RELEASE = "20240814" +PBS_VERSION = "3.12.5" +PBS_FLAVOR = "install_only_stripped" + +REQUIRES_PYTHON_VERSION = ".".join(PBS_VERSION.split(".")[:2]) PEX_REQUIREMENT = "pex==2.7.0" PEX_PEX = f"pex-{hashlib.sha1(PEX_REQUIREMENT.encode('utf-8')).hexdigest()}.pex" @@ -29,6 +62,25 @@ LOCK_FILE = BUILD_ROOT / "lock.json" IS_WINDOWS = os.name == "nt" +IS_WINDOWS_ARM64 = IS_WINDOWS and subprocess.run( + args=["pwsh.exe", "-c", "$Env:PROCESSOR_ARCHITECTURE.ToLower()"], + stdout=subprocess.PIPE, + text=True, +).stdout.strip() in ("aarch64", "arm64") + + +def check_lift_manifest(session: Session): + session.run( + "python", + BUILD_ROOT / "scripts" / "check-manifest.py", + "--release", + PBS_RELEASE, + "--version", + PBS_VERSION, + "--flavor", + PBS_FLAVOR, + BUILD_ROOT / "lift.toml", + ) def run_pex(session: Session, script, *args, silent=False, **env) -> Any | None: @@ -165,11 +217,50 @@ def install_locked_requirements(session: Session, input_reqs: Iterable[Path]) -> ) +def ensure_windows_x86_64_python() -> str: + pbs_root = BUILD_ROOT / ".nox" / "PBS" / PBS_RELEASE / PBS_VERSION / PBS_FLAVOR + if not pbs_root.exists(): + url = ( + "https://github.com/indygreg/python-build-standalone/releases/download/" + f"{PBS_RELEASE}/" + f"cpython-{PBS_VERSION}+{PBS_RELEASE}-x86_64-pc-windows-msvc-{PBS_FLAVOR}.tar.gz" + ) + with urllib.request.urlopen(f"{url}.sha256") as resp: + expected_hash = resp.read().decode().strip() + pbs_root.parent.mkdir(parents=True, exist_ok=True) + with tempfile.TemporaryDirectory(dir=pbs_root.parent, prefix="PBS-prepare.") as chroot: + with urllib.request.urlopen(url) as resp, tempfile.TemporaryFile() as archive: + digest = hashlib.sha256() + for chunk in iter(lambda: resp.read(io.DEFAULT_BUFFER_SIZE), b""): + archive.write(chunk) + digest.update(chunk) + if expected_hash != (actual_hash := digest.hexdigest()): + raise ValueError( + dedent( + f"""\ + The PBS archive downloaded from {url} had unexpected contents: + Expected sha256 hash: {expected_hash} + Actual sha256 hash: {actual_hash} + """ + ) + ) + archive.flush() + archive.seek(0) + with tarfile.open(fileobj=archive) as tf: + tf.extractall(chroot) + os.replace(chroot, str(pbs_root)) + return str(pbs_root / "python" / "python.exe") + + T = TypeVar("T") def nox_session() -> Callable[[Callable[[Session], T]], Callable[[Session], T]]: - return nox.session(python=[REQUIRES_PYTHON_VERSION], reuse_venv=True) + # N.B.: We use an x86-64 Python for Windows ARM64 because this is what we ship with via PBS, + # and we need to be able to resolve x86-64 compatible requirements (which include native deps + # like psutil) for our shiv. + python = ensure_windows_x86_64_python() if IS_WINDOWS_ARM64 else REQUIRES_PYTHON_VERSION + return nox.session(python=[python], reuse_venv=True) @nox_session() @@ -197,8 +288,8 @@ def wrapper(session: Session) -> T: requirements.append(session_reqs) if include_project: requirements.append(BUILD_ROOT / "requirements.txt") - - install_locked_requirements(session, input_reqs=requirements) + if requirements: + install_locked_requirements(session, input_reqs=requirements) return func(session) return nox_session()(wrapper) @@ -237,6 +328,8 @@ def lint(session: Session) -> None: @python_session(include_project=True, extra_reqs=["doc", "test"]) def check(session: Session) -> None: + check_lift_manifest(session) + session.run("mypy", "--python-version", ".".join(map(str, MIN_NOX_PYTHON)), "noxfile.py") session.run( "mypy", "--python-version", REQUIRES_PYTHON_VERSION, *PATHS_TO_CHECK, *session.posargs ) @@ -261,6 +354,9 @@ def create_zipapp(session: Session) -> Path: str(BUILD_ROOT / "requirements.windows-amd64.lock.txt"), external=True, ) + session.run( + str(venv_dir / "Scripts" / "python.exe"), "-m", "pip", "uninstall", "--yes", "pip" + ) site_packages = str(venv_dir / "Lib" / "site-packages") else: run_pex( diff --git a/science/__init__.py b/science/__init__.py index 8e802a9..86efeba 100644 --- a/science/__init__.py +++ b/science/__init__.py @@ -3,6 +3,6 @@ from packaging.version import Version -__version__ = "0.6.1" +__version__ = "0.7.0" VERSION = Version(__version__) diff --git a/science/commands/doc.py b/science/commands/doc.py index d1f5c1c..43d13db 100644 --- a/science/commands/doc.py +++ b/science/commands/doc.py @@ -15,6 +15,7 @@ from datetime import datetime from functools import cache from pathlib import Path, PurePath +from typing import Any import psutil @@ -179,7 +180,7 @@ def launch( env = {**os.environ, "PYTHONUNBUFFERED": "1"} with log.open("w") as fp: # Not proper daemonization, but good enough. - daemon_kwargs = ( + daemon_kwargs: dict[str, Any] = ( { # The subprocess.{DETACHED_PROCESS,CREATE_NEW_PROCESS_GROUP} attributes are only # defined on Windows. @@ -188,8 +189,11 @@ def launch( | subprocess.CREATE_NEW_PROCESS_GROUP # type: ignore[attr-defined] ) } - if Platform.current() is Platform.Windows_x86_64 - else {"preexec_fn": os.setsid} + if Platform.current() in (Platform.Windows_aarch64, Platform.Windows_x86_64) + else { + # The os.setsid function is not available on Windows. + "preexec_fn": os.setsid # type: ignore[attr-defined] + } ) process = subprocess.Popen( args=[sys.executable, "-m", "http.server", str(port)], diff --git a/science/commands/lift.py b/science/commands/lift.py index 269baca..2c45fc1 100644 --- a/science/commands/lift.py +++ b/science/commands/lift.py @@ -89,6 +89,7 @@ class LiftConfig: include_provenance: bool = False app_info: tuple[AppInfo, ...] = () app_name: str | None = None + platforms: tuple[Platform, ...] = () def export_manifest( diff --git a/science/exe.py b/science/exe.py index 08c2653..7a9e765 100644 --- a/science/exe.py +++ b/science/exe.py @@ -495,6 +495,14 @@ def _list(emit_json: bool) -> None: """ ), ) +@click.option( + "--platform", + "platforms", + type=Platform.parse, + multiple=True, + default=[], + help="Override any configured platforms and target these platforms instead.", +) @click.pass_context def _lift( ctx: click.Context, @@ -503,6 +511,7 @@ def _lift( include_provenance: bool, app_name: str | None, app_info: list[AppInfo], + platforms: list[Platform], ) -> None: # N.B.: Help is defined above in the _lift group decorator since it's a dynamic string. ctx.obj = LiftConfig( @@ -511,6 +520,7 @@ def _lift( include_provenance=include_provenance or bool(app_info), app_info=tuple(app_info), app_name=app_name, + platforms=tuple(platforms), ) @@ -581,7 +591,9 @@ def export( application = parse_application(lift_config, config) platform_info = PlatformInfo.create(application, use_suffix=use_platform_suffix) with temporary_directory(cleanup=True) as td: - for _, manifest_path in lift.export_manifest(lift_config, application, dest_dir=td): + for _, manifest_path in lift.export_manifest( + lift_config, application, dest_dir=td, platforms=lift_config.platforms + ): lift_manifest = dest_dir / ( manifest_path.relative_to(td) if platform_info.use_suffix else manifest_path.name ) @@ -677,7 +689,7 @@ def _build( application = parse_application(lift_config, config) platform_info = PlatformInfo.create(application, use_suffix=use_platform_suffix) - platforms = application.platforms + platforms = lift_config.platforms or application.platforms if use_jump and use_platform_suffix: logger.warning(f"Cannot use a custom scie jump build with a multi-platform configuration.") logger.warning( diff --git a/science/platform.py b/science/platform.py index 6d65d45..956760b 100644 --- a/science/platform.py +++ b/science/platform.py @@ -3,6 +3,7 @@ from __future__ import annotations +import os import platform from enum import Enum @@ -14,6 +15,7 @@ class Platform(Enum): Linux_x86_64 = "linux-x86_64" Macos_aarch64 = "macos-aarch64" Macos_x86_64 = "macos-x86_64" + Windows_aarch64 = "windows-aarch64" Windows_x86_64 = "windows-x86_64" @classmethod @@ -22,6 +24,12 @@ def parse(cls, value: str) -> Platform: @classmethod def current(cls) -> Platform: + # N.B.: Used by the science scie to seal in the correct current platform as determined by + # the scie-jump. This helps work around our Windows ARM64 science binary thinking its + # running on Windows x86-64 since we use an x86-64 PBS Cpython in that scie. + if current := os.environ.get("__SCIENCE_CURRENT_PLATFORM__"): + return cls.parse(current) + match (system := platform.system().lower(), machine := platform.machine().lower()): case ("linux", "aarch64" | "arm64"): return cls.Linux_aarch64 @@ -31,6 +39,8 @@ def current(cls) -> Platform: return cls.Macos_aarch64 case ("darwin", "amd64" | "x86_64"): return cls.Macos_x86_64 + case ("windows", "aarch64" | "arm64"): + return cls.Windows_aarch64 case ("windows", "amd64" | "x86_64"): return cls.Windows_x86_64 case _: @@ -41,7 +51,7 @@ def current(cls) -> Platform: @property def extension(self): - return ".exe" if self is self.Windows_x86_64 else "" + return ".exe" if self in (self.Windows_aarch64, self.Windows_x86_64) else "" def binary_name(self, binary_name: str) -> str: return f"{binary_name}{self.extension}" diff --git a/science/providers/pypy.py b/science/providers/pypy.py index 8d48a0e..e28b2f5 100644 --- a/science/providers/pypy.py +++ b/science/providers/pypy.py @@ -83,6 +83,12 @@ class PyPy(Provider[Config]): All science platforms are supported for PyPy release v7.3.0 and up. + ```{note} + Windows ARM64 uses the x86-64 binaries since there are currently no Windows ARM64 releases + from [PyPy][PyPy]. This means slow execution when the Windows Prism emulation system has to + warm up its instruction caches. + ``` + For all platforms, both a `pypy` placeholder (`#{:pypy}`) and a `python` placeholder (`#{:python}`) are supported and will be substituted with the selected distribution's PyPy interpreter binary path. @@ -113,7 +119,7 @@ def rank_compatibility(platform: Platform, arch: str) -> int | None: return 0 case "osx64": return 1 - case Platform.Windows_x86_64: + case Platform.Windows_aarch64 | Platform.Windows_x86_64: match arch: case "win64": return 0 @@ -226,7 +232,7 @@ def distribution(self, platform: Platform) -> Distribution | None: top_level_archive_dir = re.sub(r"-portable$", "", selected_asset.file_stem()) match platform: - case Platform.Windows_x86_64: + case Platform.Windows_aarch64 | Platform.Windows_x86_64: pypy_binary = f"{top_level_archive_dir}\\{pypy}.exe" placeholders[Identifier("pypy")] = pypy_binary placeholders[Identifier("python")] = pypy_binary diff --git a/science/providers/python_build_standalone.py b/science/providers/python_build_standalone.py index 96bd17e..85a9fb6 100644 --- a/science/providers/python_build_standalone.py +++ b/science/providers/python_build_standalone.py @@ -104,10 +104,16 @@ class PythonBuildStandalone(Provider[Config]): All science platforms are supported for Python 3 minor versions >= 8. + ```{note} + Windows ARM64 uses the x86-64 binaries since there are currently no Windows ARM64 releases + from [Python Standalone Builds][PBS]. This means slow execution when the Windows Prism + emulation system has to warm up its instruction caches. + ``` + For all platforms, a `python` placeholder (`#{:python}`) is supported and will be substituted with the selected distribution's Python binary path. - On the Linux and MacOS platforms a `pip` placeholder (`#{:pip}`) is supported and will be + On the Linux and macOS platforms a `pip` placeholder (`#{:pip}`) is supported and will be substituted with the selected distribution's pip script path. ```{danger} @@ -169,9 +175,12 @@ def rank_compatibility(platform: Platform, target_triple: str) -> int | None: match target_triple: case "x86_64-apple-darwin": return 0 - case Platform.Windows_x86_64: + case Platform.Windows_aarch64 | Platform.Windows_x86_64: match target_triple: - case "x86_64-pc-windows-msvc-shared": + # N.B.: The -shared tag was removed in + # https://github.com/indygreg/python-build-standalone/releases/tag/20240415 + # but the archive is still dynamically linked. + case "x86_64-pc-windows-msvc" | "x86_64-pc-windows-msvc-shared": return 0 case "x86_64-pc-windows-msvc-static": return 1 @@ -298,7 +307,7 @@ def distribution(self, platform: Platform) -> Distribution | None: match self.flavor: case "install_only" | "install_only_stripped": match platform: - case Platform.Windows_x86_64: + case Platform.Windows_aarch64 | Platform.Windows_x86_64: placeholders[Identifier("python")] = "python\\python.exe" case _: version = f"{selected_asset.version.major}.{selected_asset.version.minor}" diff --git a/scripts/check-manifest.py b/scripts/check-manifest.py new file mode 100644 index 0000000..09d2a4d --- /dev/null +++ b/scripts/check-manifest.py @@ -0,0 +1,70 @@ +# Copyright 2024 Science project contributors. +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +# N.B.: This script is used by noxfile.py to check that its internal PBS_* constants match +# corresponding lift.toml values. It will be run by the PBS interpreter used to power the science +# binary and so it can safely use the syntax and stdlib of that interpreter, which is currently +# CPython 3.12. + +import os +import sys +import tomllib +from argparse import ArgumentParser +from pathlib import Path +from textwrap import dedent +from typing import Any + + +def check_manifest( + manifest_path: Path, + *, + expected_release: str, + expected_version: str, + expected_flavor: str +) -> str | None: + with manifest_path.open("rb") as fp: + manifest = tomllib.load(fp) + + interpreters = manifest["lift"]["interpreters"] + if 1 != len(interpreters): + return f"Expected lift.toml to define one interpreter but found {len(interpreters)}" + interpreter = interpreters[0] + + errors = [] + if "PythonBuildStandalone" != (provider := interpreter.get("provider", "")): + errors.append(f"Expected interpreter provider of PythonBuildStandalone but was {provider}.") + if expected_release != (release := interpreter.get("release", "")): + errors.append(f"Expected interpreter release of {expected_release} but was {release}.") + if expected_version != (version := interpreter.get("version", "")): + errors.append(f"Expected interpreter version of {expected_version} but was {version}.") + if expected_flavor != (flavor := interpreter.get("flavor", "")): + errors.append(f"Expected interpreter flavor of {expected_flavor} but was {flavor}.") + if errors: + return dedent( + """ + Found the following lift.toml errors: + {errors} + + Fix by aligning lift.toml noxfile.py values + """ + ).format(errors=os.linesep.join(errors)).rstrip() + + return None + +def main() -> Any: + parser = ArgumentParser() + parser.add_argument("--release", required=True) + parser.add_argument("--version", required=True) + parser.add_argument("--flavor", required=True) + parser.add_argument("manifest", nargs=1, type=Path) + options = parser.parse_args() + return check_manifest( + options.manifest[0], + expected_release=options.release, + expected_version=options.version, + expected_flavor=options.flavor + ) + + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/tests/data/interpreter-groups.toml b/tests/data/interpreter-groups.toml index b7bc6ae..17e341a 100644 --- a/tests/data/interpreter-groups.toml +++ b/tests/data/interpreter-groups.toml @@ -3,7 +3,7 @@ name = "igs" description = "Test interpreter group selection." [lift.scie_jump] -version = "0.11.0" +version = "1.2.0" [[lift.interpreters]] id = "cpython310" diff --git a/tests/data/unrecognized-config-fields.toml b/tests/data/unrecognized-config-fields.toml index 6451c48..209d148 100644 --- a/tests/data/unrecognized-config-fields.toml +++ b/tests/data/unrecognized-config-fields.toml @@ -8,7 +8,7 @@ version = "0.11.0" [lift.scie_jump] # N.B: `version2` is invalid, should be `version`. -version2 = "0.11.0" +version2 = "1.2.0" [[lift.interpreters]] id = "cpython310" diff --git a/tests/test_a_scie.py b/tests/test_a_scie.py index b656e91..042e67e 100644 --- a/tests/test_a_scie.py +++ b/tests/test_a_scie.py @@ -18,9 +18,9 @@ def test_ptex_latest(tmp_path_factory: TempPathFactory) -> None: def test_ptex_version(tmp_path_factory: TempPathFactory) -> None: dest_dir = tmp_path_factory.mktemp("staging") - latest = a_scie.ptex(dest_dir=dest_dir, specification=Ptex(version=version.parse("0.6.0"))) + latest = a_scie.ptex(dest_dir=dest_dir, specification=Ptex(version=version.parse("1.2.0"))) assert ( - "0.6.0" + "1.2.0" == subprocess.run( args=[str(dest_dir / latest.name), "-V"], stdout=subprocess.PIPE, text=True, check=True ).stdout.strip() diff --git a/tests/test_config.py b/tests/test_config.py index 6b559f2..632cdb2 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -81,7 +81,7 @@ def test_interpreter_groups(tmp_path: Path, science_pyz: Path) -> None: def test_scie_base(tmp_path: Path, science_pyz: Path) -> None: current_platform = Platform.current() match current_platform: - case Platform.Windows_x86_64: + case Platform.Windows_aarch64 | Platform.Windows_x86_64: config_name = "scie-base.windows.toml" expected_base = "~\\AppData\\Local\\Temp\\custom-base" case _: diff --git a/tests/test_exe.py b/tests/test_exe.py index df2e3c5..b21cfbe 100644 --- a/tests/test_exe.py +++ b/tests/test_exe.py @@ -11,6 +11,7 @@ import shutil import subprocess import sys +from collections import defaultdict from dataclasses import dataclass from pathlib import Path from shutil import which @@ -310,6 +311,9 @@ def assert_failure(self) -> None: assert not self.scie.exists() +_WORK_DIRS = defaultdict[Path, list[Path]](list[Path]) + + def create_url_source_scie( tmp_path: Path, science_exe: Path, @@ -321,8 +325,14 @@ def create_url_source_scie( extra_lift_args: Iterable[str] = (), **env: str, ) -> Result: - dest = tmp_path / "dest" - chroot = tmp_path / "chroot" + + work_dirs = _WORK_DIRS[tmp_path] + work_dir = tmp_path / str(len(work_dirs)) + work_dirs.append(work_dir) + + dest = work_dir / "dest" + chroot = work_dir / "chroot" + lift_toml_content = url_source_lift_toml_content( chroot, lazy=lazy, @@ -330,6 +340,7 @@ def create_url_source_scie( expected_fingerprint=expected_fingerprint, ) lift_toml_content = f"{lift_toml_content}\n{additional_toml}" + scie = dest / Platform.current().binary_name(expected_name) result = subprocess.run( args=[str(science_exe), "lift", *extra_lift_args, "build", "--dest-dir", str(dest), "-"],