relenv 0.20.6__tar.gz → 0.20.7__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.
Files changed (42) hide show
  1. {relenv-0.20.6/relenv.egg-info → relenv-0.20.7}/PKG-INFO +1 -1
  2. {relenv-0.20.6 → relenv-0.20.7}/relenv/build/__init__.py +2 -1
  3. {relenv-0.20.6 → relenv-0.20.7}/relenv/build/common.py +1 -0
  4. {relenv-0.20.6 → relenv-0.20.7}/relenv/build/darwin.py +4 -5
  5. {relenv-0.20.6 → relenv-0.20.7}/relenv/build/linux.py +48 -22
  6. {relenv-0.20.6 → relenv-0.20.7}/relenv/build/windows.py +1 -0
  7. {relenv-0.20.6 → relenv-0.20.7}/relenv/common.py +106 -2
  8. {relenv-0.20.6 → relenv-0.20.7}/relenv/pyversions.py +40 -104
  9. {relenv-0.20.6 → relenv-0.20.7/relenv.egg-info}/PKG-INFO +1 -1
  10. {relenv-0.20.6 → relenv-0.20.7}/tests/test_common.py +4 -0
  11. {relenv-0.20.6 → relenv-0.20.7}/tests/test_verify_build.py +94 -5
  12. {relenv-0.20.6 → relenv-0.20.7}/LICENSE.md +0 -0
  13. {relenv-0.20.6 → relenv-0.20.7}/MANIFEST.in +0 -0
  14. {relenv-0.20.6 → relenv-0.20.7}/NOTICE +0 -0
  15. {relenv-0.20.6 → relenv-0.20.7}/README.md +0 -0
  16. {relenv-0.20.6 → relenv-0.20.7}/pyproject.toml +0 -0
  17. {relenv-0.20.6 → relenv-0.20.7}/relenv/__init__.py +0 -0
  18. {relenv-0.20.6 → relenv-0.20.7}/relenv/__main__.py +0 -0
  19. {relenv-0.20.6 → relenv-0.20.7}/relenv/_scripts/install_vc_build.ps1 +0 -0
  20. {relenv-0.20.6 → relenv-0.20.7}/relenv/buildenv.py +0 -0
  21. {relenv-0.20.6 → relenv-0.20.7}/relenv/check.py +0 -0
  22. {relenv-0.20.6 → relenv-0.20.7}/relenv/create.py +0 -0
  23. {relenv-0.20.6 → relenv-0.20.7}/relenv/fetch.py +0 -0
  24. {relenv-0.20.6 → relenv-0.20.7}/relenv/manifest.py +0 -0
  25. {relenv-0.20.6 → relenv-0.20.7}/relenv/relocate.py +0 -0
  26. {relenv-0.20.6 → relenv-0.20.7}/relenv/runtime.py +0 -0
  27. {relenv-0.20.6 → relenv-0.20.7}/relenv/toolchain.py +0 -0
  28. {relenv-0.20.6 → relenv-0.20.7}/relenv.egg-info/SOURCES.txt +0 -0
  29. {relenv-0.20.6 → relenv-0.20.7}/relenv.egg-info/dependency_links.txt +0 -0
  30. {relenv-0.20.6 → relenv-0.20.7}/relenv.egg-info/entry_points.txt +0 -0
  31. {relenv-0.20.6 → relenv-0.20.7}/relenv.egg-info/requires.txt +0 -0
  32. {relenv-0.20.6 → relenv-0.20.7}/relenv.egg-info/top_level.txt +0 -0
  33. {relenv-0.20.6 → relenv-0.20.7}/setup.cfg +0 -0
  34. {relenv-0.20.6 → relenv-0.20.7}/setup.py +0 -0
  35. {relenv-0.20.6 → relenv-0.20.7}/tests/__init__.py +0 -0
  36. {relenv-0.20.6 → relenv-0.20.7}/tests/conftest.py +0 -0
  37. {relenv-0.20.6 → relenv-0.20.7}/tests/test_build.py +0 -0
  38. {relenv-0.20.6 → relenv-0.20.7}/tests/test_create.py +0 -0
  39. {relenv-0.20.6 → relenv-0.20.7}/tests/test_downloads.py +0 -0
  40. {relenv-0.20.6 → relenv-0.20.7}/tests/test_fips_photon.py +0 -0
  41. {relenv-0.20.6 → relenv-0.20.7}/tests/test_relocate.py +0 -0
  42. {relenv-0.20.6 → relenv-0.20.7}/tests/test_runtime.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: relenv
