pyenv-native 0.1.10__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyenv_native-0.1.10/.gitignore +38 -0
- pyenv_native-0.1.10/LICENSE +21 -0
- pyenv_native-0.1.10/PKG-INFO +128 -0
- pyenv_native-0.1.10/README.md +99 -0
- pyenv_native-0.1.10/pyproject.toml +55 -0
- pyenv_native-0.1.10/src/pyenv_native_bootstrap/__init__.py +6 -0
- pyenv_native-0.1.10/src/pyenv_native_bootstrap/__main__.py +8 -0
- pyenv_native-0.1.10/src/pyenv_native_bootstrap/bundle.py +163 -0
- pyenv_native-0.1.10/src/pyenv_native_bootstrap/cli.py +215 -0
- pyenv_native-0.1.10/src/pyenv_native_bootstrap/github.py +105 -0
- pyenv_native-0.1.10/src/pyenv_native_bootstrap/installer.py +302 -0
- pyenv_native-0.1.10/src/pyenv_native_bootstrap/platforms.py +63 -0
- pyenv_native-0.1.10/tests/__init__.py +11 -0
- pyenv_native-0.1.10/tests/test_bundle.py +86 -0
- pyenv_native-0.1.10/tests/test_github.py +95 -0
- pyenv_native-0.1.10/tests/test_installer.py +214 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/target/
|
|
2
|
+
/dist/
|
|
3
|
+
/python-package/dist/
|
|
4
|
+
/packaging/winget/manifests/
|
|
5
|
+
/packaging/homebrew/Formula/
|
|
6
|
+
/python-package/src/**/*.pyc
|
|
7
|
+
/python-package/tests/**/*.pyc
|
|
8
|
+
**/__pycache__/
|
|
9
|
+
/.tmp*/
|
|
10
|
+
/test-install/
|
|
11
|
+
|
|
12
|
+
# Compiled binaries and debug symbols
|
|
13
|
+
*.exe
|
|
14
|
+
*.pdb
|
|
15
|
+
|
|
16
|
+
# AI Memory
|
|
17
|
+
MEMORY.md
|
|
18
|
+
.gemini/
|
|
19
|
+
|
|
20
|
+
# IDEs
|
|
21
|
+
.vscode/
|
|
22
|
+
.idea/
|
|
23
|
+
|
|
24
|
+
# Rust
|
|
25
|
+
Cargo.lock
|
|
26
|
+
|
|
27
|
+
# OS Junk
|
|
28
|
+
.DS_Store
|
|
29
|
+
Thumbs.db
|
|
30
|
+
|
|
31
|
+
# Local publish script (operator-only, not for distribution)
|
|
32
|
+
/scripts/publish.ps1
|
|
33
|
+
|
|
34
|
+
# Reproduction and scratch scripts
|
|
35
|
+
/repro_*.rs
|
|
36
|
+
/temp_*.rs
|
|
37
|
+
/clippy_errors.txt
|
|
38
|
+
/walkthrough.md
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Roy Dawson IV
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyenv-native
|
|
3
|
+
Version: 0.1.10
|
|
4
|
+
Summary: Python install package for the native-first pyenv-native runtime bundles.
|
|
5
|
+
Project-URL: Homepage, https://github.com/imyourboyroy
|
|
6
|
+
Project-URL: Repository, https://github.com/imyourboyroy/pyenv-native
|
|
7
|
+
Project-URL: Issues, https://github.com/imyourboyroy/pyenv-native/issues
|
|
8
|
+
Author-email: Roy Dawson IV <Roy.Dawson.IV@gmail.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: bootstrap,pipx,pyenv,python,windows
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Topic :: Software Development
|
|
26
|
+
Classifier: Topic :: System :: Installation/Setup
|
|
27
|
+
Requires-Python: >=3.8
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# pyenv-native
|
|
31
|
+
|
|
32
|
+

|
|
33
|
+

|
|
34
|
+

