pystand 1.6__py3-none-any.whl → 1.8__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pystand
3
- Version: 1.6
3
+ Version: 1.8
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
@@ -121,8 +121,8 @@ Type `pystand` or `pystand -h` to view the usage summary:
121
121
  ```
122
122
  usage: pystand [-h] [-D DISTRIBUTION] [-B BASE_DIR] [-C CACHE_MINUTES]
123
123
  [--purge-days PURGE_DAYS]
124
- [--github-access-token GITHUB_ACCESS_TOKEN] [--do-not-strip]
125
- [-V]
124
+ [--github-access-token GITHUB_ACCESS_TOKEN] [--no-strip]
125
+ [--no-extra-strip] [-V]
126
126
  {install,update,remove,list,show,path} ...
127
127
 
128
128
  Command line tool to download, install, and update pre-built Python versions
@@ -150,7 +150,8 @@ options:
150
150
  --github-access-token GITHUB_ACCESS_TOKEN
151
151
  optional Github access token. Can specify to reduce
152
152
  rate limiting.
153
- --do-not-strip Do not strip unneeded symbols from binaries
153
+ --no-strip do not use or create stripped binaries
154
+ --no-extra-strip do not restrip already stripped source binaries
154
155
  -V show pystand version
155
156
 
156
157
  Commands:
@@ -344,8 +345,9 @@ anything after on a line) are ignored. Type `pystand` to see all
344
345
  supported options.
345
346
 
346
347
  The global options: `--distribution`, `--base-dir`, `--cache-minutes`,
347
- `--purge-days`, `--github-access-token`, `--do-not-strip` are the only
348
- sensible candidates to consider setting as defaults.
348
+ `--purge-days`, `--github-access-token`, `--no-strip`,
349
+ `--no-extra-strip` are the only sensible candidates to consider setting
350
+ as defaults.
349
351
 
350
352
  ## Command Line Tab Completion
351
353
 
@@ -0,0 +1,6 @@
1
+ pystand.py,sha256=0yqrw_cnYnidYkCOCP5rDy-UgDZv9klpFURHxFANC5I,30695
2
+ pystand-1.8.dist-info/METADATA,sha256=KqUUnga3dbLKMyqmttZk-YLLt-10nVIoNvpDfTR7mcc,15105
3
+ pystand-1.8.dist-info/WHEEL,sha256=Wyh-_nZ0DJYolHNn1_hMa4lM7uDedD_RGVwbmTjyItk,91
4
+ pystand-1.8.dist-info/entry_points.txt,sha256=DG4ps3I3nni1bubV1tXs6u8FARgkdbAYaEAzZD4RAo8,41
5
+ pystand-1.8.dist-info/top_level.txt,sha256=NoWUh19UQymAJLHTCdxMnVwV6Teftef5fzyF3OWLyNY,8
6
+ pystand-1.8.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (70.3.0)
2
+ Generator: setuptools (71.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
pystand.py CHANGED
@@ -40,6 +40,8 @@ FIRST_RELEASE = '20210724'
40
40
  # Sample release tag for documentation/usage examples
41
41
  SAMPL_RELEASE = '20240415'
42
42
 
43
+ STRIP_EXT = '_stripped'
44
+
43
45
  # The following regexp pattern is used to match the end of a release file name
44
46
  ENDPAT = r'-install_only[-\dT]*.tar.gz$'
45
47
 
@@ -234,14 +236,22 @@ def get_release_tag(args: Namespace) -> str:
234
236
  args._latest_release.write_text(tag + '\n')
235
237
  return tag
236
238
 
237
- def get_release_name(filename: str) -> Optional[str]:
238
- 'Search for and strip the release name from the file name'
239
- if not (m := re.search(ENDPAT, filename)):
240
- return None
241
-
242
- # Strip of end of file name and also strip any +8 digit embedded
243
- # release date
244
- return re.sub(r'\+\d{8}', '', filename[:m.start()])
239
+ def merge(files: dict, name: str, url: str, stripped: bool) -> None:
240
+ 'Merge a file into the files dict'
241
+ # We store: A string url when there is no stripped version; or a
242
+ # tuple of string urls, the first unstripped and the second
243
+ # stripped. It's a bit messy but we are keeping compatibility with
244
+ # earlier releases (and their cached release files) which did not
245
+ # have stripped versions.
246
+ impl, ver, distrib = name.split('-', maxsplit=2)
247
+ if impl not in files:
248
+ files[impl] = defaultdict(dict)
249
+
250
+ if exist := files[impl].get(ver, {}).get(distrib, {}):
251
+ files[impl][ver][distrib] = (exist, url) \
252
+ if stripped else (url, exist[1])
253
+ else:
254
+ files[impl][ver][distrib] = ('', url) if stripped else url
245
255
 
246
256
  def get_release_files(args, tag, implementation: Optional[str] = None) -> dict:
247
257
  'Return the release files for the given tag'
@@ -260,17 +270,19 @@ def get_release_files(args, tag, implementation: Optional[str] = None) -> dict:
260
270
  except Exception:
261
271
  return {}
262
272
 
263
- # Iterate over the release assets and store the files in a dict to
264
- # return
265
- for file in release.get_assets():
266
- if not (name := get_release_name(file.name)):
267
- continue
273
+ # Iterate over the release assets and store pertinent files in a
274
+ # dict to return.
275
+ for asset in release.get_assets():
276
+ name = asset.name
277
+ if is_stripped := (STRIP_EXT in name):
278
+ name = name.replace(STRIP_EXT, '')
268
279
 
269
- impl, ver, rest = name.split('-', maxsplit=2)
270
- if impl not in files:
271
- files[impl] = defaultdict(dict)
280
+ if m := re.search(ENDPAT, name):
281
+ name = re.sub(r'\+\d{8}', '', name[:m.start()])
282
+ merge(files, name, asset.browser_download_url, is_stripped)
272
283
 
273
- files[impl][ver][rest] = file.browser_download_url
284
+ if not files:
285
+ sys.exit(f'Failed to fetch any files for release {tag}')
274
286
 
275
287
  if error := set_json(jfile, files):
276
288
  sys.exit(f'Failed to write release {tag} file {jfile}: {error}')
@@ -309,13 +321,13 @@ def update_version_symlinks(args: Namespace) -> None:
309
321
  for name, tgt in oldlinks.items():
310
322
  new_tgt = newlinks.get(name)
311
323
  if not new_tgt or new_tgt != tgt:
312
- Path(base / name).unlink()
324
+ (base / name).unlink()
313
325
 
314
326
  # Create all needed new links
315
327
  for name, tgt in newlinks.items():
316
328
  old_tgt = oldlinks.get(name)
317
329
  if not old_tgt or old_tgt != tgt:
318
- Path(base / name).symlink_to(tgt, target_is_directory=True)
330
+ (base / name).symlink_to(tgt, target_is_directory=True)
319
331
 
320
332
  def purge_unused_releases(args: Namespace) -> None:
321
333
  'Purge old releases that are no longer needed and have expired'
@@ -368,34 +380,46 @@ def remove(args: Namespace, version: str) -> None:
368
380
 
369
381
  shutil.rmtree(vdir)
370
382
 
371
- def strip_binaries(vdir: Path, distribution: str) -> None:
383
+ def strip_binaries(vdir: Path, distribution: str) -> bool:
372
384
  'Strip binaries from files in a version directory'
373
385
  # Only run the strip command on Linux hosts and for Linux distributions
374
- if platform.system() != 'Linux' or '-linux-' not in distribution:
375
- return
386
+ was_stripped = False
387
+ if platform.system() == 'Linux' and '-linux-' in distribution:
388
+ for path in ('bin', 'lib'):
389
+ base = vdir / path
390
+ if not base.is_dir():
391
+ continue
376
392
 
377
- for path in ('bin', 'lib'):
378
- base = vdir / path
379
- if not base.is_dir():
380
- continue
393
+ for file in base.iterdir():
394
+ if not file.is_symlink() and file.is_file():
395
+ cmd = f'strip -p --strip-unneeded {file}'.split()
396
+ try:
397
+ subprocess.run(cmd, stderr=subprocess.DEVNULL)
398
+ except Exception:
399
+ pass
400
+ else:
401
+ was_stripped = True
381
402
 
382
- for file in base.iterdir():
383
- if not file.is_symlink() and file.is_file():
384
- cmd = f'strip -p --strip-unneeded {file}'.split()
385
- try:
386
- subprocess.run(cmd, stderr=subprocess.DEVNULL)
387
- except Exception:
388
- pass
403
+ return was_stripped
389
404
 
390
405
  def install(args: Namespace, vdir: Path, release: str, distribution: str,
391
406
  files: dict) -> Optional[str]:
392
407
  'Install a version'
393
408
  version = vdir.name
394
409
 
395
- if not (file := files[version].get(distribution)):
410
+ if not (fileurl := files[version].get(distribution)):
396
411
  return f'Arch "{distribution}" not found for release '\
397
412
  f'{release} version {version}.'
398
413
 
414
+ if isinstance(fileurl, str):
415
+ stripped = False
416
+ else:
417
+ stripped = not args.no_strip
418
+ if not (fileurl := fileurl[stripped]):
419
+ desc = 'stripped' if stripped else 'unstripped'
420
+ return f'Arch {desc} "{distribution}" not found for release '\
421
+ f'{release} version {version}.'
422
+
399
423
  tmpdir = args._versions / f'.{version}-tmp'
400
424
  rm_path(tmpdir)
401
425
  tmpdir.mkdir()
@@ -403,19 +427,32 @@ def install(args: Namespace, vdir: Path, release: str, distribution: str,
403
427
  error = None
404
428
 
405
429
  try:
406
- urllib.request.urlretrieve(file, tmpdir / 'tmp.tar.gz')
430
+ urllib.request.urlretrieve(fileurl, tmpdir / 'tmp.tar.gz')
431
+ except Exception as e:
432
+ error = f'Failed to fetch "{fileurl}": {e}'
433
+
434
+ try:
407
435
  shutil.unpack_archive(tmpdir / 'tmp.tar.gz', tmpdir)
408
436
  except Exception as e:
409
- error = f'Failed to fetch "{version}": {e}'
437
+ error = f'Failed to unpack "{fileurl}": {e}'
410
438
 
411
439
  if not error:
412
440
  data = {'release': release, 'distribution': distribution}
441
+
442
+ if args.no_strip:
443
+ data['stripped'] = 'forced off'
444
+ elif stripped and args.no_extra_strip:
445
+ data['stripped'] = 'at_source_only'
446
+ else:
447
+ if strip_binaries(tmpdir_py, distribution):
448
+ data['stripped'] = 'at_source_and_manually' \
449
+ if stripped else 'manually'
450
+ else:
451
+ data['stripped'] = 'at_source' if stripped else 'n/a'
452
+
413
453
  if (error := set_json(tmpdir_py / args._data, data)):
414
454
  error = f'Failed to write {version} data file: {error}'
415
455
 
416
- if not args.do_not_strip:
417
- strip_binaries(tmpdir_py, distribution)
418
-
419
456
  if not error:
420
457
  remove(args, version)
421
458
  tmpdir_py.replace(vdir)
@@ -456,8 +493,10 @@ def main() -> Optional[str]:
456
493
  opt.add_argument('--github-access-token',
457
494
  help='optional Github access token. Can specify to reduce '
458
495
  'rate limiting.')
459
- opt.add_argument('--do-not-strip', action='store_true',
460
- help='Do not strip unneeded symbols from binaries')
496
+ opt.add_argument('--no-strip', action='store_true',
497
+ help='do not use or create stripped binaries')
498
+ opt.add_argument('--no-extra-strip', action='store_true',
499
+ help='do not restrip already stripped source binaries')
461
500
  opt.add_argument('-V', action='store_true',
462
501
  help=f'show {PROG} version')
463
502
  cmd = opt.add_subparsers(title='Commands', dest='cmdname')
@@ -1,6 +0,0 @@
1
- pystand.py,sha256=STdf_mHj76RKAAiMIwh2nsHh_697HuqvIa3--V9LBA0,29038
2
- pystand-1.6.dist-info/METADATA,sha256=3fsgG6ZB_2G8UkGYkjawAYIKMC_HFiu-COc32QB1fz4,15007
3
- pystand-1.6.dist-info/WHEEL,sha256=Z4pYXqR_rTB7OWNDYFOm1qRk0RX6GFP2o8LgvP453Hk,91
4
- pystand-1.6.dist-info/entry_points.txt,sha256=DG4ps3I3nni1bubV1tXs6u8FARgkdbAYaEAzZD4RAo8,41
5
- pystand-1.6.dist-info/top_level.txt,sha256=NoWUh19UQymAJLHTCdxMnVwV6Teftef5fzyF3OWLyNY,8
6
- pystand-1.6.dist-info/RECORD,,