3
- Version: 0.20.6
3
+ Version: 0.20.7
4
4
  Project-URL: Source Code, https://github.com/saltstack/relative-environment-for-python
5
5
  Project-URL: Documentation, https://relenv.readthedocs.io/en/latest/
6
6
  Project-URL: Changelog, https://relenv.readthedocs.io/en/latest/changelog.html
@@ -152,7 +152,7 @@ def main(args):
152
152
  build_version = requested
153
153
  else:
154
154
  pyversions = python_versions(args.python)
155
- build_version = pyversions[0]
155
+ build_version = pyversions.keys()[0]
156
156
 
157
157
  # print(pyversions)
158
158
  # print(pyversions[0].major)
@@ -168,6 +168,7 @@ def main(args):
168
168
  build.version = str(build_version)
169
169
  build.dirs.version = str(build_version)
170
170
  build.recipies["python"]["download"].version = str(build_version)
171
+ build.recipies["python"]["download"].checksum = pyversions[build_version]
171
172
 
172
173
  if args.check_versions:
173
174
  if not CHECK_VERSIONS_SUPPORT:
@@ -535,6 +535,7 @@ class Download:
535
535
  if self.fallback_url:
536
536
  print(f"Download failed {self.url} ({exc}); trying fallback url")
537
537
  return download_url(self.fallback_url, self.destination, CICD), True
538
+ raise
538
539
 
539
540
  def fetch_signature(self, version):
