pystand 1.12__py3-none-any.whl → 2.0__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.0.dist-info}/METADATA +74 -38
- pystand-2.0.dist-info/RECORD +6 -0
- {pystand-1.12.dist-info → pystand-2.0.dist-info}/WHEEL +1 -1
- pystand.py +129 -106
- pystand-1.12.dist-info/RECORD +0 -6
- {pystand-1.12.dist-info → pystand-2.0.dist-info}/entry_points.txt +0 -0
- {pystand-1.12.dist-info → pystand-2.0.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: pystand
|
3
|
-
Version:
|
3
|
+
Version: 2.0
|
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=UqlzHehVQ6gsP0p4CmVqrkYoaV6w5t0mO7y1d6VYkR0,32507
|
2
|
+
pystand-2.0.dist-info/METADATA,sha256=P6edgnJxiBTLN_iRj9fphdhOLW3nmRMJsZo4SOZA-EI,17403
|
3
|
+
pystand-2.0.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
|
4
|
+
pystand-2.0.dist-info/entry_points.txt,sha256=DG4ps3I3nni1bubV1tXs6u8FARgkdbAYaEAzZD4RAo8,41
|
5
|
+
pystand-2.0.dist-info/top_level.txt,sha256=NoWUh19UQymAJLHTCdxMnVwV6Teftef5fzyF3OWLyNY,8
|
6
|
+
pystand-2.0.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,51 @@ def rm_path(path: Path) -> None:
|
|
132
126
|
elif path.exists():
|
133
127
|
path.unlink()
|
134
128
|
|
129
|
+
def unpack_zst(filename, extract_dir):
|
130
|
+
with open(filename, 'rb') as compressed:
|
131
|
+
dctx = zstandard.ZstdDecompressor()
|
132
|
+
with dctx.stream_reader(compressed) as reader:
|
133
|
+
with tarfile.open(fileobj=reader, mode='r|') as tar:
|
134
|
+
tar.extractall(path=extract_dir)
|
135
|
+
|
136
|
+
def fetch(args: Namespace, release: str, url: str, tdir: Path) -> str | None:
|
137
|
+
'Fetch and unpack a release file'
|
138
|
+
error = None
|
139
|
+
tmpdir = tdir.with_name(f'{tdir.name}-tmp')
|
140
|
+
rm_path(tmpdir)
|
141
|
+
tmpdir.mkdir(parents=True)
|
142
|
+
|
143
|
+
filename_q = Path(urllib.parse.urlparse(url).path).name
|
144
|
+
filename = urllib.parse.unquote(filename_q)
|
145
|
+
cache_file = args._downloads / release / filename
|
146
|
+
cache_file.parent.mkdir(parents=True, exist_ok=True)
|
147
|
+
|
148
|
+
if not cache_file.exists():
|
149
|
+
try:
|
150
|
+
urllib.request.urlretrieve(url, cache_file)
|
151
|
+
except Exception as e:
|
152
|
+
error = f'Failed to fetch "{url}": {e}'
|
153
|
+
|
154
|
+
if error:
|
155
|
+
rm_path(cache_file)
|
156
|
+
else:
|
157
|
+
if filename.endswith('.zst'):
|
158
|
+
shutil.register_unpack_format('zst', ['.zst'], unpack_zst)
|
159
|
+
|
160
|
+
try:
|
161
|
+
shutil.unpack_archive(cache_file, tmpdir)
|
162
|
+
except Exception as e:
|
163
|
+
error = f'Failed to unpack "{url}": {e}'
|
164
|
+
else:
|
165
|
+
pdir = tmpdir / 'python' / 'install'
|
166
|
+
if not pdir.exists():
|
167
|
+
pdir = pdir.parent
|
168
|
+
|
169
|
+
pdir.replace(tdir)
|
170
|
+
|
171
|
+
rm_path(tmpdir)
|
172
|
+
return error
|
173
|
+
|
135
174
|
def is_release_version(version: str) -> bool:
|
136
175
|
'Check if a string is a formal Python release tag'
|
137
176
|
return version.replace('.', '').isdigit()
|
@@ -211,10 +250,9 @@ def get_version_names(args: Namespace) -> list[str]:
|
|
211
250
|
return sorted(all_names - given, key=parse_version) \
|
212
251
|
if args.all else versions
|
213
252
|
|
214
|
-
def check_release_tag(release: str
|
215
|
-
check_first: bool = True) -> str | None:
|
253
|
+
def check_release_tag(release: str) -> str | None:
|
216
254
|
'Check the specified release tag is valid'
|
217
|
-
if not release.isdigit() or len(release) != len(
|
255
|
+
if not release.isdigit() or len(release) != len(SAMPL_RELEASE):
|
218
256
|
return 'Release must be a YYYYMMDD string.'
|
219
257
|
|
220
258
|
try:
|
@@ -222,9 +260,6 @@ def check_release_tag(release: str, *,
|
|
222
260
|
except Exception:
|
223
261
|
return 'Release must be a YYYYMMDD date string.'
|
224
262
|
|
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
263
|
return None
|
229
264
|
|
230
265
|
def get_release_tag(args: Namespace) -> str:
|
@@ -257,22 +292,32 @@ def get_release_tag(args: Namespace) -> str:
|
|
257
292
|
args._latest_release.write_text(tag + '\n')
|
258
293
|
return tag
|
259
294
|
|
260
|
-
def
|
261
|
-
'
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
295
|
+
def add_file(files: dict, tag: str, name: str, url: str) -> None:
|
296
|
+
'Extract the implementation, version, and architecture from a filename'
|
297
|
+
if name.endswith('.tar.zst'):
|
298
|
+
name = name[:-8]
|
299
|
+
elif name.endswith('.tar.gz'):
|
300
|
+
name = name[:-7]
|
301
|
+
else:
|
302
|
+
return
|
303
|
+
|
304
|
+
impl, ver, arch = name.split('-', 2)
|
305
|
+
|
306
|
+
# Modern releases have a '+' in the name to separate the version
|
307
|
+
if '+' in ver:
|
308
|
+
ver, filetag = ver.split('+')
|
309
|
+
if filetag != tag:
|
310
|
+
return
|
311
|
+
|
268
312
|
if impl not in files:
|
269
|
-
files[impl] =
|
313
|
+
files[impl] = {}
|
270
314
|
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
315
|
+
vers = files[impl]
|
316
|
+
|
317
|
+
if ver not in vers:
|
318
|
+
vers[ver] = {}
|
319
|
+
|
320
|
+
vers[ver][arch] = url
|
276
321
|
|
277
322
|
def get_release_files(args, tag, implementation: str | None = None) -> dict:
|
278
323
|
'Return the release files for the given tag'
|
@@ -294,13 +339,7 @@ def get_release_files(args, tag, implementation: str | None = None) -> dict:
|
|
294
339
|
# Iterate over the release assets and store pertinent files in a
|
295
340
|
# dict to return.
|
296
341
|
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)
|
342
|
+
add_file(files, tag, asset.name, asset.browser_download_url)
|
304
343
|
|
305
344
|
if not files:
|
306
345
|
sys.exit(f'Failed to fetch any files for release {tag}')
|
@@ -374,10 +413,17 @@ def purge_unused_releases(args: Namespace) -> None:
|
|
374
413
|
|
375
414
|
now_secs = time.time()
|
376
415
|
end_secs = args.purge_days * 86400
|
377
|
-
|
416
|
+
releases -= keep
|
417
|
+
for release in releases:
|
378
418
|
rdir = args._releases / release
|
379
419
|
if (rdir.stat().st_mtime + end_secs) < now_secs:
|
380
420
|
rdir.unlink()
|
421
|
+
else:
|
422
|
+
keep.add(release)
|
423
|
+
|
424
|
+
downloads = set(f.name for f in args._downloads.iterdir())
|
425
|
+
for release in (downloads - keep):
|
426
|
+
rm_path(args._downloads / release)
|
381
427
|
|
382
428
|
class COMMAND:
|
383
429
|
'Base class for all commands'
|
@@ -439,57 +485,29 @@ def install(args: Namespace, vdir: Path, release: str, distribution: str,
|
|
439
485
|
'Install a version'
|
440
486
|
version = vdir.name
|
441
487
|
|
442
|
-
if not (
|
488
|
+
if not (url := files[version].get(distribution)):
|
443
489
|
return f'Arch "{distribution}" not found for release '\
|
444
490
|
f'{release} version {version}.'
|
445
491
|
|
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
492
|
tmpdir = args._versions / f'.{version}-tmp'
|
456
493
|
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}'
|
494
|
+
tmpdir.mkdir(parents=True)
|
470
495
|
|
471
|
-
if not error:
|
496
|
+
if not (error := fetch(args, release, url, tmpdir)):
|
472
497
|
data = {'release': release, 'distribution': distribution}
|
473
498
|
|
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'
|
499
|
+
if not args.no_strip and strip_binaries(tmpdir, distribution):
|
500
|
+
data['stripped'] = 'true'
|
484
501
|
|
485
|
-
if (error := set_json(
|
502
|
+
if (error := set_json(tmpdir / args._data, data)):
|
486
503
|
error = f'Failed to write {version} data file: {error}'
|
487
504
|
|
488
|
-
if
|
505
|
+
if error:
|
506
|
+
shutil.rmtree(tmpdir)
|
507
|
+
else:
|
489
508
|
remove(args, version)
|
490
|
-
|
509
|
+
tmpdir.replace(vdir)
|
491
510
|
|
492
|
-
shutil.rmtree(tmpdir)
|
493
511
|
return error
|
494
512
|
|
495
513
|
def main() -> str | None:
|
@@ -497,8 +515,9 @@ def main() -> str | None:
|
|
497
515
|
distro_default = DISTRIBUTIONS.get((platform.system(), platform.machine()))
|
498
516
|
distro_help = distro_default or '?unknown?'
|
499
517
|
|
500
|
-
|
501
|
-
|
518
|
+
p = '/opt' if is_admin() else platformdirs.user_data_dir()
|
519
|
+
prefix_dir = str(Path(p, PROG))
|
520
|
+
cache_dir = platformdirs.user_cache_path() / PROG
|
502
521
|
|
503
522
|
# Parse arguments
|
504
523
|
opt = ArgumentParser(description=__doc__,
|
@@ -507,28 +526,28 @@ def main() -> str | None:
|
|
507
526
|
|
508
527
|
# Set up main/global arguments
|
509
528
|
opt.add_argument('-D', '--distribution',
|
510
|
-
help=f'{REPO}
|
511
|
-
'distribution, e.g. "x86_64-unknown-linux-gnu". '
|
529
|
+
help=f'{REPO} distribution. '
|
512
530
|
f'Default is auto-detected (detected as "{distro_help}" '
|
513
531
|
'for this current host).')
|
514
|
-
opt.add_argument('-
|
515
|
-
help=
|
516
|
-
'versions
|
517
|
-
opt.add_argument('-C', '--cache-
|
532
|
+
opt.add_argument('-P', '--prefix-dir', default=prefix_dir,
|
533
|
+
help='specify prefix dir for storing '
|
534
|
+
'versions. Default is "%(default)s"')
|
535
|
+
opt.add_argument('-C', '--cache-dir', default=str(cache_dir),
|
536
|
+
help='specify cache dir for downloads. '
|
537
|
+
'Default is "%(default)s"')
|
538
|
+
opt.add_argument('-M', '--cache-minutes', default=60, type=float,
|
518
539
|
help='cache latest YYYYMMDD release tag fetch for this '
|
519
540
|
'many minutes, before rechecking for latest. '
|
520
541
|
'Default is %(default)d minutes')
|
521
542
|
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')
|
543
|
+
help='cache YYYYMMDD release file lists and downloads for '
|
544
|
+
'this number of days after last version referencing that '
|
545
|
+
'release is removed. Default is %(default)d days')
|
525
546
|
opt.add_argument('--github-access-token',
|
526
547
|
help='optional Github access token. Can specify to reduce '
|
527
548
|
'rate limiting.')
|
528
549
|
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')
|
550
|
+
help='do strip downloaded binaries')
|
532
551
|
opt.add_argument('-V', '--version', action='store_true',
|
533
552
|
help=f'just show {PROG} version')
|
534
553
|
cmd = opt.add_subparsers(title='Commands', dest='cmdname')
|
@@ -583,20 +602,24 @@ def main() -> str | None:
|
|
583
602
|
'using -D/--distribution option.')
|
584
603
|
|
585
604
|
# Keep some useful info in the namespace passed to the command
|
586
|
-
|
605
|
+
prefix_dir = Path(args.prefix_dir).expanduser()
|
606
|
+
cache_dir = Path(args.cache_dir).expanduser()
|
587
607
|
|
588
608
|
args._distribution = distribution
|
589
609
|
args._data = f'{PROG}.json'
|
590
|
-
|
591
|
-
args.
|
592
|
-
args._versions = base_dir / 'versions'
|
610
|
+
|
611
|
+
args._versions = prefix_dir
|
593
612
|
args._versions.mkdir(parents=True, exist_ok=True)
|
594
|
-
|
613
|
+
|
614
|
+
args._downloads = cache_dir / 'downloads'
|
615
|
+
args._downloads.mkdir(parents=True, exist_ok=True)
|
616
|
+
args._releases = cache_dir / 'releases'
|
595
617
|
args._releases.mkdir(parents=True, exist_ok=True)
|
618
|
+
args._latest_release = cache_dir / 'latest_release'
|
596
619
|
|
597
620
|
result = args.func(args)
|
598
|
-
update_version_symlinks(args)
|
599
621
|
purge_unused_releases(args)
|
622
|
+
update_version_symlinks(args)
|
600
623
|
return result
|
601
624
|
|
602
625
|
@COMMAND.add
|
@@ -722,7 +745,7 @@ class _remove(COMMAND):
|
|
722
745
|
def run(args: Namespace) -> str | None:
|
723
746
|
release_del = args.release
|
724
747
|
if release_del and \
|
725
|
-
(err := check_release_tag(release_del
|
748
|
+
(err := check_release_tag(release_del)):
|
726
749
|
return err
|
727
750
|
|
728
751
|
for version in get_version_names(args):
|
@@ -802,7 +825,7 @@ class _show(COMMAND):
|
|
802
825
|
'Show versions available from a release.'
|
803
826
|
@staticmethod
|
804
827
|
def init(parser: ArgumentParser) -> None:
|
805
|
-
parser.add_argument('-
|
828
|
+
parser.add_argument('-a', '--all', action='store_true',
|
806
829
|
help='also show all available distributions for '
|
807
830
|
'each version from the release')
|
808
831
|
parser.add_argument('release', nargs='?',
|
@@ -829,7 +852,7 @@ class _show(COMMAND):
|
|
829
852
|
for distribution in files[version]:
|
830
853
|
app = ' (installed)' \
|
831
854
|
if distribution == installed_distribution else ''
|
832
|
-
if args.
|
855
|
+
if args.all or app \
|
833
856
|
or distribution == args._distribution:
|
834
857
|
if distribution == args._distribution:
|
835
858
|
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
|