diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5f9ce2b..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: 8d54d7e1fd9f7281a20a9d40f8ef55aded2abd74 + 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 new file mode 100644 index 0000000..620f748 --- /dev/null +++ b/.github/workflows/native-release.yml @@ -0,0 +1,19 @@ +name: Native Rez packages + +on: + pull_request: + workflow_dispatch: + push: + tags: + - 'python-*' + - '!python-*-pbs-*' + +permissions: + contents: write + +jobs: + native: + uses: vx-org/vx-rez-packages/.github/workflows/runtime-release.yml@8ca39f087bee99307e9bd5f2b61b1915791df581 + with: + tooling-ref: 8ca39f087bee99307e9bd5f2b61b1915791df581 + publish: ${{ github.event_name == 'push' }} 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 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(