pystand 1.12__py3-none-any.whl → 2.1__py3-none-any.whl
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.
- {pystand-1.12.dist-info → pystand-2.1.dist-info}/METADATA +74 -38
- pystand-2.1.dist-info/RECORD +6 -0
- {pystand-1.12.dist-info → pystand-2.1.dist-info}/WHEEL +1 -1
- pystand.py +132 -107
- pystand-1.12.dist-info/RECORD +0 -6
- {pystand-1.12.dist-info → pystand-2.1.dist-info}/entry_points.txt +0 -0
- {pystand-1.12.dist-info → pystand-2.1.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: pystand
|
3
|
-
Version: 1
|
3
|
+
Version: 2.1
|
4
4
|
Summary: Install Python versions from python-build-standalone project
|
5
5
|
Author-email: Mark Blakeney <mark.blakeney@bullet-systems.net>
|
6
6
|
License: GPLv3
|
@@ -13,6 +13,7 @@ Requires-Dist: argcomplete
|
|
13
13
|
Requires-Dist: packaging
|
14
14
|
Requires-Dist: platformdirs
|
15
15
|
Requires-Dist: pygithub
|
16
|
+
Requires-Dist: zstandard
|
16
17
|
|
17
18
|
## PYSTAND - Install Python Versions From The Python-Build-Standalone Project
|
18
19
|
[](https://pypi.org/project/pystand/)
|
@@ -33,11 +34,15 @@ provided:
|
|
33
34
|
|`path` |Show path prefix to installed version base directory |
|
34
35
|
|
35
36
|
By default, Python versions are sourced from the latest
|
36
|
-
`python-build-standalone` [release][pbs-rel] available (e.g.
|
37
|
-
optionally specify any older release. The
|
37
|
+
`python-build-standalone` [release][pbs-rel] available (e.g.
|
38
|
+
"`20240415`") but you can optionally specify any older release. The
|
39
|
+
required
|
38
40
|
[distribution](https://gregoryszorc.com/docs/python-build-standalone/main/running.html)
|
39
|
-
for your machine architecture is normally auto-detected
|
40
|
-
|
41
|
+
for your machine architecture is normally auto-detected. By default, the
|
42
|
+
_`install_only_stripped`_ build of the distribution is installed but you
|
43
|
+
can choose to [install any other
|
44
|
+
build/distribution](#installing-other-builds/distributions) instead, or
|
45
|
+
in parallel.
|
41
46
|
|
42
47
|
Some simple usage examples are:
|
43
48
|
|
@@ -66,21 +71,21 @@ $ pystand install 3.10
|
|
66
71
|
Version 3.10.14 @ 20240415 installed.
|
67
72
|
|
68
73
|
$ pystand list
|
69
|
-
3.10.14 @ 20240415 distribution="x86_64-unknown-linux-gnu"
|
70
|
-
3.12.3 @ 20240415 distribution="x86_64-unknown-linux-gnu"
|
74
|
+
3.10.14 @ 20240415 distribution="x86_64-unknown-linux-gnu-install_only_stripped"
|
75
|
+
3.12.3 @ 20240415 distribution="x86_64-unknown-linux-gnu-install_only_stripped"
|
71
76
|
|
72
77
|
$ pystand show
|
73
|
-
3.8.19 @ 20240415 distribution="x86_64-unknown-linux-gnu"
|
74
|
-
3.9.19 @ 20240415 distribution="x86_64-unknown-linux-gnu"
|
75
|
-
3.10.14 @ 20240415 distribution="x86_64-unknown-linux-gnu" (installed)
|
76
|
-
3.11.9 @ 20240415 distribution="x86_64-unknown-linux-gnu"
|
77
|
-
3.12.3 @ 20240415 distribution="x86_64-unknown-linux-gnu" (installed)
|
78
|
+
3.8.19 @ 20240415 distribution="x86_64-unknown-linux-gnu-install_only_stripped"
|
79
|
+
3.9.19 @ 20240415 distribution="x86_64-unknown-linux-gnu-install_only_stripped"
|
80
|
+
3.10.14 @ 20240415 distribution="x86_64-unknown-linux-gnu-install_only_stripped" (installed)
|
81
|
+
3.11.9 @ 20240415 distribution="x86_64-unknown-linux-gnu-install_only_stripped"
|
82
|
+
3.12.3 @ 20240415 distribution="x86_64-unknown-linux-gnu-install_only_stripped" (installed)
|
78
83
|
|
79
84
|
$ pystand remove 3.10
|
80
85
|
Version 3.10.14 @ 20240415 removed.
|
81
86
|
|
82
87
|
$ pystand list
|
83
|
-
3.12.3 @ 20240415 distribution="x86_64-unknown-linux-gnu"
|
88
|
+
3.12.3 @ 20240415 distribution="x86_64-unknown-linux-gnu-install_only_stripped"
|
84
89
|
```
|
85
90
|
|
86
91
|
Here are some examples showing how to use an installed version ..
|
@@ -122,10 +127,10 @@ https://github.com/bulletmark/pystand.
|
|
122
127
|
Type `pystand` or `pystand -h` to view the usage summary:
|
123
128
|
|
124
129
|
```
|
125
|
-
usage: pystand [-h] [-D DISTRIBUTION] [-
|
126
|
-
[--purge-days PURGE_DAYS]
|
130
|
+
usage: pystand [-h] [-D DISTRIBUTION] [-P PREFIX_DIR] [-C CACHE_DIR]
|
131
|
+
[-M CACHE_MINUTES] [--purge-days PURGE_DAYS]
|
127
132
|
[--github-access-token GITHUB_ACCESS_TOKEN] [--no-strip]
|
128
|
-
[
|
133
|
+
[-V]
|
129
134
|
{install,update,remove,list,show,path} ...
|
130
135
|
|
131
136
|
Command line tool to download, install, and update pre-built Python versions
|
@@ -135,26 +140,27 @@ https://github.com/indygreg/python-build-standalone.
|
|
135
140
|
options:
|
136
141
|
-h, --help show this help message and exit
|
137
142
|
-D DISTRIBUTION, --distribution DISTRIBUTION
|
138
|
-
python-build-standalone
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
143
|
+
python-build-standalone distribution. Default is auto-
|
144
|
+
detected (detected as "x86_64-unknown-linux-gnu-
|
145
|
+
install_only_stripped" for this current host).
|
146
|
+
-P PREFIX_DIR, --prefix-dir PREFIX_DIR
|
147
|
+
specify prefix dir for storing versions. Default is
|
148
|
+
"$HOME/.local/share/pystand"
|
149
|
+
-C CACHE_DIR, --cache-dir CACHE_DIR
|
150
|
+
specify cache dir for downloads. Default is
|
151
|
+
"$HOME/.cache/pystand"
|
152
|
+
-M CACHE_MINUTES, --cache-minutes CACHE_MINUTES
|
146
153
|
cache latest YYYYMMDD release tag fetch for this many
|
147
154
|
minutes, before rechecking for latest. Default is 60
|
148
155
|
minutes
|
149
156
|
--purge-days PURGE_DAYS
|
150
|
-
cache YYYYMMDD release file lists
|
151
|
-
days after last version referencing
|
152
|
-
Default is 90 days
|
157
|
+
cache YYYYMMDD release file lists and downloads for
|
158
|
+
this number of days after last version referencing
|
159
|
+
that release is removed. Default is 90 days
|
153
160
|
--github-access-token GITHUB_ACCESS_TOKEN
|
154
161
|
optional Github access token. Can specify to reduce
|
155
162
|
rate limiting.
|
156
|
-
--no-strip do
|
157
|
-
--no-extra-strip do not restrip already stripped source binaries
|
163
|
+
--no-strip do strip downloaded binaries
|
158
164
|
-V, --version just show pystand version
|
159
165
|
|
160
166
|
Commands:
|
@@ -258,18 +264,18 @@ options:
|
|
258
264
|
### Command `show`
|
259
265
|
|
260
266
|
```
|
261
|
-
usage: pystand show [-h] [-
|
267
|
+
usage: pystand show [-h] [-a] [release]
|
262
268
|
|
263
269
|
Show versions available from a release.
|
264
270
|
|
265
271
|
positional arguments:
|
266
|
-
release
|
267
|
-
|
272
|
+
release python-build-standalone YYYYMMDD release to show (e.g.
|
273
|
+
20240415), default is latest release
|
268
274
|
|
269
275
|
options:
|
270
|
-
-h, --help
|
271
|
-
-
|
272
|
-
|
276
|
+
-h, --help show this help message and exit
|
277
|
+
-a, --all also show all available distributions for each version from the
|
278
|
+
release
|
273
279
|
```
|
274
280
|
|
275
281
|
### Command `path`
|
@@ -313,6 +319,36 @@ To uninstall:
|
|
313
319
|
$ pipx uninstall pystand
|
314
320
|
```
|
315
321
|
|
322
|
+
## Installing Other Builds/Distributions
|
323
|
+
|
324
|
+
The _`install_only_stripped`_ build of each distribution is installed by
|
325
|
+
default. See description of distributions/builds
|
326
|
+
[here](https://gregoryszorc.com/docs/python-build-standalone/main/running.html#obtaining-distributions).
|
327
|
+
However, you can choose to install other distributions/builds. E.g. If
|
328
|
+
we use a standard modern Linux x86_64 machine as an example, the default
|
329
|
+
distribution is _`x86_64-unknown-linux-gnu-install_only_stripped`_ and
|
330
|
+
the versions for these are installed by default at
|
331
|
+
`~/.local/share/pystand/<version>`.
|
332
|
+
|
333
|
+
However, let's say you want to experiment with the new free-threaded
|
334
|
+
3.13 build. You can install this to a different directory, e.g.
|
335
|
+
|
336
|
+
```sh
|
337
|
+
$ mkdir ./3.13-freethreaded
|
338
|
+
$ cd ./3.13-freethreaded
|
339
|
+
|
340
|
+
$ pystand -P. -D x86_64-unknown-linux-gnu-freethreaded+lto-full install 3.13
|
341
|
+
$ ./3.13/bin/python -V
|
342
|
+
Python 3.13.0
|
343
|
+
|
344
|
+
$ pystand -P . list
|
345
|
+
3.13.0 @ 20241016 distribution="x86_64_v4-unknown-linux-gnu-freethreaded+lto-full"
|
346
|
+
```
|
347
|
+
|
348
|
+
Note you can set a different default distribution by
|
349
|
+
specifying `--distribution` as a [default
|
350
|
+
option](#command-default-options).
|
351
|
+
|
316
352
|
## Extrapolation of Python Versions
|
317
353
|
|
318
354
|
`pystand` extrapolates any version text you specify on the command line
|
@@ -348,9 +384,9 @@ options will be concatenated and automatically prepended to your
|
|
348
384
|
anything after on a line) are ignored. Type `pystand` to see all
|
349
385
|
supported options.
|
350
386
|
|
351
|
-
The global options: `--distribution`, `--
|
352
|
-
`--purge-days`, `--github-access-token`,
|
353
|
-
`--no-
|
387
|
+
The global options: `--distribution`, `--prefix-dir`, `--cache-dir`,
|
388
|
+
`--cache-minutes`, `--purge-days`, `--github-access-token`,
|
389
|
+
`--no-strip`, are the only sensible candidates to consider setting
|
354
390
|
as defaults.
|
355
391
|
|
356
392
|
## Github API Rate Limiting
|
@@ -0,0 +1,6 @@
|
|
1
|
+
pystand.py,sha256=x6qijPBLaKWX0BxlbQyKa3_N20dWnbSCGvBSfflEczU,32597
|
2
|
+
pystand-2.1.dist-info/METADATA,sha256=d79DfUbvnmHlCFkUDHYSxJwQDGJTDFMT_nmNx1Wu7yI,17403
|
3
|
+
pystand-2.1.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
|
4
|
+
pystand-2.1.dist-info/entry_points.txt,sha256=DG4ps3I3nni1bubV1tXs6u8FARgkdbAYaEAzZD4RAo8,41
|
5
|
+
pystand-2.1.dist-info/top_level.txt,sha256=NoWUh19UQymAJLHTCdxMnVwV6Teftef5fzyF3OWLyNY,8
|
6
|
+
pystand-2.1.dist-info/RECORD,,
|
pystand.py
CHANGED
@@ -15,7 +15,9 @@ import shlex
|
|
15
15
|
import shutil
|
16
16
|
import subprocess
|
17
17
|
import sys
|
18
|
+
import tarfile
|
18
19
|
import time
|
20
|
+
import urllib.parse
|
19
21
|
import urllib.request
|
20
22
|
from argparse import ArgumentParser, Namespace
|
21
23
|
from collections import defaultdict
|
@@ -25,6 +27,7 @@ from typing import Any, Iterable, Iterator
|
|
25
27
|
|
26
28
|
import argcomplete
|
27
29
|
import platformdirs
|
30
|
+
import zstandard
|
28
31
|
from packaging.version import parse as parse_version
|
29
32
|
|
30
33
|
REPO_OWNER = 'indygreg'
|
@@ -33,31 +36,22 @@ GITHUB_REPO = f'{REPO_OWNER}/{REPO}'
|
|
33
36
|
LATEST_RELEASE_URL = f'https://raw.githubusercontent.com/{GITHUB_REPO}'\
|
34
37
|
'/latest-release/latest-release.json'
|
35
38
|
|
36
|
-
# The following release is the first one that supports "install_only"
|
37
|
-
# builds so this tool does not support any releases before this.
|
38
|
-
FIRST_RELEASE = '20210724'
|
39
|
-
|
40
39
|
# Sample release tag for documentation/usage examples
|
41
40
|
SAMPL_RELEASE = '20240415'
|
42
41
|
|
43
|
-
STRIP_EXT = '_stripped'
|
44
|
-
|
45
|
-
# The following regexp pattern is used to match the end of a release file name
|
46
|
-
ENDPAT = r'-install_only[-\dT]*.tar.gz$'
|
47
|
-
|
48
42
|
PROG = Path(__file__).stem
|
49
43
|
CNFFILE = platformdirs.user_config_path(f'{PROG}-flags.conf')
|
50
44
|
|
51
45
|
# Default distributions for various platforms
|
52
46
|
DISTRIBUTIONS = {
|
53
|
-
('Linux', 'x86_64'): 'x86_64-unknown-linux-gnu',
|
54
|
-
('Linux', 'aarch64'): 'aarch64-unknown-linux-gnu',
|
55
|
-
('Linux', 'armv7l'): 'armv7-unknown-linux-gnueabihf',
|
56
|
-
('Linux', 'armv8l'): 'armv7-unknown-linux-gnueabihf',
|
57
|
-
('Darwin', 'x86_64'): 'x86_64-apple-darwin',
|
58
|
-
('Darwin', 'aarch64'): 'aarch64-apple-darwin',
|
59
|
-
('Windows', 'x86_64'): 'x86_64-pc-windows-msvc',
|
60
|
-
('Windows', 'i686'): 'i686-pc-windows-msvc',
|
47
|
+
('Linux', 'x86_64'): 'x86_64-unknown-linux-gnu-install_only_stripped',
|
48
|
+
('Linux', 'aarch64'): 'aarch64-unknown-linux-gnu-install_only_stripped',
|
49
|
+
('Linux', 'armv7l'): 'armv7-unknown-linux-gnueabihf-install_only_stripped',
|
50
|
+
('Linux', 'armv8l'): 'armv7-unknown-linux-gnueabihf-install_only_stripped',
|
51
|
+
('Darwin', 'x86_64'): 'x86_64-apple-darwin-install_only_stripped',
|
52
|
+
('Darwin', 'aarch64'): 'aarch64-apple-darwin-install_only_stripped',
|
53
|
+
('Windows', 'x86_64'): 'x86_64-pc-windows-msvc-install_only_stripped',
|
54
|
+
('Windows', 'i686'): 'i686-pc-windows-msvc-install_only_stripped',
|
61
55
|
}
|
62
56
|
|
63
57
|
def is_admin() -> bool:
|
@@ -132,6 +126,52 @@ def rm_path(path: Path) -> None:
|
|
132
126
|
elif path.exists():
|
133
127
|
path.unlink()
|
134
128
|
|
129
|
+
def unpack_zst(filename: str, extract_dir: str) -> None:
|
130
|
+
'Unpack a zstandard compressed tar'
|
131
|
+
with open(filename, 'rb') as compressed:
|
132
|
+
dctx = zstandard.ZstdDecompressor()
|
133
|
+
with dctx.stream_reader(compressed) as reader:
|
134
|
+
with tarfile.open(fileobj=reader, mode='r|') as tar:
|
135
|
+
tar.extractall(path=extract_dir)
|
136
|
+
|
137
|
+
def fetch(args: Namespace, release: str, url: str, tdir: Path) -> str | None:
|
138
|
+
'Fetch and unpack a release file'
|
139
|
+
error = None
|
140
|
+
tmpdir = tdir.with_name(f'{tdir.name}-tmp')
|
141
|
+
rm_path(tmpdir)
|
142
|
+
tmpdir.mkdir(parents=True)
|
143
|
+
|
144
|
+
filename_q = Path(urllib.parse.urlparse(url).path).name
|
145
|
+
filename = urllib.parse.unquote(filename_q)
|
146
|
+
cache_file = args._downloads / release / filename
|
147
|
+
cache_file.parent.mkdir(parents=True, exist_ok=True)
|
148
|
+
|
149
|
+
if not cache_file.exists():
|
150
|
+
try:
|
151
|
+
urllib.request.urlretrieve(url, cache_file)
|
152
|
+
except Exception as e:
|
153
|
+
error = f'Failed to fetch "{url}": {e}'
|
154
|
+
|
155
|
+
if error:
|
156
|
+
rm_path(cache_file)
|
157
|
+
else:
|
158
|
+
if filename.endswith('.zst'):
|
159
|
+
shutil.register_unpack_format('zst', ['.zst'], unpack_zst)
|
160
|
+
|
161
|
+
try:
|
162
|
+
shutil.unpack_archive(cache_file, tmpdir)
|
163
|
+
except Exception as e:
|
164
|
+
error = f'Failed to unpack "{url}": {e}'
|
165
|
+
else:
|
166
|
+
pdir = tmpdir / 'python' / 'install'
|
167
|
+
if not pdir.exists():
|
168
|
+
pdir = pdir.parent
|
169
|
+
|
170
|
+
pdir.replace(tdir)
|
171
|
+
|
172
|
+
rm_path(tmpdir)
|
173
|
+
return error
|
174
|
+
|
135
175
|
def is_release_version(version: str) -> bool:
|
136
176
|
'Check if a string is a formal Python release tag'
|
137
177
|
return version.replace('.', '').isdigit()
|
@@ -179,7 +219,8 @@ class VersionMatcher:
|
|
179
219
|
def iter_versions(args: Namespace) -> Iterator[Path]:
|
180
220
|
'Iterate over all version dirs'
|
181
221
|
for f in args._versions.iterdir():
|
182
|
-
if f.is_dir() and not f.is_symlink()
|
222
|
+
if f.is_dir() and not f.is_symlink() \
|
223
|
+
and f.name[0] != '.' and f.name[0].isdigit():
|
183
224
|
yield f
|
184
225
|
|
185
226
|
def get_version_names(args: Namespace) -> list[str]:
|
@@ -211,10 +252,9 @@ def get_version_names(args: Namespace) -> list[str]:
|
|
211
252
|
return sorted(all_names - given, key=parse_version) \
|
212
253
|
if args.all else versions
|
213
254
|
|
214
|
-
def check_release_tag(release: str
|
215
|
-
check_first: bool = True) -> str | None:
|
255
|
+
def check_release_tag(release: str) -> str | None:
|
216
256
|
'Check the specified release tag is valid'
|
217
|
-
if not release.isdigit() or len(release) != len(
|
257
|
+
if not release.isdigit() or len(release) != len(SAMPL_RELEASE):
|
218
258
|
return 'Release must be a YYYYMMDD string.'
|
219
259
|
|
220
260
|
try:
|
@@ -222,9 +262,6 @@ def check_release_tag(release: str, *,
|
|
222
262
|
except Exception:
|
223
263
|
return 'Release must be a YYYYMMDD date string.'
|
224
264
|
|
225
|
-
if check_first and release < FIRST_RELEASE:
|
226
|
-
return f'Releases before "{FIRST_RELEASE}" do not include '\
|
227
|
-
'"*-install_only" builds so are not supported.'
|
228
265
|
return None
|
229
266
|
|
230
267
|
def get_release_tag(args: Namespace) -> str:
|
@@ -257,22 +294,32 @@ def get_release_tag(args: Namespace) -> str:
|
|
257
294
|
args._latest_release.write_text(tag + '\n')
|
258
295
|
return tag
|
259
296
|
|
260
|
-
def
|
261
|
-
'
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
297
|
+
def add_file(files: dict, tag: str, name: str, url: str) -> None:
|
298
|
+
'Extract the implementation, version, and architecture from a filename'
|
299
|
+
if name.endswith('.tar.zst'):
|
300
|
+
name = name[:-8]
|
301
|
+
elif name.endswith('.tar.gz'):
|
302
|
+
name = name[:-7]
|
303
|
+
else:
|
304
|
+
return
|
305
|
+
|
306
|
+
impl, ver, arch = name.split('-', 2)
|
307
|
+
|
308
|
+
# Modern releases have a '+' in the name to separate the version
|
309
|
+
if '+' in ver:
|
310
|
+
ver, filetag = ver.split('+')
|
311
|
+
if filetag != tag:
|
312
|
+
return
|
313
|
+
|
268
314
|
if impl not in files:
|
269
|
-
files[impl] =
|
315
|
+
files[impl] = {}
|
270
316
|
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
317
|
+
vers = files[impl]
|
318
|
+
|
319
|
+
if ver not in vers:
|
320
|
+
vers[ver] = {}
|
321
|
+
|
322
|
+
vers[ver][arch] = url
|
276
323
|
|
277
324
|
def get_release_files(args, tag, implementation: str | None = None) -> dict:
|
278
325
|
'Return the release files for the given tag'
|
@@ -294,13 +341,7 @@ def get_release_files(args, tag, implementation: str | None = None) -> dict:
|
|
294
341
|
# Iterate over the release assets and store pertinent files in a
|
295
342
|
# dict to return.
|
296
343
|
for asset in release.get_assets():
|
297
|
-
|
298
|
-
if is_stripped := (STRIP_EXT in name):
|
299
|
-
name = name.replace(STRIP_EXT, '')
|
300
|
-
|
301
|
-
if m := re.search(ENDPAT, name):
|
302
|
-
name = re.sub(r'\+\d{8}', '', name[:m.start()])
|
303
|
-
merge(files, name, asset.browser_download_url, is_stripped)
|
344
|
+
add_file(files, tag, asset.name, asset.browser_download_url)
|
304
345
|
|
305
346
|
if not files:
|
306
347
|
sys.exit(f'Failed to fetch any files for release {tag}')
|
@@ -374,10 +415,17 @@ def purge_unused_releases(args: Namespace) -> None:
|
|
374
415
|
|
375
416
|
now_secs = time.time()
|
376
417
|
end_secs = args.purge_days * 86400
|
377
|
-
|
418
|
+
releases -= keep
|
419
|
+
for release in releases:
|
378
420
|
rdir = args._releases / release
|
379
421
|
if (rdir.stat().st_mtime + end_secs) < now_secs:
|
380
422
|
rdir.unlink()
|
423
|
+
else:
|
424
|
+
keep.add(release)
|
425
|
+
|
426
|
+
downloads = set(f.name for f in args._downloads.iterdir())
|
427
|
+
for release in (downloads - keep):
|
428
|
+
rm_path(args._downloads / release)
|
381
429
|
|
382
430
|
class COMMAND:
|
383
431
|
'Base class for all commands'
|
@@ -439,57 +487,29 @@ def install(args: Namespace, vdir: Path, release: str, distribution: str,
|
|
439
487
|
'Install a version'
|
440
488
|
version = vdir.name
|
441
489
|
|
442
|
-
if not (
|
490
|
+
if not (url := files[version].get(distribution)):
|
443
491
|
return f'Arch "{distribution}" not found for release '\
|
444
492
|
f'{release} version {version}.'
|
445
493
|
|
446
|
-
if isinstance(fileurl, str):
|
447
|
-
stripped = False
|
448
|
-
else:
|
449
|
-
stripped = not args.no_strip
|
450
|
-
if not (fileurl := fileurl[stripped]):
|
451
|
-
desc = 'stripped' if stripped else 'unstripped'
|
452
|
-
return f'Arch {desc} "{distribution}" not found for release '\
|
453
|
-
f'{release} version {version}.'
|
454
|
-
|
455
494
|
tmpdir = args._versions / f'.{version}-tmp'
|
456
495
|
rm_path(tmpdir)
|
457
|
-
tmpdir.mkdir()
|
458
|
-
tmpdir_py = tmpdir / 'python'
|
459
|
-
error = None
|
460
|
-
|
461
|
-
try:
|
462
|
-
urllib.request.urlretrieve(fileurl, tmpdir / 'tmp.tar.gz')
|
463
|
-
except Exception as e:
|
464
|
-
error = f'Failed to fetch "{fileurl}": {e}'
|
465
|
-
|
466
|
-
try:
|
467
|
-
shutil.unpack_archive(tmpdir / 'tmp.tar.gz', tmpdir)
|
468
|
-
except Exception as e:
|
469
|
-
error = f'Failed to unpack "{fileurl}": {e}'
|
496
|
+
tmpdir.mkdir(parents=True)
|
470
497
|
|
471
|
-
if not error:
|
498
|
+
if not (error := fetch(args, release, url, tmpdir)):
|
472
499
|
data = {'release': release, 'distribution': distribution}
|
473
500
|
|
474
|
-
if args.no_strip:
|
475
|
-
data['stripped'] = '
|
476
|
-
elif stripped and args.no_extra_strip:
|
477
|
-
data['stripped'] = 'at_source_only'
|
478
|
-
else:
|
479
|
-
if strip_binaries(tmpdir_py, distribution):
|
480
|
-
data['stripped'] = 'at_source_and_manually' \
|
481
|
-
if stripped else 'manually'
|
482
|
-
else:
|
483
|
-
data['stripped'] = 'at_source' if stripped else 'n/a'
|
501
|
+
if not args.no_strip and strip_binaries(tmpdir, distribution):
|
502
|
+
data['stripped'] = 'true'
|
484
503
|
|
485
|
-
if (error := set_json(
|
504
|
+
if (error := set_json(tmpdir / args._data, data)):
|
486
505
|
error = f'Failed to write {version} data file: {error}'
|
487
506
|
|
488
|
-
if
|
507
|
+
if error:
|
508
|
+
shutil.rmtree(tmpdir)
|
509
|
+
else:
|
489
510
|
remove(args, version)
|
490
|
-
|
511
|
+
tmpdir.replace(vdir)
|
491
512
|
|
492
|
-
shutil.rmtree(tmpdir)
|
493
513
|
return error
|
494
514
|
|
495
515
|
def main() -> str | None:
|
@@ -497,8 +517,9 @@ def main() -> str | None:
|
|
497
517
|
distro_default = DISTRIBUTIONS.get((platform.system(), platform.machine()))
|
498
518
|
distro_help = distro_default or '?unknown?'
|
499
519
|
|
500
|
-
|
501
|
-
|
520
|
+
p = '/opt' if is_admin() else platformdirs.user_data_dir()
|
521
|
+
prefix_dir = str(Path(p, PROG))
|
522
|
+
cache_dir = platformdirs.user_cache_path() / PROG
|
502
523
|
|
503
524
|
# Parse arguments
|
504
525
|
opt = ArgumentParser(description=__doc__,
|
@@ -507,28 +528,28 @@ def main() -> str | None:
|
|
507
528
|
|
508
529
|
# Set up main/global arguments
|
509
530
|
opt.add_argument('-D', '--distribution',
|
510
|
-
help=f'{REPO}
|
511
|
-
'distribution, e.g. "x86_64-unknown-linux-gnu". '
|
531
|
+
help=f'{REPO} distribution. '
|
512
532
|
f'Default is auto-detected (detected as "{distro_help}" '
|
513
533
|
'for this current host).')
|
514
|
-
opt.add_argument('-
|
515
|
-
help=
|
516
|
-
'versions
|
517
|
-
opt.add_argument('-C', '--cache-
|
534
|
+
opt.add_argument('-P', '--prefix-dir', default=prefix_dir,
|
535
|
+
help='specify prefix dir for storing '
|
536
|
+
'versions. Default is "%(default)s"')
|
537
|
+
opt.add_argument('-C', '--cache-dir', default=str(cache_dir),
|
538
|
+
help='specify cache dir for downloads. '
|
539
|
+
'Default is "%(default)s"')
|
540
|
+
opt.add_argument('-M', '--cache-minutes', default=60, type=float,
|
518
541
|
help='cache latest YYYYMMDD release tag fetch for this '
|
519
542
|
'many minutes, before rechecking for latest. '
|
520
543
|
'Default is %(default)d minutes')
|
521
544
|
opt.add_argument('--purge-days', default=90, type=int,
|
522
|
-
help='cache YYYYMMDD release file lists
|
523
|
-
'of days after last version referencing
|
524
|
-
'Default is %(default)d days')
|
545
|
+
help='cache YYYYMMDD release file lists and downloads for '
|
546
|
+
'this number of days after last version referencing that '
|
547
|
+
'release is removed. Default is %(default)d days')
|
525
548
|
opt.add_argument('--github-access-token',
|
526
549
|
help='optional Github access token. Can specify to reduce '
|
527
550
|
'rate limiting.')
|
528
551
|
opt.add_argument('--no-strip', action='store_true',
|
529
|
-
help='do
|
530
|
-
opt.add_argument('--no-extra-strip', action='store_true',
|
531
|
-
help='do not restrip already stripped source binaries')
|
552
|
+
help='do strip downloaded binaries')
|
532
553
|
opt.add_argument('-V', '--version', action='store_true',
|
533
554
|
help=f'just show {PROG} version')
|
534
555
|
cmd = opt.add_subparsers(title='Commands', dest='cmdname')
|
@@ -583,20 +604,24 @@ def main() -> str | None:
|
|
583
604
|
'using -D/--distribution option.')
|
584
605
|
|
585
606
|
# Keep some useful info in the namespace passed to the command
|
586
|
-
|
607
|
+
prefix_dir = Path(args.prefix_dir).expanduser()
|
608
|
+
cache_dir = Path(args.cache_dir).expanduser()
|
587
609
|
|
588
610
|
args._distribution = distribution
|
589
611
|
args._data = f'{PROG}.json'
|
590
|
-
|
591
|
-
args.
|
592
|
-
args._versions = base_dir / 'versions'
|
612
|
+
|
613
|
+
args._versions = prefix_dir
|
593
614
|
args._versions.mkdir(parents=True, exist_ok=True)
|
594
|
-
|
615
|
+
|
616
|
+
args._downloads = cache_dir / 'downloads'
|
617
|
+
args._downloads.mkdir(parents=True, exist_ok=True)
|
618
|
+
args._releases = cache_dir / 'releases'
|
595
619
|
args._releases.mkdir(parents=True, exist_ok=True)
|
620
|
+
args._latest_release = cache_dir / 'latest_release'
|
596
621
|
|
597
622
|
result = args.func(args)
|
598
|
-
update_version_symlinks(args)
|
599
623
|
purge_unused_releases(args)
|
624
|
+
update_version_symlinks(args)
|
600
625
|
return result
|
601
626
|
|
602
627
|
@COMMAND.add
|
@@ -722,7 +747,7 @@ class _remove(COMMAND):
|
|
722
747
|
def run(args: Namespace) -> str | None:
|
723
748
|
release_del = args.release
|
724
749
|
if release_del and \
|
725
|
-
(err := check_release_tag(release_del
|
750
|
+
(err := check_release_tag(release_del)):
|
726
751
|
return err
|
727
752
|
|
728
753
|
for version in get_version_names(args):
|
@@ -802,7 +827,7 @@ class _show(COMMAND):
|
|
802
827
|
'Show versions available from a release.'
|
803
828
|
@staticmethod
|
804
829
|
def init(parser: ArgumentParser) -> None:
|
805
|
-
parser.add_argument('-
|
830
|
+
parser.add_argument('-a', '--all', action='store_true',
|
806
831
|
help='also show all available distributions for '
|
807
832
|
'each version from the release')
|
808
833
|
parser.add_argument('release', nargs='?',
|
@@ -829,7 +854,7 @@ class _show(COMMAND):
|
|
829
854
|
for distribution in files[version]:
|
830
855
|
app = ' (installed)' \
|
831
856
|
if distribution == installed_distribution else ''
|
832
|
-
if args.
|
857
|
+
if args.all or app \
|
833
858
|
or distribution == args._distribution:
|
834
859
|
if distribution == args._distribution:
|
835
860
|
installable = True
|
pystand-1.12.dist-info/RECORD
DELETED
@@ -1,6 +0,0 @@
|
|
1
|
-
pystand.py,sha256=lWNyCZTLNNmh6OoKrAG7pKCqOaoZollVpCWbAHrCxeM,32450
|
2
|
-
pystand-1.12.dist-info/METADATA,sha256=VV3aLNNThQ0p06Hk_MWTN79jZ4MaKCgF273oMuRrZBE,15923
|
3
|
-
pystand-1.12.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
4
|
-
pystand-1.12.dist-info/entry_points.txt,sha256=DG4ps3I3nni1bubV1tXs6u8FARgkdbAYaEAzZD4RAo8,41
|
5
|
-
pystand-1.12.dist-info/top_level.txt,sha256=NoWUh19UQymAJLHTCdxMnVwV6Teftef5fzyF3OWLyNY,8
|
6
|
-
pystand-1.12.dist-info/RECORD,,
|
File without changes
|
File without changes
|