|
|
35
|
+
|
|
36
|
+
`pyenv-native` on PyPI is the Python convenience package for installing the native `pyenv-native` release bundles.
|
|
37
|
+
|
|
38
|
+
Those bundles install both:
|
|
39
|
+
- `pyenv`
|
|
40
|
+
- `pyenv-mcp`
|
|
41
|
+
|
|
42
|
+
It exists for users who:
|
|
43
|
+
- already have Python installed,
|
|
44
|
+
- want a `pip` or `pipx` entrypoint,
|
|
45
|
+
- still want the real runtime to remain native.
|
|
46
|
+
|
|
47
|
+
## Important principle
|
|
48
|
+
|
|
49
|
+
> This package installs `pyenv-native`.
|
|
50
|
+
> It does **not** replace `pyenv-native` with a Python implementation.
|
|
51
|
+
|
|
52
|
+
## Release selection behavior
|
|
53
|
+
|
|
54
|
+
By default, the package targets the **latest published GitHub release**.
|
|
55
|
+
|
|
56
|
+
## What it does
|
|
57
|
+
|
|
58
|
+
- downloads native release bundles,
|
|
59
|
+
- verifies checksums,
|
|
60
|
+
- reads bundle metadata,
|
|
61
|
+
- extracts the bundle,
|
|
62
|
+
- runs the bundled installer,
|
|
63
|
+
- installs the companion `pyenv-mcp` server when the bundle provides it,
|
|
64
|
+
- supports GitHub Release-based installs,
|
|
65
|
+
- works with Windows ZIP bundles and Linux/macOS `.tar.gz` bundles.
|
|
66
|
+
|
|
67
|
+
## Quick start
|
|
68
|
+
|
|
69
|
+
### `pipx` latest release
|
|
70
|
+
|
|
71
|
+
```powershell
|
|
72
|
+
pipx install pyenv-native
|
|
73
|
+
pyenv-native install --github-repo imyourboyroy/pyenv-native --install-root ~\.pyenv
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### `pip` latest release
|
|
77
|
+
|
|
78
|
+
```powershell
|
|
79
|
+
python -m pip install pyenv-native
|
|
80
|
+
pyenv-native install --github-repo imyourboyroy/pyenv-native --install-root ~\.pyenv
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### POSIX latest release
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
python -m pip install pyenv-native
|
|
87
|
+
pyenv-native install --github-repo imyourboyroy/pyenv-native --install-root ~/.pyenv
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Commands
|
|
91
|
+
|
|
92
|
+
```text
|
|
93
|
+
pyenv-native verify <bundle-archive> [--checksum-path <bundle.sha256>]
|
|
94
|
+
pyenv-native download [--bundle-url <url> | --release-base-url <url> | --github-repo <owner/repo>] [--tag <tag>]
|
|
95
|
+
pyenv-native install [--bundle-path <bundle-archive> | --release-base-url <url> | --github-repo <owner/repo>] [--tag <tag>] [--install-root <dir>]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Examples
|
|
99
|
+
|
|
100
|
+
Verify a local bundle:
|
|
101
|
+
|
|
102
|
+
```powershell
|
|
103
|
+
pyenv-native verify .\dist\pyenv-native-windows-x64.zip --checksum-path .\dist\pyenv-native-windows-x64.zip.sha256
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Install from a local bundle:
|
|
107
|
+
|
|
108
|
+
```powershell
|
|
109
|
+
pyenv-native install --bundle-path .\dist\pyenv-native-windows-x64.zip --checksum-path .\dist\pyenv-native-windows-x64.zip.sha256 --install-root ~\.pyenv
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Install from the latest GitHub release:
|
|
113
|
+
|
|
114
|
+
```powershell
|
|
115
|
+
pyenv-native install --github-repo imyourboyroy/pyenv-native --install-root ~\.pyenv
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Install a POSIX bundle:
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
pyenv-native install --bundle-path ./dist/pyenv-native-linux-x64.tar.gz --checksum-path ./dist/pyenv-native-linux-x64.tar.gz.sha256 --install-root ~/.pyenv --shell bash
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Relationship to the main project
|
|
125
|
+
|
|
126
|
+
For the full project overview, install scripts, release-bundle flow, and command reference, see the main repository README:
|
|
127
|
+
|
|
128
|
+
- <https://github.com/imyourboyroy/pyenv-native>
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# pyenv-native
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
`pyenv-native` on PyPI is the Python convenience package for installing the native `pyenv-native` release bundles.
|
|
8
|
+
|
|
9
|
+
Those bundles install both:
|
|
10
|
+
- `pyenv`
|
|
11
|
+
- `pyenv-mcp`
|
|
12
|
+
|
|
13
|
+
It exists for users who:
|
|
14
|
+
- already have Python installed,
|
|
15
|
+
- want a `pip` or `pipx` entrypoint,
|
|
16
|
+
- still want the real runtime to remain native.
|
|
17
|
+
|
|
18
|
+
## Important principle
|
|
19
|
+
|
|
20
|
+
> This package installs `pyenv-native`.
|
|
21
|
+
> It does **not** replace `pyenv-native` with a Python implementation.
|
|
22
|
+
|
|
23
|
+
## Release selection behavior
|
|
24
|
+
|
|
25
|
+
By default, the package targets the **latest published GitHub release**.
|
|
26
|
+
|
|
27
|
+
## What it does
|
|
28
|
+
|
|
29
|
+
- downloads native release bundles,
|
|
30
|
+
- verifies checksums,
|
|
31
|
+
- reads bundle metadata,
|
|
32
|
+
- extracts the bundle,
|
|
33
|
+
- runs the bundled installer,
|
|
34
|
+
- installs the companion `pyenv-mcp` server when the bundle provides it,
|
|
35
|
+
- supports GitHub Release-based installs,
|
|
36
|
+
- works with Windows ZIP bundles and Linux/macOS `.tar.gz` bundles.
|
|
37
|
+
|
|
38
|
+
## Quick start
|
|
39
|
+
|
|
40
|
+
### `pipx` latest release
|
|
41
|
+
|
|
42
|
+
```powershell
|
|
43
|
+
pipx install pyenv-native
|
|
44
|
+
pyenv-native install --github-repo imyourboyroy/pyenv-native --install-root ~\.pyenv
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### `pip` latest release
|
|
48
|
+
|
|
49
|
+
```powershell
|
|
50
|
+
python -m pip install pyenv-native
|
|
51
|
+
pyenv-native install --github-repo imyourboyroy/pyenv-native --install-root ~\.pyenv
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### POSIX latest release
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
python -m pip install pyenv-native
|
|
58
|
+
pyenv-native install --github-repo imyourboyroy/pyenv-native --install-root ~/.pyenv
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Commands
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
pyenv-native verify <bundle-archive> [--checksum-path <bundle.sha256>]
|
|
65
|
+
pyenv-native download [--bundle-url <url> | --release-base-url <url> | --github-repo <owner/repo>] [--tag <tag>]
|
|
66
|
+
pyenv-native install [--bundle-path <bundle-archive> | --release-base-url <url> | --github-repo <owner/repo>] [--tag <tag>] [--install-root <dir>]
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Examples
|
|
70
|
+
|
|
71
|
+
Verify a local bundle:
|
|
72
|
+
|
|
73
|
+
```powershell
|
|
74
|
+
pyenv-native verify .\dist\pyenv-native-windows-x64.zip --checksum-path .\dist\pyenv-native-windows-x64.zip.sha256
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Install from a local bundle:
|
|
78
|
+
|
|
79
|
+
```powershell
|
|
80
|
+
pyenv-native install --bundle-path .\dist\pyenv-native-windows-x64.zip --checksum-path .\dist\pyenv-native-windows-x64.zip.sha256 --install-root ~\.pyenv
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Install from the latest GitHub release:
|
|
84
|
+
|
|
85
|
+
```powershell
|
|
86
|
+
pyenv-native install --github-repo imyourboyroy/pyenv-native --install-root ~\.pyenv
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Install a POSIX bundle:
|
|
90
|
+
|
|
91
|
+
```sh
|
|
92
|
+
pyenv-native install --bundle-path ./dist/pyenv-native-linux-x64.tar.gz --checksum-path ./dist/pyenv-native-linux-x64.tar.gz.sha256 --install-root ~/.pyenv --shell bash
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Relationship to the main project
|
|
96
|
+
|
|
97
|
+
For the full project overview, install scripts, release-bundle flow, and command reference, see the main repository README:
|
|
98
|
+
|
|
99
|
+
- <https://github.com/imyourboyroy/pyenv-native>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.27"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pyenv-native"
|
|
7
|
+
version = "0.1.10"
|
|
8
|
+
description = "Python install package for the native-first pyenv-native runtime bundles."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Roy Dawson IV", email = "Roy.Dawson.IV@gmail.com" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["pyenv", "python", "windows", "bootstrap", "pipx"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Environment :: Console",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Operating System :: Microsoft :: Windows",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
24
|
+
"Programming Language :: Python :: 3.8",
|
|
25
|
+
"Programming Language :: Python :: 3.9",
|
|
26
|
+
"Programming Language :: Python :: 3.10",
|
|
27
|
+
"Programming Language :: Python :: 3.11",
|
|
28
|
+
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Programming Language :: Python :: 3.13",
|
|
30
|
+
"Topic :: Software Development",
|
|
31
|
+
"Topic :: System :: Installation/Setup",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://github.com/imyourboyroy"
|
|
36
|
+
Repository = "https://github.com/imyourboyroy/pyenv-native"
|
|
37
|
+
Issues = "https://github.com/imyourboyroy/pyenv-native/issues"
|
|
38
|
+
|
|
39
|
+
[project.scripts]
|
|
40
|
+
pyenv-native = "pyenv_native_bootstrap.cli:main"
|
|
41
|
+
|
|
42
|
+
[tool.hatch.build.targets.wheel]
|
|
43
|
+
packages = ["src/pyenv_native_bootstrap"]
|
|
44
|
+
|
|
45
|
+
[tool.hatch.build.targets.sdist]
|
|
46
|
+
include = [
|
|
47
|
+
"src/pyenv_native_bootstrap",
|
|
48
|
+
"tests",
|
|
49
|
+
"LICENSE",
|
|
50
|
+
"README.md",
|
|
51
|
+
"pyproject.toml",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# ./python-package/src/pyenv_native_bootstrap/bundle.py
|
|
2
|
+
"""Bundle metadata, checksum, and download helpers for pyenv-native release assets."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import hashlib
|
|
7
|
+
import json
|
|
8
|
+
import tarfile
|
|
9
|
+
import urllib.request
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Optional
|
|
13
|
+
from zipfile import ZipFile
|
|
14
|
+
|
|
15
|
+
from . import __version__
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class BundleManifest:
|
|
20
|
+
"""Describes a built release bundle and the scripts it carries."""
|
|
21
|
+
|
|
22
|
+
bundle_name: str
|
|
23
|
+
bundle_version: str
|
|
24
|
+
platform: str
|
|
25
|
+
architecture: str
|
|
26
|
+
executable: str
|
|
27
|
+
install_script: str
|
|
28
|
+
uninstall_script: str
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def sha256_file(path: Path) -> str:
|
|
32
|
+
"""Return the SHA-256 digest for a file."""
|
|
33
|
+
|
|
34
|
+
digest = hashlib.sha256()
|
|
35
|
+
with path.open("rb") as handle:
|
|
36
|
+
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
|
37
|
+
digest.update(chunk)
|
|
38
|
+
return digest.hexdigest()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def parse_checksum_text(text: str, expected_file_name: Optional[str] = None) -> str:
|
|
42
|
+
"""Extract a SHA-256 value from a checksum file or raw string."""
|
|
43
|
+
|
|
44
|
+
lines = [line.strip() for line in text.splitlines() if line.strip()]
|
|
45
|
+
if not lines:
|
|
46
|
+
raise ValueError("checksum text did not contain any usable lines")
|
|
47
|
+
|
|
48
|
+
for line in lines:
|
|
49
|
+
parts = line.split()
|
|
50
|
+
if len(parts) == 1 and len(parts[0]) == 64:
|
|
51
|
+
return parts[0].lower()
|
|
52
|
+
if len(parts) >= 2 and len(parts[0]) == 64:
|
|
53
|
+
candidate_name = parts[-1].lstrip("*")
|
|
54
|
+
if expected_file_name is None or candidate_name == expected_file_name:
|
|
55
|
+
return parts[0].lower()
|
|
56
|
+
|
|
57
|
+
raise ValueError("checksum text did not contain a valid SHA-256 entry")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def is_tar_bundle(bundle_path: Path) -> bool:
|
|
61
|
+
"""Return true when a bundle path uses a tar-based archive format."""
|
|
62
|
+
|
|
63
|
+
suffixes = [suffix.lower() for suffix in bundle_path.suffixes]
|
|
64
|
+
return suffixes[-2:] == [".tar", ".gz"] or bundle_path.suffix.lower() == ".tgz"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def read_manifest_from_bundle(bundle_path: Path) -> BundleManifest:
|
|
68
|
+
"""Read and parse the bundle manifest embedded inside a release archive."""
|
|
69
|
+
|
|
70
|
+
if is_tar_bundle(bundle_path):
|
|
71
|
+
with tarfile.open(bundle_path, "r:*") as archive:
|
|
72
|
+
member = next(
|
|
73
|
+
(
|
|
74
|
+
candidate
|
|
75
|
+
for candidate in archive.getmembers()
|
|
76
|
+
if Path(candidate.name).name == "bundle-manifest.json"
|
|
77
|
+
),
|
|
78
|
+
None,
|
|
79
|
+
)
|
|
80
|
+
if member is None:
|
|
81
|
+
raise FileNotFoundError("bundle-manifest.json was not found in the bundle archive")
|
|
82
|
+
handle = archive.extractfile(member)
|
|
83
|
+
if handle is None:
|
|
84
|
+
raise FileNotFoundError("bundle-manifest.json could not be extracted")
|
|
85
|
+
payload = json.loads(handle.read().decode("utf-8-sig"))
|
|
86
|
+
else:
|
|
87
|
+
with ZipFile(bundle_path, "r") as archive:
|
|
88
|
+
with archive.open("bundle-manifest.json", "r") as handle:
|
|
89
|
+
payload = json.loads(handle.read().decode("utf-8-sig"))
|
|
90
|
+
|
|
91
|
+
return BundleManifest(
|
|
92
|
+
bundle_name=payload["bundle_name"],
|
|
93
|
+
bundle_version=payload["bundle_version"],
|
|
94
|
+
platform=payload["platform"],
|
|
95
|
+
architecture=payload["architecture"],
|
|
96
|
+
executable=payload["executable"],
|
|
97
|
+
install_script=payload["install_script"],
|
|
98
|
+
uninstall_script=payload["uninstall_script"],
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def read_manifest_from_zip(bundle_path: Path) -> BundleManifest:
|
|
103
|
+
"""Backward-compatible wrapper for zip-based bundle manifest reads."""
|
|
104
|
+
|
|
105
|
+
return read_manifest_from_bundle(bundle_path)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def download_file(url: str, destination: Path) -> Path:
|
|
109
|
+
"""Download a file to a destination path, replacing any prior file."""
|
|
110
|
+
|
|
111
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
112
|
+
request = urllib.request.Request(
|
|
113
|
+
url,
|
|
114
|
+
headers={"User-Agent": f"pyenv-native/{__version__}"},
|
|
115
|
+
)
|
|
116
|
+
with urllib.request.urlopen(request) as response, destination.open("wb") as handle: # noqa: S310
|
|
117
|
+
handle.write(response.read())
|
|
118
|
+
return destination
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def load_checksum_text(
|
|
122
|
+
checksum: Optional[str],
|
|
123
|
+
checksum_path: Optional[Path],
|
|
124
|
+
checksum_url: Optional[str],
|
|
125
|
+
cache_path: Path,
|
|
126
|
+
) -> Optional[str]:
|
|
127
|
+
"""Load checksum content from an inline value, local file, or remote URL."""
|
|
128
|
+
|
|
129
|
+
if checksum:
|
|
130
|
+
return checksum.strip()
|
|
131
|
+
if checksum_path:
|
|
132
|
+
return checksum_path.read_text(encoding="utf-8")
|
|
133
|
+
if checksum_url:
|
|
134
|
+
checksum_target = cache_path.with_suffix(cache_path.suffix + ".sha256")
|
|
135
|
+
download_file(checksum_url, checksum_target)
|
|
136
|
+
return checksum_target.read_text(encoding="utf-8")
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def verify_bundle_checksum(
|
|
141
|
+
bundle_path: Path,
|
|
142
|
+
checksum: Optional[str] = None,
|
|
143
|
+
checksum_path: Optional[Path] = None,
|
|
144
|
+
checksum_url: Optional[str] = None,
|
|
145
|
+
) -> str:
|
|
146
|
+
"""Verify the bundle against a supplied checksum source and return the digest."""
|
|
147
|
+
|
|
148
|
+
checksum_text = load_checksum_text(
|
|
149
|
+
checksum=checksum,
|
|
150
|
+
checksum_path=checksum_path,
|
|
151
|
+
checksum_url=checksum_url,
|
|
152
|
+
cache_path=bundle_path,
|
|
153
|
+
)
|
|
154
|
+
actual = sha256_file(bundle_path)
|
|
155
|
+
if checksum_text is None:
|
|
156
|
+
return actual
|
|
157
|
+
|
|
158
|
+
expected = parse_checksum_text(checksum_text, bundle_path.name)
|
|
159
|
+
if actual.lower() != expected.lower():
|
|
160
|
+
raise ValueError(
|
|
161
|
+
f"checksum mismatch for {bundle_path}: expected {expected}, got {actual}"
|
|
162
|
+
)
|
|
163
|
+
return actual
|