540
541
  """
@@ -28,7 +28,6 @@ def populate_env(env, dirs):
28
28
  env["LDFLAGS"] = " ".join(ldflags).format(prefix=dirs.prefix)
29
29
  env["MACOSX_DEPLOYMENT_TARGET"] = MACOS_DEVELOPMENT_TARGET
30
30
  cflags = [
31
- "-L{prefix}/lib",
32
31
  "-I{prefix}/include",
33
32
  "-I{prefix}/include/readline",
34
33
  ]
@@ -79,8 +78,8 @@ build.add(
79
78
  build_func=build_openssl,
80
79
  download={
81
80
  "url": "https://github.com/openssl/openssl/releases/download/openssl-{version}/openssl-{version}.tar.gz",
82
- "version": "3.2.4",
83
- "checksum": "2247802a1193c0f8eb41c870e8de45a2241422d5",
81
+ "version": "3.5.4",
82
+ "checksum": "b75daac8e10f189abe28a076ba5905d363e4801f",
84
83
  },
85
84
  )
86
85
 
@@ -88,8 +87,8 @@ build.add(
88
87
  "XZ",
89
88
  download={
90
89
  "url": "http://tukaani.org/xz/xz-{version}.tar.gz",
91
- "version": "5.6.2",
92
- "checksum": "0d6b10e4628fe08e19293c65e8dbcaade084a083",
90
+ "version": "5.8.1",
91
+ "checksum": "ed4d5589c4cfe84e1697bd02a9954b76af336931",
93
92
  },
94
93
  )
95
94
 
@@ -6,7 +6,7 @@ The linux build process.
6
6
  import pathlib
7
7
  import tempfile
8
8
  from .common import *
9
- from ..common import arches, LINUX
9
+ from ..common import arches, LINUX, Version
10
10
 
11
11
 
12
12
  ARCHES = arches[LINUX]
@@ -393,6 +393,43 @@ def build_python(env, dirs, logfp):
393
393
  env["OPENSSL_LDFLAGS"] = f"-L{dirs.prefix}/lib"
394
394
  env["CFLAGS"] = f"-Wno-coverage-mismatch {env['CFLAGS']}"
395
395
 
396
+ runcmd(
397
+ [
398
+ "sed",
399
+ "-i",
400
+ "s/#readline readline.c -lreadline -ltermcap/readline readline.c -lreadline -ltinfow/g",
401
+ "Modules/Setup",
402
+ ]
403
+ )
404
+ if Version.parse_string(env["RELENV_PY_MAJOR_VERSION"]) <= Version.parse_string(
405
+ "3.10"
406
+ ):
407
+ runcmd(
408
+ [
409
+ "sed",
410
+ "-i",
411
+ (
412
+ "s/#_curses -lncurses -lncursesw -ltermcap _cursesmodule.c"
413
+ "/_curses -lncursesw -ltinfow _cursesmodule.c/g"
414
+ ),
415
+ "Modules/Setup",
416
+ ]
417
+ )
418
+ runcmd(
419
+ [
420
+ "sed",
421
+ "-i",
422
+ (
423
+ "s/#_curses_panel _curses_panel.c -lpanel -lncurses"
424
+ "/_curses_panel _curses_panel.c -lpanelw -lncursesw/g"
425
+ ),
426
+ "Modules/Setup",
427
+ ]
428
+ )
429
+ else:
430
+ env["CURSES_LIBS"] = "-lncursesw -ltinfow"
431
+ env["PANEL_LIBS"] = "-lpanelw"
432
+
396
433
  cmd = [
397
434
  "./configure",
398
435
  "-v",
@@ -421,14 +458,7 @@ def build_python(env, dirs, logfp):
421
458
  ]
422
459
 
423
460
  runcmd(cmd, env=env, stderr=logfp, stdout=logfp)
424
- runcmd(
425
- [
426
- "sed",
427
- "-i",
428
- "s/#readline readline.c -lreadline -ltermcap/readline readline.c -lreadline -ltinfow/g",
429
- "Modules/Setup",
430
- ]
431
- )
461
+
432
462
  with io.open("Modules/Setup", "a+") as fp:
433
463
  fp.seek(0, io.SEEK_END)
434
464
  fp.write("*disabled*\n" "_tkinter\n" "nsl\n" "nis\n")
@@ -452,14 +482,13 @@ build.add(
452
482
  build_func=build_openssl,
453
483
  download={
454
484
  "url": "https://github.com/openssl/openssl/releases/download/openssl-{version}/openssl-{version}.tar.gz",
455
- "version": "3.2.4",
456
- "checksum": "2247802a1193c0f8eb41c870e8de45a2241422d5",
485
+ "version": "3.5.4",
486
+ "checksum": "b75daac8e10f189abe28a076ba5905d363e4801f",
457
487
  "checkfunc": tarball_version,
458
488
  "checkurl": "https://www.openssl.org/source/",
459
489
  },
460
490
  )
461
491
 
462
-
463
492
  build.add(
464
493
  "openssl-fips-module",
465
494
  build_func=build_openssl_fips,
@@ -489,8 +518,8 @@ build.add(
489
518
  "XZ",
490
519
  download={
491
520
  "url": "http://tukaani.org/xz/xz-{version}.tar.gz",
492
- "version": "5.6.2",
493
- "checksum": "0d6b10e4628fe08e19293c65e8dbcaade084a083",
521
+ "version": "5.8.1",
522
+ "checksum": "ed4d5589c4cfe84e1697bd02a9954b76af336931",
494
523
  "checkfunc": tarball_version,
495
524
  },
496
525
  )
@@ -522,7 +551,7 @@ build.add(
522
551
  name="gdbm",
523
552
  build_func=build_gdbm,
524
553
  download={
525
- "url": "https://ftp.gnu.org/gnu/gdbm/gdbm-{version}.tar.gz",
554
+ "url": "https://mirrors.ocf.berkeley.edu/gnu/gdbm/gdbm-{version}.tar.gz",
526
555
  "version": "1.26",
527
556
  "checksum": "6cee3657de948e691e8df26509157be950cef4d4",
528
557
  "checkfunc": tarball_version,
@@ -533,12 +562,9 @@ build.add(
533
562
  name="ncurses",
534
563
  build_func=build_ncurses,
535
564
  download={
536
- "url": "https://ftp.gnu.org/pub/gnu/ncurses/ncurses-{version}.tar.gz",
537
- # XXX: Need to work out tinfo linkage
538
- # "version": "6.5",
539
- # "checksum": "cde3024ac3f9ef21eaed6f001476ea8fffcaa381",
540
- "version": "6.4",
541
- "checksum": "bb5eb3f34b3ecd5bac8d0b58164b847f135b3d62",
565
+ "url": "https://mirrors.ocf.berkeley.edu/gnu/ncurses/ncurses-{version}.tar.gz",
566
+ "version": "6.5",
567
+ "checksum": "cde3024ac3f9ef21eaed6f001476ea8fffcaa381",
542
568
  "checkfunc": tarball_version,
543
569
  },
544
570
  )
@@ -594,7 +620,7 @@ build.add(
594
620
  build_func=build_readline,
595
621
  wait_on=["ncurses"],
596
622
  download={
597
- "url": "https://ftp.gnu.org/gnu/readline/readline-{version}.tar.gz",
623
+ "url": "https://mirrors.ocf.berkeley.edu/gnu/readline/readline-{version}.tar.gz",
598
624
  "version": "8.3",
599
625
  "checksum": "2c05ae9350b695f69d70b47f17f092611de2081f",
600
626
  "checkfunc": tarball_version,
@@ -91,6 +91,7 @@ def build_python(env, dirs, logfp):
91
91
  "3.11",
92
92
  ]:
93
93
  override_dependency(dirs.source, r"sqlite-\d+.\d+.\d+.\d+", "sqlite-3.50.4.0")
94
+ override_dependency(dirs.source, r"xz-\d+.\d+.\d+", "xz-5.6.2")
94
95
 
95
96
  arch_to_plat = {
96
97
  "amd64": "x64",
@@ -18,7 +18,7 @@ import threading
18
18
  import time
19
19
 
20
20
  # relenv package version
21
- __version__ = "0.20.6"
21
+ __version__ = "0.20.7"
22
22
 
23
23
  MODULE_DIR = pathlib.Path(__file__).resolve().parent
24
24
 
@@ -105,7 +105,7 @@ def format_shebang(python, tpl=SHEBANG_TPL):
105
105
  """
