From 7504b116a6421d824e12a5ebc33c0c5e4ebc1be2 Mon Sep 17 00:00:00 2001 From: loonghao Date: Sat, 10 Oct 2026 12:08:58 +0800 Subject: [PATCH 1/4] ci: validate and publish native Python Rez packages --- .github/workflows/ci.yml | 2 +- .github/workflows/native-release.yml | 28 ++++++++++++++++++++++++++++ README.md | 2 ++ 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/native-release.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5f9ce2b..8604bd1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@v7 with: repository: vx-org/vx-rez-packages - ref: 8d54d7e1fd9f7281a20a9d40f8ef55aded2abd74 + ref: 6f47843c52a77578fa7987188f73f7d751ce1b46 path: tooling persist-credentials: false - uses: vx-org/vx@b2c1ce558af4b516261f34a27eb0576a9bec45f1 diff --git a/.github/workflows/native-release.yml b/.github/workflows/native-release.yml new file mode 100644 index 0000000..48238db --- /dev/null +++ b/.github/workflows/native-release.yml @@ -0,0 +1,28 @@ +name: Native Rez packages + +on: + pull_request: + workflow_dispatch: + push: + tags: + - 'python-*' + - '!python-*-pbs-*' + +permissions: + contents: read + +jobs: + native: + if: github.event_name != 'push' + uses: vx-org/vx-rez-packages/.github/workflows/runtime-release.yml@6f47843c52a77578fa7987188f73f7d751ce1b46 + with: + tooling-ref: 6f47843c52a77578fa7987188f73f7d751ce1b46 + + publish: + if: github.event_name == 'push' + permissions: + contents: write + uses: vx-org/vx-rez-packages/.github/workflows/runtime-release.yml@6f47843c52a77578fa7987188f73f7d751ce1b46 + with: + tooling-ref: 6f47843c52a77578fa7987188f73f7d751ce1b46 + publish: true diff --git a/README.md b/README.md index 626ea9f..93a7c2b 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,8 @@ assets with an empty cache and successfully resolves and runs this package. ## Native validation +The native workflow pins shared tooling commit `6f47843c52a77578fa7987188f73f7d751ce1b46` and builds every supported recipe target on its matching runner. Pull requests and manual runs verify bundles through public SDK 0.3.10 and adapter 0.1.1. Pushing the recipe's `python-3.7.9` tag also publishes the verified bundles and index, verifies every public asset, and repeats native acquisition from an empty cache and then offline. Historical PBS intake tags do not trigger this workflow. + The native smoke checks the real interpreter, binary extension modules, bundled standard library, headers and link libraries, then creates and executes a fresh virtual environment. A separate VX consumer acceptance run must still verify From 380b8a3ab7268b4b8f86771fffe7198fd9b3728a Mon Sep 17 00:00:00 2001 From: loonghao Date: Sat, 10 Oct 2026 14:25:21 +0800 Subject: [PATCH 2/4] fix: match reusable release workflow permissions --- .github/workflows/native-release.yml | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/.github/workflows/native-release.yml b/.github/workflows/native-release.yml index 48238db..c08112c 100644 --- a/.github/workflows/native-release.yml +++ b/.github/workflows/native-release.yml @@ -9,20 +9,11 @@ on: - '!python-*-pbs-*' permissions: - contents: read + contents: write jobs: native: - if: github.event_name != 'push' uses: vx-org/vx-rez-packages/.github/workflows/runtime-release.yml@6f47843c52a77578fa7987188f73f7d751ce1b46 with: tooling-ref: 6f47843c52a77578fa7987188f73f7d751ce1b46 - - publish: - if: github.event_name == 'push' - permissions: - contents: write - uses: vx-org/vx-rez-packages/.github/workflows/runtime-release.yml@6f47843c52a77578fa7987188f73f7d751ce1b46 - with: - tooling-ref: 6f47843c52a77578fa7987188f73f7d751ce1b46 - publish: true + publish: ${{ github.event_name == 'push' }} From ee4bee7abaaa2552c3755e47a95f2f9e57eebaba Mon Sep 17 00:00:00 2001 From: loonghao Date: Sat, 10 Oct 2026 21:20:15 +0800 Subject: [PATCH 3/4] ci: bump vx-rez-packages tooling pin to 8ca39f0 Move the reusable runtime-release workflow and the intake checkout off 6f47843c, which predates PRs #10 and #12. The newer ref surfaces the underlying command exit code with stdout/stderr on build failures and injects a native SystemRoot during launch, so the three platform native jobs stop failing with no diagnosable reason. Validation: intake contract verification, the repository's 27 tests and the tooling ref's own 100 tests all pass locally at this pin. --- .github/workflows/ci.yml | 2 +- .github/workflows/native-release.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8604bd1..251d1fc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@v7 with: repository: vx-org/vx-rez-packages - ref: 6f47843c52a77578fa7987188f73f7d751ce1b46 + ref: 8ca39f087bee99307e9bd5f2b61b1915791df581 path: tooling persist-credentials: false - uses: vx-org/vx@b2c1ce558af4b516261f34a27eb0576a9bec45f1 diff --git a/.github/workflows/native-release.yml b/.github/workflows/native-release.yml index c08112c..620f748 100644 --- a/.github/workflows/native-release.yml +++ b/.github/workflows/native-release.yml @@ -13,7 +13,7 @@ permissions: jobs: native: - uses: vx-org/vx-rez-packages/.github/workflows/runtime-release.yml@6f47843c52a77578fa7987188f73f7d751ce1b46 + uses: vx-org/vx-rez-packages/.github/workflows/runtime-release.yml@8ca39f087bee99307e9bd5f2b61b1915791df581 with: - tooling-ref: 6f47843c52a77578fa7987188f73f7d751ce1b46 + tooling-ref: 8ca39f087bee99307e9bd5f2b61b1915791df581 publish: ${{ github.event_name == 'push' }} From 3bec627d58d8ae01fd2202c02907c40ae6995252 Mon Sep 17 00:00:00 2001 From: loonghao Date: Sat, 10 Oct 2026 22:08:58 +0800 Subject: [PATCH 4/4] fix: symlink the interpreter when the bundle smoke test creates a venv CPython 3.7's venv.EnvBuilder defaults to symlinks=False, so the smoke test copied the interpreter stub into the venv tree. The copy resolves the payload's $ORIGIN-relative libpython against /lib, where it is absent, so the venv launcher never started: exit status 127 on Linux and SIGABRT from dyld on macOS. Windows was unaffected because venv copies .exe/.dll there and the payload is not relocated through a symlink. Requesting symlinks=True keeps $ORIGIN pointing at the payload, and an explicit is_symlink assertion turns any future silent fallback to a copy into a clear failure instead of an opaque launcher crash. Reproduced on Linux against the pinned upstream payload, where the fixed script prints VX_PYTHON_NATIVE_SMOKE_OK and the previous form fails. Validation: intake contract verification and the repository's 27 tests pass. --- recipe.json | 8 ++++---- smoke.py | 7 ++++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/recipe.json b/recipe.json index c067661..98cd50d 100644 --- a/recipe.json +++ b/recipe.json @@ -26,7 +26,7 @@ "{root}/payload/install/python{exe}", "-I", "-c", - "\"\"\"Native acceptance script embedded unchanged in each target's smoke command.\"\"\"\n\nimport ctypes\nimport ensurepip\nimport hashlib\nimport json\nimport lzma\nimport os\nfrom pathlib import Path\nimport sqlite3\nimport ssl\nimport sys\nimport sysconfig\nimport tempfile\nimport venv\n\n\nassert sys.version_info[:3] == tuple(map(int, \"{version}\".split(\".\")))\nassert sys.implementation.name == \"cpython\"\nassert \"PYTHONHOME\" not in os.environ\nassert \"PYTHONPATH\" not in os.environ\n\napplication_root = Path(sys.executable).resolve().parent\nif sys.platform != \"win32\":\n application_root = application_root.parent\nassert Path(sys.prefix).resolve() == application_root\nassert Path(sys.base_prefix).resolve() == application_root\ninclude = Path(sysconfig.get_path(\"include\"))\nassert (include / \"Python.h\").is_file()\nif sys.platform == \"win32\":\n assert (application_root / \"libs\" / \"python37.lib\").is_file()\n assert (application_root / \"vcruntime140.dll\").is_file()\nelse:\n assert (application_root / \"lib\" / \"libpython3.7m.a\").is_file()\nassert Path(sysconfig.get_path(\"stdlib\"), \"os.py\").is_file()\nassert Path(sysconfig.get_path(\"stdlib\"), \"venv\", \"__init__.py\").is_file()\nassert ensurepip.version()\nassert ssl.OPENSSL_VERSION\nassert hashlib.sha256(b\"vx-python-native\").hexdigest()\nassert lzma.decompress(lzma.compress(b\"standalone-runtime\")) == b\"standalone-runtime\"\nassert ctypes.sizeof(ctypes.c_void_p) == 8\nwith sqlite3.connect(\":memory:\") as database:\n assert database.execute(\"select 3 + 7\").fetchone() == (10,)\n\nwith tempfile.TemporaryDirectory(prefix=\"vx-python-venv-\") as temporary:\n environment = Path(temporary) / \"environment\"\n venv.EnvBuilder(with_pip=False).create(str(environment))\n executable = environment / (\"Scripts/python.exe\" if sys.platform == \"win32\" else \"bin/python\")\n assert executable.is_file()\n import subprocess\n\n completed = subprocess.run(\n [str(executable), \"-I\", \"-c\", \"import json,sys;print(json.dumps([list(sys.version_info[:3]),sys.prefix,sys.base_prefix]))\"],\n check=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n universal_newlines=True,\n )\n version, prefix, base_prefix = json.loads(completed.stdout)\n assert version == [3, 7, 9]\n assert Path(prefix).resolve() == environment.resolve()\n assert Path(base_prefix).resolve() == application_root\n\nprint(\"VX_PYTHON_NATIVE_SMOKE_OK\")\n" + "\"\"\"Native acceptance script embedded unchanged in each target's smoke command.\"\"\"\n\nimport ctypes\nimport ensurepip\nimport hashlib\nimport json\nimport lzma\nimport os\nfrom pathlib import Path\nimport sqlite3\nimport ssl\nimport sys\nimport sysconfig\nimport tempfile\nimport venv\n\n\nassert sys.version_info[:3] == tuple(map(int, \"{version}\".split(\".\")))\nassert sys.implementation.name == \"cpython\"\nassert \"PYTHONHOME\" not in os.environ\nassert \"PYTHONPATH\" not in os.environ\n\napplication_root = Path(sys.executable).resolve().parent\nif sys.platform != \"win32\":\n application_root = application_root.parent\nassert Path(sys.prefix).resolve() == application_root\nassert Path(sys.base_prefix).resolve() == application_root\ninclude = Path(sysconfig.get_path(\"include\"))\nassert (include / \"Python.h\").is_file()\nif sys.platform == \"win32\":\n assert (application_root / \"libs\" / \"python37.lib\").is_file()\n assert (application_root / \"vcruntime140.dll\").is_file()\nelse:\n assert (application_root / \"lib\" / \"libpython3.7m.a\").is_file()\nassert Path(sysconfig.get_path(\"stdlib\"), \"os.py\").is_file()\nassert Path(sysconfig.get_path(\"stdlib\"), \"venv\", \"__init__.py\").is_file()\nassert ensurepip.version()\nassert ssl.OPENSSL_VERSION\nassert hashlib.sha256(b\"vx-python-native\").hexdigest()\nassert lzma.decompress(lzma.compress(b\"standalone-runtime\")) == b\"standalone-runtime\"\nassert ctypes.sizeof(ctypes.c_void_p) == 8\nwith sqlite3.connect(\":memory:\") as database:\n assert database.execute(\"select 3 + 7\").fetchone() == (10,)\n\nwith tempfile.TemporaryDirectory(prefix=\"vx-python-venv-\") as temporary:\n environment = Path(temporary) / \"environment\"\n # Symlink the interpreter rather than copying it. A copy resolves the payload's\n # $ORIGIN-relative libpython against the venv tree, where it is absent, so the\n # venv launcher fails to start even though the payload interpreter is intact.\n venv.EnvBuilder(with_pip=False, symlinks=True).create(str(environment))\n executable = environment / (\"Scripts/python.exe\" if sys.platform == \"win32\" else \"bin/python\")\n assert executable.is_file()\n if sys.platform != \"win32\":\n assert executable.is_symlink(), \"venv copied the interpreter instead of symlinking it\"\n import subprocess\n\n completed = subprocess.run(\n [str(executable), \"-I\", \"-c\", \"import json,sys;print(json.dumps([list(sys.version_info[:3]),sys.prefix,sys.base_prefix]))\"],\n check=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n universal_newlines=True,\n )\n version, prefix, base_prefix = json.loads(completed.stdout)\n assert version == [3, 7, 9]\n assert Path(prefix).resolve() == environment.resolve()\n assert Path(base_prefix).resolve() == application_root\n\nprint(\"VX_PYTHON_NATIVE_SMOKE_OK\")\n" ], "expect": "VX_PYTHON_NATIVE_SMOKE_OK", "timeout_seconds": 120 @@ -531,7 +531,7 @@ "{root}/payload/install/python{exe}", "-I", "-c", - "\"\"\"Native acceptance script embedded unchanged in each target's smoke command.\"\"\"\n\nimport ctypes\nimport ensurepip\nimport hashlib\nimport json\nimport lzma\nimport os\nfrom pathlib import Path\nimport sqlite3\nimport ssl\nimport sys\nimport sysconfig\nimport tempfile\nimport venv\n\n\nassert sys.version_info[:3] == tuple(map(int, \"{version}\".split(\".\")))\nassert sys.implementation.name == \"cpython\"\nassert \"PYTHONHOME\" not in os.environ\nassert \"PYTHONPATH\" not in os.environ\n\napplication_root = Path(sys.executable).resolve().parent\nif sys.platform != \"win32\":\n application_root = application_root.parent\nassert Path(sys.prefix).resolve() == application_root\nassert Path(sys.base_prefix).resolve() == application_root\ninclude = Path(sysconfig.get_path(\"include\"))\nassert (include / \"Python.h\").is_file()\nif sys.platform == \"win32\":\n assert (application_root / \"libs\" / \"python37.lib\").is_file()\n assert (application_root / \"vcruntime140.dll\").is_file()\nelse:\n assert (application_root / \"lib\" / \"libpython3.7m.a\").is_file()\nassert Path(sysconfig.get_path(\"stdlib\"), \"os.py\").is_file()\nassert Path(sysconfig.get_path(\"stdlib\"), \"venv\", \"__init__.py\").is_file()\nassert ensurepip.version()\nassert ssl.OPENSSL_VERSION\nassert hashlib.sha256(b\"vx-python-native\").hexdigest()\nassert lzma.decompress(lzma.compress(b\"standalone-runtime\")) == b\"standalone-runtime\"\nassert ctypes.sizeof(ctypes.c_void_p) == 8\nwith sqlite3.connect(\":memory:\") as database:\n assert database.execute(\"select 3 + 7\").fetchone() == (10,)\n\nwith tempfile.TemporaryDirectory(prefix=\"vx-python-venv-\") as temporary:\n environment = Path(temporary) / \"environment\"\n venv.EnvBuilder(with_pip=False).create(str(environment))\n executable = environment / (\"Scripts/python.exe\" if sys.platform == \"win32\" else \"bin/python\")\n assert executable.is_file()\n import subprocess\n\n completed = subprocess.run(\n [str(executable), \"-I\", \"-c\", \"import json,sys;print(json.dumps([list(sys.version_info[:3]),sys.prefix,sys.base_prefix]))\"],\n check=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n universal_newlines=True,\n )\n version, prefix, base_prefix = json.loads(completed.stdout)\n assert version == [3, 7, 9]\n assert Path(prefix).resolve() == environment.resolve()\n assert Path(base_prefix).resolve() == application_root\n\nprint(\"VX_PYTHON_NATIVE_SMOKE_OK\")\n" + "\"\"\"Native acceptance script embedded unchanged in each target's smoke command.\"\"\"\n\nimport ctypes\nimport ensurepip\nimport hashlib\nimport json\nimport lzma\nimport os\nfrom pathlib import Path\nimport sqlite3\nimport ssl\nimport sys\nimport sysconfig\nimport tempfile\nimport venv\n\n\nassert sys.version_info[:3] == tuple(map(int, \"{version}\".split(\".\")))\nassert sys.implementation.name == \"cpython\"\nassert \"PYTHONHOME\" not in os.environ\nassert \"PYTHONPATH\" not in os.environ\n\napplication_root = Path(sys.executable).resolve().parent\nif sys.platform != \"win32\":\n application_root = application_root.parent\nassert Path(sys.prefix).resolve() == application_root\nassert Path(sys.base_prefix).resolve() == application_root\ninclude = Path(sysconfig.get_path(\"include\"))\nassert (include / \"Python.h\").is_file()\nif sys.platform == \"win32\":\n assert (application_root / \"libs\" / \"python37.lib\").is_file()\n assert (application_root / \"vcruntime140.dll\").is_file()\nelse:\n assert (application_root / \"lib\" / \"libpython3.7m.a\").is_file()\nassert Path(sysconfig.get_path(\"stdlib\"), \"os.py\").is_file()\nassert Path(sysconfig.get_path(\"stdlib\"), \"venv\", \"__init__.py\").is_file()\nassert ensurepip.version()\nassert ssl.OPENSSL_VERSION\nassert hashlib.sha256(b\"vx-python-native\").hexdigest()\nassert lzma.decompress(lzma.compress(b\"standalone-runtime\")) == b\"standalone-runtime\"\nassert ctypes.sizeof(ctypes.c_void_p) == 8\nwith sqlite3.connect(\":memory:\") as database:\n assert database.execute(\"select 3 + 7\").fetchone() == (10,)\n\nwith tempfile.TemporaryDirectory(prefix=\"vx-python-venv-\") as temporary:\n environment = Path(temporary) / \"environment\"\n # Symlink the interpreter rather than copying it. A copy resolves the payload's\n # $ORIGIN-relative libpython against the venv tree, where it is absent, so the\n # venv launcher fails to start even though the payload interpreter is intact.\n venv.EnvBuilder(with_pip=False, symlinks=True).create(str(environment))\n executable = environment / (\"Scripts/python.exe\" if sys.platform == \"win32\" else \"bin/python\")\n assert executable.is_file()\n if sys.platform != \"win32\":\n assert executable.is_symlink(), \"venv copied the interpreter instead of symlinking it\"\n import subprocess\n\n completed = subprocess.run(\n [str(executable), \"-I\", \"-c\", \"import json,sys;print(json.dumps([list(sys.version_info[:3]),sys.prefix,sys.base_prefix]))\"],\n check=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n universal_newlines=True,\n )\n version, prefix, base_prefix = json.loads(completed.stdout)\n assert version == [3, 7, 9]\n assert Path(prefix).resolve() == environment.resolve()\n assert Path(base_prefix).resolve() == application_root\n\nprint(\"VX_PYTHON_NATIVE_SMOKE_OK\")\n" ], "expect": "VX_PYTHON_NATIVE_SMOKE_OK", "timeout_seconds": 120 @@ -566,7 +566,7 @@ "{root}/payload/install/bin/python", "-I", "-c", - "\"\"\"Native acceptance script embedded unchanged in each target's smoke command.\"\"\"\n\nimport ctypes\nimport ensurepip\nimport hashlib\nimport json\nimport lzma\nimport os\nfrom pathlib import Path\nimport sqlite3\nimport ssl\nimport sys\nimport sysconfig\nimport tempfile\nimport venv\n\n\nassert sys.version_info[:3] == tuple(map(int, \"{version}\".split(\".\")))\nassert sys.implementation.name == \"cpython\"\nassert \"PYTHONHOME\" not in os.environ\nassert \"PYTHONPATH\" not in os.environ\n\napplication_root = Path(sys.executable).resolve().parent\nif sys.platform != \"win32\":\n application_root = application_root.parent\nassert Path(sys.prefix).resolve() == application_root\nassert Path(sys.base_prefix).resolve() == application_root\ninclude = Path(sysconfig.get_path(\"include\"))\nassert (include / \"Python.h\").is_file()\nif sys.platform == \"win32\":\n assert (application_root / \"libs\" / \"python37.lib\").is_file()\n assert (application_root / \"vcruntime140.dll\").is_file()\nelse:\n assert (application_root / \"lib\" / \"libpython3.7m.a\").is_file()\nassert Path(sysconfig.get_path(\"stdlib\"), \"os.py\").is_file()\nassert Path(sysconfig.get_path(\"stdlib\"), \"venv\", \"__init__.py\").is_file()\nassert ensurepip.version()\nassert ssl.OPENSSL_VERSION\nassert hashlib.sha256(b\"vx-python-native\").hexdigest()\nassert lzma.decompress(lzma.compress(b\"standalone-runtime\")) == b\"standalone-runtime\"\nassert ctypes.sizeof(ctypes.c_void_p) == 8\nwith sqlite3.connect(\":memory:\") as database:\n assert database.execute(\"select 3 + 7\").fetchone() == (10,)\n\nwith tempfile.TemporaryDirectory(prefix=\"vx-python-venv-\") as temporary:\n environment = Path(temporary) / \"environment\"\n venv.EnvBuilder(with_pip=False).create(str(environment))\n executable = environment / (\"Scripts/python.exe\" if sys.platform == \"win32\" else \"bin/python\")\n assert executable.is_file()\n import subprocess\n\n completed = subprocess.run(\n [str(executable), \"-I\", \"-c\", \"import json,sys;print(json.dumps([list(sys.version_info[:3]),sys.prefix,sys.base_prefix]))\"],\n check=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n universal_newlines=True,\n )\n version, prefix, base_prefix = json.loads(completed.stdout)\n assert version == [3, 7, 9]\n assert Path(prefix).resolve() == environment.resolve()\n assert Path(base_prefix).resolve() == application_root\n\nprint(\"VX_PYTHON_NATIVE_SMOKE_OK\")\n" + "\"\"\"Native acceptance script embedded unchanged in each target's smoke command.\"\"\"\n\nimport ctypes\nimport ensurepip\nimport hashlib\nimport json\nimport lzma\nimport os\nfrom pathlib import Path\nimport sqlite3\nimport ssl\nimport sys\nimport sysconfig\nimport tempfile\nimport venv\n\n\nassert sys.version_info[:3] == tuple(map(int, \"{version}\".split(\".\")))\nassert sys.implementation.name == \"cpython\"\nassert \"PYTHONHOME\" not in os.environ\nassert \"PYTHONPATH\" not in os.environ\n\napplication_root = Path(sys.executable).resolve().parent\nif sys.platform != \"win32\":\n application_root = application_root.parent\nassert Path(sys.prefix).resolve() == application_root\nassert Path(sys.base_prefix).resolve() == application_root\ninclude = Path(sysconfig.get_path(\"include\"))\nassert (include / \"Python.h\").is_file()\nif sys.platform == \"win32\":\n assert (application_root / \"libs\" / \"python37.lib\").is_file()\n assert (application_root / \"vcruntime140.dll\").is_file()\nelse:\n assert (application_root / \"lib\" / \"libpython3.7m.a\").is_file()\nassert Path(sysconfig.get_path(\"stdlib\"), \"os.py\").is_file()\nassert Path(sysconfig.get_path(\"stdlib\"), \"venv\", \"__init__.py\").is_file()\nassert ensurepip.version()\nassert ssl.OPENSSL_VERSION\nassert hashlib.sha256(b\"vx-python-native\").hexdigest()\nassert lzma.decompress(lzma.compress(b\"standalone-runtime\")) == b\"standalone-runtime\"\nassert ctypes.sizeof(ctypes.c_void_p) == 8\nwith sqlite3.connect(\":memory:\") as database:\n assert database.execute(\"select 3 + 7\").fetchone() == (10,)\n\nwith tempfile.TemporaryDirectory(prefix=\"vx-python-venv-\") as temporary:\n environment = Path(temporary) / \"environment\"\n # Symlink the interpreter rather than copying it. A copy resolves the payload's\n # $ORIGIN-relative libpython against the venv tree, where it is absent, so the\n # venv launcher fails to start even though the payload interpreter is intact.\n venv.EnvBuilder(with_pip=False, symlinks=True).create(str(environment))\n executable = environment / (\"Scripts/python.exe\" if sys.platform == \"win32\" else \"bin/python\")\n assert executable.is_file()\n if sys.platform != \"win32\":\n assert executable.is_symlink(), \"venv copied the interpreter instead of symlinking it\"\n import subprocess\n\n completed = subprocess.run(\n [str(executable), \"-I\", \"-c\", \"import json,sys;print(json.dumps([list(sys.version_info[:3]),sys.prefix,sys.base_prefix]))\"],\n check=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n universal_newlines=True,\n )\n version, prefix, base_prefix = json.loads(completed.stdout)\n assert version == [3, 7, 9]\n assert Path(prefix).resolve() == environment.resolve()\n assert Path(base_prefix).resolve() == application_root\n\nprint(\"VX_PYTHON_NATIVE_SMOKE_OK\")\n" ], "expect": "VX_PYTHON_NATIVE_SMOKE_OK", "timeout_seconds": 120 @@ -601,7 +601,7 @@ "{root}/payload/install/bin/python", "-I", "-c", - "\"\"\"Native acceptance script embedded unchanged in each target's smoke command.\"\"\"\n\nimport ctypes\nimport ensurepip\nimport hashlib\nimport json\nimport lzma\nimport os\nfrom pathlib import Path\nimport sqlite3\nimport ssl\nimport sys\nimport sysconfig\nimport tempfile\nimport venv\n\n\nassert sys.version_info[:3] == tuple(map(int, \"{version}\".split(\".\")))\nassert sys.implementation.name == \"cpython\"\nassert \"PYTHONHOME\" not in os.environ\nassert \"PYTHONPATH\" not in os.environ\n\napplication_root = Path(sys.executable).resolve().parent\nif sys.platform != \"win32\":\n application_root = application_root.parent\nassert Path(sys.prefix).resolve() == application_root\nassert Path(sys.base_prefix).resolve() == application_root\ninclude = Path(sysconfig.get_path(\"include\"))\nassert (include / \"Python.h\").is_file()\nif sys.platform == \"win32\":\n assert (application_root / \"libs\" / \"python37.lib\").is_file()\n assert (application_root / \"vcruntime140.dll\").is_file()\nelse:\n assert (application_root / \"lib\" / \"libpython3.7m.a\").is_file()\nassert Path(sysconfig.get_path(\"stdlib\"), \"os.py\").is_file()\nassert Path(sysconfig.get_path(\"stdlib\"), \"venv\", \"__init__.py\").is_file()\nassert ensurepip.version()\nassert ssl.OPENSSL_VERSION\nassert hashlib.sha256(b\"vx-python-native\").hexdigest()\nassert lzma.decompress(lzma.compress(b\"standalone-runtime\")) == b\"standalone-runtime\"\nassert ctypes.sizeof(ctypes.c_void_p) == 8\nwith sqlite3.connect(\":memory:\") as database:\n assert database.execute(\"select 3 + 7\").fetchone() == (10,)\n\nwith tempfile.TemporaryDirectory(prefix=\"vx-python-venv-\") as temporary:\n environment = Path(temporary) / \"environment\"\n venv.EnvBuilder(with_pip=False).create(str(environment))\n executable = environment / (\"Scripts/python.exe\" if sys.platform == \"win32\" else \"bin/python\")\n assert executable.is_file()\n import subprocess\n\n completed = subprocess.run(\n [str(executable), \"-I\", \"-c\", \"import json,sys;print(json.dumps([list(sys.version_info[:3]),sys.prefix,sys.base_prefix]))\"],\n check=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n universal_newlines=True,\n )\n version, prefix, base_prefix = json.loads(completed.stdout)\n assert version == [3, 7, 9]\n assert Path(prefix).resolve() == environment.resolve()\n assert Path(base_prefix).resolve() == application_root\n\nprint(\"VX_PYTHON_NATIVE_SMOKE_OK\")\n" + "\"\"\"Native acceptance script embedded unchanged in each target's smoke command.\"\"\"\n\nimport ctypes\nimport ensurepip\nimport hashlib\nimport json\nimport lzma\nimport os\nfrom pathlib import Path\nimport sqlite3\nimport ssl\nimport sys\nimport sysconfig\nimport tempfile\nimport venv\n\n\nassert sys.version_info[:3] == tuple(map(int, \"{version}\".split(\".\")))\nassert sys.implementation.name == \"cpython\"\nassert \"PYTHONHOME\" not in os.environ\nassert \"PYTHONPATH\" not in os.environ\n\napplication_root = Path(sys.executable).resolve().parent\nif sys.platform != \"win32\":\n application_root = application_root.parent\nassert Path(sys.prefix).resolve() == application_root\nassert Path(sys.base_prefix).resolve() == application_root\ninclude = Path(sysconfig.get_path(\"include\"))\nassert (include / \"Python.h\").is_file()\nif sys.platform == \"win32\":\n assert (application_root / \"libs\" / \"python37.lib\").is_file()\n assert (application_root / \"vcruntime140.dll\").is_file()\nelse:\n assert (application_root / \"lib\" / \"libpython3.7m.a\").is_file()\nassert Path(sysconfig.get_path(\"stdlib\"), \"os.py\").is_file()\nassert Path(sysconfig.get_path(\"stdlib\"), \"venv\", \"__init__.py\").is_file()\nassert ensurepip.version()\nassert ssl.OPENSSL_VERSION\nassert hashlib.sha256(b\"vx-python-native\").hexdigest()\nassert lzma.decompress(lzma.compress(b\"standalone-runtime\")) == b\"standalone-runtime\"\nassert ctypes.sizeof(ctypes.c_void_p) == 8\nwith sqlite3.connect(\":memory:\") as database:\n assert database.execute(\"select 3 + 7\").fetchone() == (10,)\n\nwith tempfile.TemporaryDirectory(prefix=\"vx-python-venv-\") as temporary:\n environment = Path(temporary) / \"environment\"\n # Symlink the interpreter rather than copying it. A copy resolves the payload's\n # $ORIGIN-relative libpython against the venv tree, where it is absent, so the\n # venv launcher fails to start even though the payload interpreter is intact.\n venv.EnvBuilder(with_pip=False, symlinks=True).create(str(environment))\n executable = environment / (\"Scripts/python.exe\" if sys.platform == \"win32\" else \"bin/python\")\n assert executable.is_file()\n if sys.platform != \"win32\":\n assert executable.is_symlink(), \"venv copied the interpreter instead of symlinking it\"\n import subprocess\n\n completed = subprocess.run(\n [str(executable), \"-I\", \"-c\", \"import json,sys;print(json.dumps([list(sys.version_info[:3]),sys.prefix,sys.base_prefix]))\"],\n check=True,\n stdout=subprocess.PIPE,\n stderr=subprocess.PIPE,\n universal_newlines=True,\n )\n version, prefix, base_prefix = json.loads(completed.stdout)\n assert version == [3, 7, 9]\n assert Path(prefix).resolve() == environment.resolve()\n assert Path(base_prefix).resolve() == application_root\n\nprint(\"VX_PYTHON_NATIVE_SMOKE_OK\")\n" ], "expect": "VX_PYTHON_NATIVE_SMOKE_OK", "timeout_seconds": 120 diff --git a/smoke.py b/smoke.py index 859e43f..2f3946b 100644 --- a/smoke.py +++ b/smoke.py @@ -44,9 +44,14 @@ with tempfile.TemporaryDirectory(prefix="vx-python-venv-") as temporary: environment = Path(temporary) / "environment" - venv.EnvBuilder(with_pip=False).create(str(environment)) + # Symlink the interpreter rather than copying it. A copy resolves the payload's + # $ORIGIN-relative libpython against the venv tree, where it is absent, so the + # venv launcher fails to start even though the payload interpreter is intact. + venv.EnvBuilder(with_pip=False, symlinks=True).create(str(environment)) executable = environment / ("Scripts/python.exe" if sys.platform == "win32" else "bin/python") assert executable.is_file() + if sys.platform != "win32": + assert executable.is_symlink(), "venv copied the interpreter instead of symlinking it" import subprocess completed = subprocess.run(