106
106
  Return a formatted shebang.
107
107
  """
108
- return tpl.format(python).strip()
108
+ return tpl.format(python).strip() + "\n"
109
109
 
110
110
 
111
111
  def build_arch():
@@ -727,3 +727,107 @@ def sanitize_sys_path(sys_path_entries):
727
727
  if p not in __sys_path:
728
728
  __sys_path.append(p)
729
729
  return __sys_path
730
+
731
+
732
+ class Version:
733
+ """
734
+ Version comparisons.
735
+ """
736
+
737
+ def __init__(self, data):
738
+ self.major, self.minor, self.micro = self.parse_string(data)
739
+ self._data = data
740
+
741
+ def __str__(self):
742
+ """
743
+ Version as string.
744
+ """
745
+ _ = f"{self.major}"
746
+ if self.minor is not None:
747
+ _ += f".{self.minor}"
748
+ if self.micro is not None:
749
+ _ += f".{self.micro}"
750
+ # XXX What if minor was None but micro was an int.
751
+ return _
752
+
753
+ def __hash__(self):
754
+ """
755
+ Hash of the version.
756
+
757
+ Hash the major, minor, and micro attributes.
758
+ """
759
+ return hash((self.major, self.minor, self.micro))
760
+
761
+ @staticmethod
762
+ def parse_string(data):
763
+ """
764
+ Parse a version string into major, minor, and micro integers.
765
+ """
766
+ parts = data.split(".")
767
+ if len(parts) == 1:
768
+ return int(parts[0]), None, None
769
+ elif len(parts) == 2:
770
+ return int(parts[0]), int(parts[1]), None
771
+ elif len(parts) == 3:
772
+ return int(parts[0]), int(parts[1]), int(parts[2])
773
+ else:
774
+ raise RuntimeError("Too many parts to parse")
775
+
776
+ def __eq__(self, other):
777
+ """
778
+ Equality comparisons.
779
+ """
780
+ mymajor = 0 if self.major is None else self.major
781
+ myminor = 0 if self.minor is None else self.minor
782
+ mymicro = 0 if self.micro is None else self.micro
783
+ major = 0 if other.major is None else other.major
784
+ minor = 0 if other.minor is None else other.minor
785
+ micro = 0 if other.micro is None else other.micro
786
+ return mymajor == major and myminor == minor and mymicro == micro
787
+
788
+ def __lt__(self, other):
789
+ """
790
+ Less than comparrison.
791
+ """
792
+ mymajor = 0 if self.major is None else self.major
793
+ myminor = 0 if self.minor is None else self.minor
794
+ mymicro = 0 if self.micro is None else self.micro
795
+ major = 0 if other.major is None else other.major
796
+ minor = 0 if other.minor is None else other.minor
797
+ micro = 0 if other.micro is None else other.micro
798
+ if mymajor < major:
799
+ return True
800
+ elif mymajor == major:
801
+ if myminor < minor:
802
+ return True
803
+ if myminor == minor and mymicro < micro:
804
+ return True
805
+ return False
806
+
807
+ def __le__(self, other):
808
+ """
809
+ Less than or equal to comparrison.
810
+ """
811
+ mymajor = 0 if self.major is None else self.major
812
+ myminor = 0 if self.minor is None else self.minor
813
+ mymicro = 0 if self.micro is None else self.micro
814
+ major = 0 if other.major is None else other.major
815
+ minor = 0 if other.minor is None else other.minor
816
+ micro = 0 if other.micro is None else other.micro
817
+ if mymajor <= major:
818
+ if myminor <= minor:
819
+ if mymicro <= micro:
820
+ return True
821
+ return False
822
+
823
+ def __gt__(self, other):
824
+ """
825
+ Greater than comparrison.
826
+ """
827
+ return not self.__le__(other)
828
+
829
+ def __ge__(self, other):
830
+ """
831
+ Greater than or equal to comparrison.
832
+ """
833
+ return not self.__lt__(other)
@@ -21,7 +21,7 @@ import subprocess
21
21
  import sys
22
22
  import time
23
23
 
24
- from relenv.common import check_url, download_url, fetch_url_content
24
+ from relenv.common import Version, check_url, download_url, fetch_url_content
25
25
 
26
26
  log = logging.getLogger(__name__)
27
27
 
@@ -43,102 +43,6 @@ def _ref_path(x):
43
43
  return x.split('href="')[1].split('"')[0]
44
44
 
45
45
 
46
- class Version:
47
- """
48
- Version comparrisons.
49
- """
50
-
51
- def __init__(self, data):
52
- self.major, self.minor, self.micro = self.parse_string(data)
53
- self._data = data
54
-
55
- def __str__(self):
56
- """
57
- Version as string.
58
- """
59
- _ = f"{self.major}"
60
- if self.minor is not None:
61
- _ += f".{self.minor}"
62
- if self.micro is not None:
63
- _ += f".{self.micro}"
64
- # XXX What if minor was None but micro was an int.
65
- return _
66
-
67
- @staticmethod
68
- def parse_string(data):
69
- """
70
- Parse a version string into major, minor, and micro integers.
71
- """
72
- parts = data.split(".")
73
- if len(parts) == 1:
74
- return int(parts[0]), None, None
75
- elif len(parts) == 2:
76
- return int(parts[0]), int(parts[1]), None
77
- elif len(parts) == 3:
78
- return int(parts[0]), int(parts[1]), int(parts[2])
79
- else:
80
- raise RuntimeError("Too many parts to parse")
81
-
82
- def __eq__(self, other):
83
- """
84
- Equality comparrison.
85
- """
86
- mymajor = 0 if self.major is None else self.major
87
- myminor = 0 if self.minor is None else self.minor
88
- mymicro = 0 if self.micro is None else self.micro
89
- major = 0 if other.major is None else other.major
90
- minor = 0 if other.minor is None else other.minor
91
- micro = 0 if other.micro is None else other.micro
92
- return mymajor == major and myminor == minor and mymicro == micro
93
-
94
- def __lt__(self, other):
95
- """
96
- Less than comparrison.
97
- """
98
- mymajor = 0 if self.major is None else self.major
99
- myminor = 0 if self.minor is None else self.minor
100
- mymicro = 0 if self.micro is None else self.micro
101
- major = 0 if other.major is None else other.major
102
- minor = 0 if other.minor is None else other.minor
103
- micro = 0 if other.micro is None else other.micro
104
- if mymajor < major:
105
- return True
106
- elif mymajor == major:
107
- if myminor < minor:
108
- return True
109
- if myminor == minor and mymicro < micro:
110
- return True
111
- return False
112
-
113
- def __le__(self, other):
114
- """
115
- Less than or equal to comparrison.
116
- """
117
- mymajor = 0 if self.major is None else self.major
118
- myminor = 0 if self.minor is None else self.minor
119
- mymicro = 0 if self.micro is None else self.micro
120
- major = 0 if other.major is None else other.major
121
- minor = 0 if other.minor is None else other.minor
122
- micro = 0 if other.micro is None else other.micro
123
- if mymajor <= major:
124
- if myminor <= minor:
125
- if mymicro <= micro:
126
- return True
127
- return False
128
-
129
- def __gt__(self, other):
130
- """
131
- Greater than comparrison.
132
- """
133
- return not self.__le__(other)
134
-
135
- def __ge__(self, other):
136
- """
137
- Greater than or equal to comparrison.
138
- """
139
- return not self.__lt__(other)
140
-
141
-
142
46
  def _release_urls(version, gzip=False):
143
47
  if gzip:
144
48
  tarball = f"https://www.python.org/ftp/python/{version}/Python-{version}.tgz"
@@ -206,7 +110,7 @@ def digest(file):
206
110
  """
207
111
  SHA-256 digest of file.
208
112
  """
209
- hsh = hashlib.sha256()
113
+ hsh = hashlib.sha1()
210
114
  with open(file, "rb") as fp:
211
115
  hsh.update(fp.read())
212
116
  return hsh.hexdigest()
@@ -301,9 +205,9 @@ def _main():
301
205
  out[str(version)] = {url: digest(path)}
302
206
 
303
207
  if PRINT:
304
- vfile.write_text(json.dumps(pyversions))
208
+ vfile.write_text(json.dumps(pyversions, indent=1))
305
209
  elif not CHECK and out:
306
- vfile.write_text(json.dumps(out))
210
+ vfile.write_text(json.dumps(out, indent=1))
307
211
 
308
212
 
309
213
  def create_pyversions(path):
@@ -313,9 +217,41 @@ def create_pyversions(path):
313
217
  url = "https://www.python.org/downloads/"
314
218
  content = fetch_url_content(url)
315
219
  matched = re.findall(r'<a href="/downloads/.*">Python.*</a>', content)
220
+ cwd = os.getcwd()
316
221
  parsed_versions = sorted([_ref_version(_) for _ in matched], reverse=True)
317
222
  versions = [_ for _ in parsed_versions if _.major >= 3]
318
- path.write_text(json.dumps({"versions": [str(_) for _ in versions]}))
223
+
224
+ if path.exists():
225
+ data = json.loads(path.read_text())
226
+ else:
227
+ data = {}
228
+
229
+ for version in versions:
230
+
231
+ if str(version) in data:
232
+ continue
233
+
234
+ if version <= Version("3.2") and version.micro == 0:
235
+ url_version = Version(f"{version.major}.{version.minor}")
236
+ else:
237
+ url_version = version
238
+ if version >= Version("3.1.4"):
239
+ url = ARCHIVE.format(version=url_version, ext="tar.xz")
240
+ else:
241
+ url = ARCHIVE.format(version=url_version, ext="tgz")
242
+ download_path = download_url(url, cwd)
243
+ sig_path = download_url(f"{url}.asc", cwd)
244
+ verified = verify_signature(download_path, sig_path)
245
+ if verified:
246
+ print(f"Version {version} has digest {digest(download_path)}")
247
+ data[str(version)] = digest(download_path)
248
+ else:
249
+ raise Exception("Signature failed to verify: {url}")
250
+
251
+ path.write_text(json.dumps(data, indent=1))
252
+
253
+ # path.write_text(json.dumps({"versions": [str(_) for _ in versions]}))
254
+ path.write_text(json.dumps(data, indent=1))
319
255
 
320
256
 
321
257
  def python_versions(minor=None, create=False, update=False):
@@ -337,11 +273,11 @@ def python_versions(minor=None, create=False, update=False):
337
273
  else:
338
274
  raise RuntimeError("No versions file found")
339
275
  pyversions = json.loads(readfrom.read_text())
340
- versions = [Version(_) for _ in pyversions["versions"]]
276
+ versions = [Version(_) for _ in pyversions]
341
277
  if minor:
342
278
  mv = Version(minor)
343
279
  versions = [_ for _ in versions if _.major == mv.major and _.minor == mv.minor]
344
- return versions
280
+ return {_: pyversions[str(_)] for _ in versions}
345
281
 
346
282
 
347
283
  def setup_parser(subparsers):
@@ -402,7 +338,7 @@ def main(args):
402
338
  if not pyversions:
403
339
  print(f"Unknown minor version {requested}")
404
340
  sys.exit(1)
405
- build_version = pyversions[0]
341
+ build_version = list(pyversions.keys())[0]
406
342
  print(build_version)
407
343
  sys.exit()
408
344
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: relenv
3
- Version: 0.20.6
3
+ Version: 0.20.7
4
4
  Project-URL: Source Code, https://github.com/saltstack/relative-environment-for-python
5
5
  Project-URL: Documentation, https://relenv.readthedocs.io/en/latest/
6
6
  Project-URL: Changelog, https://relenv.readthedocs.io/en/latest/changelog.html
@@ -179,6 +179,10 @@ def test_shebang_tpl_macos():
179
179
  assert proc.returncode == 0
180
180
 
181
181
 
182
+ def test_format_shebang_newline():
183
+ assert format_shebang("python3", SHEBANG_TPL_LINUX).endswith("\n")
184
+
185
+
182
186
  def test_relative_interpreter_default_location():
183
187
  assert relative_interpreter(
184
188
  "/tmp/relenv", "/tmp/relenv/bin", "/tmp/relenv/bin/python3"
@@ -665,20 +665,75 @@ def test_pip_install_m2crypto_system_ssl(pipexec, pyexec):
665
665
  assert p.returncode == 0, p.stderr
666
666
 
667
667
 
668
+ SSLVERSION = """
669
+ import ctypes
670
+ import ctypes.util
671
+ import platform
672
+
673
+ def get_openssl_version():
674
+ '''
675
+ Programmatically discovers the OpenSSL version using ctypes.
676
+ '''
677
+ # Determine the library name based on the operating system
678
+ if platform.system() == "Windows":
679
+ lib_name = ctypes.util.find_library("libcrypto-3") or ctypes.util.find_library("libcrypto-1_1")
680
+ else:
681
+ lib_name = ctypes.util.find_library("crypto")
682
+
683
+ if not lib_name:
684
+ print("Could not find OpenSSL libcrypto library.")
685
+ return None, None
686
+
687
+ libcrypto = ctypes.CDLL(lib_name)
688
+
689
+ # Define the C function prototypes
690
+ libcrypto.OpenSSL_version_num.restype = ctypes.c_ulong
691
+ libcrypto.OpenSSL_version.argtypes = [ctypes.c_int]
692
+ libcrypto.OpenSSL_version.restype = ctypes.c_char_p
693
+
694
+ # Call the C functions
695
+ version_num_hex = libcrypto.OpenSSL_version_num()
696
+ version_str = libcrypto.OpenSSL_version(0).decode("utf-8")
697
+
698
+ # Parse the numeric version
699
+ # The version number format is MNNFFPPS
700
+ major = (version_num_hex >> 28) & 0xFF
701
+ minor = (version_num_hex >> 20) & 0xFF
702
+ patch = (version_num_hex >> 4) & 0xFF
703
+
704
+ return (major, minor, patch)
705
+
706
+ if __name__ == "__main__":
707
+ print(
708
+ ",".join([str(x) for x in get_openssl_version()]
709
+ )
710
+ )
711
+ """
712
+
713
+
714
+ @pytest.fixture
715
+ def ssl_version(pyexec, tmp_path):
716
+ file = tmp_path / "script.py"
717
+ file.write_text(SSLVERSION)
718
+ ret = subprocess.run([pyexec, str(file)], capture_output=True)
719
+ print(ret)
720
+ return tuple([int(x) for x in ret.stdout.decode().strip().split(",")])
721
+
722
+
668
723
  @pytest.mark.skip_unless_on_linux
669
724
  @pytest.mark.parametrize(
670
725
  "m2crypto_version",
671
- [
672
- "0.38.0",
673
- "0.44.0",
674
- ],
726
+ ["0.38.0", "0.44.0", "0.46.0"],
675
727
  )
676
728
  def test_pip_install_m2crypto_relenv_ssl(
677
- m2crypto_version, pipexec, pyexec, build, build_version, minor_version
729
+ m2crypto_version, pipexec, pyexec, build, build_version, minor_version, ssl_version
678
730
  ):
679
731
  if m2crypto_version == "0.38.0" and minor_version in ["3.12", "3.13"]:
680
732
  pytest.xfail("Fails due to no distutils")
681
733
 
734
+ if ssl_version >= (3, 5) and m2crypto_version in ["0.38.0", "0.44.0"]:
735
+ pytest.xfail("Openssl Needs newer m2crypto")
736
+
682
737
  _install_ppbt(pyexec)
683
738
 
684
739
  p = subprocess.run(
@@ -1169,6 +1224,40 @@ def test_install_with_target_shebang(pipexec, build, minor_version):
1169
1224
  )
1170
1225
 
1171
1226
 
1227
+ @pytest.mark.skip_unless_on_linux
1228
+ def test_install_shebang_pip_24_2(pipexec, build, minor_version):
1229
+ subprocess.run(
1230
+ [str(pipexec), "install", "--upgrade", "pip==24.2"],
1231
+ check=True,
1232
+ )
1233
+ subprocess.run(
1234
+ [str(pipexec), "install", "cowsay"],
1235
+ check=True,
1236
+ )
1237
+ ret = subprocess.run(
1238
+ [str(build / "bin" / "cowsay"), "-t", "moo"],
1239
+ check=False,
1240
+ )
1241
+ assert ret.returncode == 0
1242
+
1243
+
1244
+ @pytest.mark.skip_unless_on_linux
1245
+ def test_install_shebang_pip_25_2(pipexec, build, minor_version):
1246
+ subprocess.run(
1247
+ [str(pipexec), "install", "--upgrade", "pip==25.2"],
1248
+ check=True,
1249
+ )
1250
+ subprocess.run(
1251
+ [str(pipexec), "install", "cowsay"],
1252
+ check=True,
1253
+ )
1254
+ ret = subprocess.run(
1255
+ [str(build / "bin" / "cowsay"), "-t", "moo"],
1256
+ check=False,
1257
+ )
1258
+ assert ret.returncode == 0
1259
+
1260
+
1172
1261
  @pytest.mark.skip_unless_on_linux
1173
1262
  def test_install_with_target_uninstall(pipexec, build):
1174
1263
  env = os.environ.copy()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes