relenv 0.21.1__py3-none-any.whl → 0.22.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.
- relenv/__init__.py +14 -2
- relenv/__main__.py +12 -6
- relenv/_resources/xz/config.h +148 -0
- relenv/_resources/xz/readme.md +4 -0
- relenv/build/__init__.py +28 -30
- relenv/build/common/__init__.py +50 -0
- relenv/build/common/_sysconfigdata_template.py +72 -0
- relenv/build/common/builder.py +907 -0
- relenv/build/common/builders.py +163 -0
- relenv/build/common/download.py +324 -0
- relenv/build/common/install.py +609 -0
- relenv/build/common/ui.py +432 -0
- relenv/build/darwin.py +128 -14
- relenv/build/linux.py +296 -78
- relenv/build/windows.py +259 -44
- relenv/buildenv.py +48 -17
- relenv/check.py +10 -5
- relenv/common.py +499 -163
- relenv/create.py +147 -7
- relenv/fetch.py +16 -4
- relenv/manifest.py +15 -7
- relenv/python-versions.json +329 -0
- relenv/pyversions.py +817 -30
- relenv/relocate.py +101 -55
- relenv/runtime.py +452 -253
- relenv/toolchain.py +9 -3
- {relenv-0.21.1.dist-info → relenv-0.22.0.dist-info}/METADATA +1 -1
- relenv-0.22.0.dist-info/RECORD +48 -0
- tests/__init__.py +2 -0
- tests/_pytest_typing.py +45 -0
- tests/conftest.py +42 -36
- tests/test_build.py +426 -9
- tests/test_common.py +311 -48
- tests/test_create.py +149 -6
- tests/test_downloads.py +19 -15
- tests/test_fips_photon.py +6 -3
- tests/test_module_imports.py +44 -0
- tests/test_pyversions_runtime.py +177 -0
- tests/test_relocate.py +45 -39
- tests/test_relocate_module.py +257 -0
- tests/test_runtime.py +1802 -6
- tests/test_verify_build.py +500 -34
- relenv/build/common.py +0 -1609
- relenv-0.21.1.dist-info/RECORD +0 -35
- {relenv-0.21.1.dist-info → relenv-0.22.0.dist-info}/WHEEL +0 -0
- {relenv-0.21.1.dist-info → relenv-0.22.0.dist-info}/entry_points.txt +0 -0
- {relenv-0.21.1.dist-info → relenv-0.22.0.dist-info}/licenses/LICENSE.md +0 -0
- {relenv-0.21.1.dist-info → relenv-0.22.0.dist-info}/licenses/NOTICE +0 -0
- {relenv-0.21.1.dist-info → relenv-0.22.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Copyright 2022-2025 Broadcom.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""
|
|
4
|
+
Build functions for specific dependencies.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import pathlib
|
|
9
|
+
import shutil
|
|
10
|
+
import sys
|
|
11
|
+
from typing import IO, MutableMapping, TYPE_CHECKING
|
|
12
|
+
|
|
13
|
+
from relenv.common import PlatformError, runcmd
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from .builder import Dirs
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def build_default(env: MutableMapping[str, str], dirs: Dirs, logfp: IO[str]) -> None:
|
|
20
|
+
"""
|
|
21
|
+
The default build function if none is given during the build process.
|
|
22
|
+
|
|
23
|
+
:param env: The environment dictionary
|
|
24
|
+
:type env: dict
|
|
25
|
+
:param dirs: The working directories
|
|
26
|
+
:type dirs: ``relenv.build.common.Dirs``
|
|
27
|
+
:param logfp: A handle for the log file
|
|
28
|
+
:type logfp: file
|
|
29
|
+
"""
|
|
30
|
+
cmd = [
|
|
31
|
+
"./configure",
|
|
32
|
+
"--prefix={}".format(dirs.prefix),
|
|
33
|
+
]
|
|
34
|
+
if env["RELENV_HOST"].find("linux") > -1:
|
|
35
|
+
cmd += [
|
|
36
|
+
"--build={}".format(env["RELENV_BUILD"]),
|
|
37
|
+
"--host={}".format(env["RELENV_HOST"]),
|
|
38
|
+
]
|
|
39
|
+
runcmd(cmd, env=env, stderr=logfp, stdout=logfp)
|
|
40
|
+
runcmd(["make", "-j8"], env=env, stderr=logfp, stdout=logfp)
|
|
41
|
+
runcmd(["make", "install"], env=env, stderr=logfp, stdout=logfp)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def build_openssl_fips(
|
|
45
|
+
env: MutableMapping[str, str], dirs: Dirs, logfp: IO[str]
|
|
46
|
+
) -> None:
|
|
47
|
+
"""Build OpenSSL with FIPS module."""
|
|
48
|
+
return build_openssl(env, dirs, logfp, fips=True)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def build_openssl(
|
|
52
|
+
env: MutableMapping[str, str],
|
|
53
|
+
dirs: Dirs,
|
|
54
|
+
logfp: IO[str],
|
|
55
|
+
fips: bool = False,
|
|
56
|
+
) -> None:
|
|
57
|
+
"""
|
|
58
|
+
Build openssl.
|
|
59
|
+
|
|
60
|
+
:param env: The environment dictionary
|
|
61
|
+
:type env: dict
|
|
62
|
+
:param dirs: The working directories
|
|
63
|
+
:type dirs: ``relenv.build.common.Dirs``
|
|
64
|
+
:param logfp: A handle for the log file
|
|
65
|
+
:type logfp: file
|
|
66
|
+
"""
|
|
67
|
+
arch = "aarch64"
|
|
68
|
+
if sys.platform == "darwin":
|
|
69
|
+
plat = "darwin64"
|
|
70
|
+
if env["RELENV_HOST_ARCH"] == "x86_64":
|
|
71
|
+
arch = "x86_64-cc"
|
|
72
|
+
elif env["RELENV_HOST_ARCH"] == "arm64":
|
|
73
|
+
arch = "arm64-cc"
|
|
74
|
+
else:
|
|
75
|
+
raise PlatformError(f"Unable to build {env['RELENV_HOST_ARCH']}")
|
|
76
|
+
extended_cmd = []
|
|
77
|
+
else:
|
|
78
|
+
plat = "linux"
|
|
79
|
+
if env["RELENV_HOST_ARCH"] == "x86_64":
|
|
80
|
+
arch = "x86_64"
|
|
81
|
+
elif env["RELENV_HOST_ARCH"] == "aarch64":
|
|
82
|
+
arch = "aarch64"
|
|
83
|
+
else:
|
|
84
|
+
raise PlatformError(f"Unable to build {env['RELENV_HOST_ARCH']}")
|
|
85
|
+
extended_cmd = [
|
|
86
|
+
"-Wl,-z,noexecstack",
|
|
87
|
+
]
|
|
88
|
+
if fips:
|
|
89
|
+
extended_cmd.append("enable-fips")
|
|
90
|
+
cmd = [
|
|
91
|
+
"./Configure",
|
|
92
|
+
f"{plat}-{arch}",
|
|
93
|
+
f"--prefix={dirs.prefix}",
|
|
94
|
+
"--openssldir=/etc/ssl",
|
|
95
|
+
"--libdir=lib",
|
|
96
|
+
"--api=1.1.1",
|
|
97
|
+
"--shared",
|
|
98
|
+
"--with-rand-seed=os,egd",
|
|
99
|
+
"enable-md2",
|
|
100
|
+
"enable-egd",
|
|
101
|
+
"no-idea",
|
|
102
|
+
]
|
|
103
|
+
cmd.extend(extended_cmd)
|
|
104
|
+
runcmd(
|
|
105
|
+
cmd,
|
|
106
|
+
env=env,
|
|
107
|
+
stderr=logfp,
|
|
108
|
+
stdout=logfp,
|
|
109
|
+
)
|
|
110
|
+
runcmd(["make", "-j8"], env=env, stderr=logfp, stdout=logfp)
|
|
111
|
+
if fips:
|
|
112
|
+
shutil.copy(
|
|
113
|
+
pathlib.Path("providers") / "fips.so",
|
|
114
|
+
pathlib.Path(dirs.prefix) / "lib" / "ossl-modules",
|
|
115
|
+
)
|
|
116
|
+
else:
|
|
117
|
+
runcmd(["make", "install_sw"], env=env, stderr=logfp, stdout=logfp)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def build_sqlite(env: MutableMapping[str, str], dirs: Dirs, logfp: IO[str]) -> None:
|
|
121
|
+
"""
|
|
122
|
+
Build sqlite.
|
|
123
|
+
|
|
124
|
+
:param env: The environment dictionary
|
|
125
|
+
:type env: dict
|
|
126
|
+
:param dirs: The working directories
|
|
127
|
+
:type dirs: ``relenv.build.common.Dirs``
|
|
128
|
+
:param logfp: A handle for the log file
|
|
129
|
+
:type logfp: file
|
|
130
|
+
"""
|
|
131
|
+
# extra_cflags=('-Os '
|
|
132
|
+
# '-DSQLITE_ENABLE_FTS5 '
|
|
133
|
+
# '-DSQLITE_ENABLE_FTS4 '
|
|
134
|
+
# '-DSQLITE_ENABLE_FTS3_PARENTHESIS '
|
|
135
|
+
# '-DSQLITE_ENABLE_JSON1 '
|
|
136
|
+
# '-DSQLITE_ENABLE_RTREE '
|
|
137
|
+
# '-DSQLITE_TCL=0 '
|
|
138
|
+
# )
|
|
139
|
+
# configure_pre=[
|
|
140
|
+
# '--enable-threadsafe',
|
|
141
|
+
# '--enable-shared=no',
|
|
142
|
+
# '--enable-static=yes',
|
|
143
|
+
# '--disable-readline',
|
|
144
|
+
# '--disable-dependency-tracking',
|
|
145
|
+
# ]
|
|
146
|
+
cmd = [
|
|
147
|
+
"./configure",
|
|
148
|
+
# "--with-shared",
|
|
149
|
+
# "--without-static",
|
|
150
|
+
"--enable-threadsafe",
|
|
151
|
+
"--disable-readline",
|
|
152
|
+
"--disable-dependency-tracking",
|
|
153
|
+
"--prefix={}".format(dirs.prefix),
|
|
154
|
+
# "--enable-add-ons=nptl,ports",
|
|
155
|
+
]
|
|
156
|
+
if env["RELENV_HOST"].find("linux") > -1:
|
|
157
|
+
cmd += [
|
|
158
|
+
"--build={}".format(env["RELENV_BUILD_ARCH"]),
|
|
159
|
+
"--host={}".format(env["RELENV_HOST"]),
|
|
160
|
+
]
|
|
161
|
+
runcmd(cmd, env=env, stderr=logfp, stdout=logfp)
|
|
162
|
+
runcmd(["make", "-j8"], env=env, stderr=logfp, stdout=logfp)
|
|
163
|
+
runcmd(["make", "install"], env=env, stderr=logfp, stdout=logfp)
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# Copyright 2022-2025 Broadcom.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""
|
|
4
|
+
Download utility class for fetching build dependencies.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import hashlib
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
import pathlib
|
|
12
|
+
import subprocess
|
|
13
|
+
import sys
|
|
14
|
+
from typing import Callable, Optional, Tuple, Union
|
|
15
|
+
|
|
16
|
+
from relenv.common import (
|
|
17
|
+
RelenvException,
|
|
18
|
+
ConfigurationError,
|
|
19
|
+
ChecksumValidationError,
|
|
20
|
+
download_url,
|
|
21
|
+
get_download_location,
|
|
22
|
+
runcmd,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# Type alias for path-like objects
|
|
26
|
+
PathLike = Union[str, os.PathLike[str]]
|
|
27
|
+
|
|
28
|
+
# Environment flag for CI/CD detection
|
|
29
|
+
CICD = "CI" in os.environ
|
|
30
|
+
|
|
31
|
+
log = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def verify_checksum(file: PathLike, checksum: Optional[str]) -> bool:
|
|
35
|
+
"""
|
|
36
|
+
Verify the checksum of a file.
|
|
37
|
+
|
|
38
|
+
Supports both SHA-1 (40 hex chars) and SHA-256 (64 hex chars) checksums.
|
|
39
|
+
The hash algorithm is auto-detected based on checksum length.
|
|
40
|
+
|
|
41
|
+
:param file: The path to the file to check.
|
|
42
|
+
:type file: str
|
|
43
|
+
:param checksum: The checksum to verify against (SHA-1 or SHA-256)
|
|
44
|
+
:type checksum: str
|
|
45
|
+
|
|
46
|
+
:raises RelenvException: If the checksum verification failed
|
|
47
|
+
|
|
48
|
+
:return: True if it succeeded, or False if the checksum was None
|
|
49
|
+
:rtype: bool
|
|
50
|
+
"""
|
|
51
|
+
if checksum is None:
|
|
52
|
+
log.error("Can't verify checksum because none was given")
|
|
53
|
+
return False
|
|
54
|
+
|
|
55
|
+
# Auto-detect hash type based on length
|
|
56
|
+
# SHA-1: 40 hex chars, SHA-256: 64 hex chars
|
|
57
|
+
if len(checksum) == 64:
|
|
58
|
+
hash_algo = hashlib.sha256()
|
|
59
|
+
hash_name = "sha256"
|
|
60
|
+
elif len(checksum) == 40:
|
|
61
|
+
hash_algo = hashlib.sha1()
|
|
62
|
+
hash_name = "sha1"
|
|
63
|
+
else:
|
|
64
|
+
raise ChecksumValidationError(
|
|
65
|
+
f"Invalid checksum length {len(checksum)}. Expected 40 (SHA-1) or 64 (SHA-256)"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
with open(file, "rb") as fp:
|
|
69
|
+
hash_algo.update(fp.read())
|
|
70
|
+
file_checksum = hash_algo.hexdigest()
|
|
71
|
+
if checksum != file_checksum:
|
|
72
|
+
raise ChecksumValidationError(
|
|
73
|
+
f"{hash_name} checksum verification failed. expected={checksum} found={file_checksum}"
|
|
74
|
+
)
|
|
75
|
+
return True
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class Download:
|
|
79
|
+
"""
|
|
80
|
+
A utility that holds information about content to be downloaded.
|
|
81
|
+
|
|
82
|
+
:param name: The name of the download
|
|
83
|
+
:type name: str
|
|
84
|
+
:param url: The url of the download
|
|
85
|
+
:type url: str
|
|
86
|
+
:param signature: The signature of the download, defaults to None
|
|
87
|
+
:type signature: str
|
|
88
|
+
:param destination: The path to download the file to
|
|
89
|
+
:type destination: str
|
|
90
|
+
:param version: The version of the content to download
|
|
91
|
+
:type version: str
|
|
92
|
+
:param sha1: The sha1 sum of the download
|
|
93
|
+
:type sha1: str
|
|
94
|
+
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
def __init__(
|
|
98
|
+
self,
|
|
99
|
+
name: str,
|
|
100
|
+
url: str,
|
|
101
|
+
fallback_url: Optional[str] = None,
|
|
102
|
+
signature: Optional[str] = None,
|
|
103
|
+
destination: PathLike = "",
|
|
104
|
+
version: str = "",
|
|
105
|
+
checksum: Optional[str] = None,
|
|
106
|
+
) -> None:
|
|
107
|
+
self.name = name
|
|
108
|
+
self.url_tpl = url
|
|
109
|
+
self.fallback_url_tpl = fallback_url
|
|
110
|
+
self.signature_tpl = signature
|
|
111
|
+
self._destination: pathlib.Path = pathlib.Path()
|
|
112
|
+
if destination:
|
|
113
|
+
self._destination = pathlib.Path(destination)
|
|
114
|
+
self.version = version
|
|
115
|
+
self.checksum = checksum
|
|
116
|
+
|
|
117
|
+
def copy(self) -> "Download":
|
|
118
|
+
"""Create a copy of this Download instance."""
|
|
119
|
+
return Download(
|
|
120
|
+
self.name,
|
|
121
|
+
self.url_tpl,
|
|
122
|
+
self.fallback_url_tpl,
|
|
123
|
+
self.signature_tpl,
|
|
124
|
+
self.destination,
|
|
125
|
+
self.version,
|
|
126
|
+
self.checksum,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def destination(self) -> pathlib.Path:
|
|
131
|
+
"""Get the destination directory path."""
|
|
132
|
+
return self._destination
|
|
133
|
+
|
|
134
|
+
@destination.setter
|
|
135
|
+
def destination(self, value: Optional[PathLike]) -> None:
|
|
136
|
+
"""Set the destination directory path."""
|
|
137
|
+
if value:
|
|
138
|
+
self._destination = pathlib.Path(value)
|
|
139
|
+
else:
|
|
140
|
+
self._destination = pathlib.Path()
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def url(self) -> str:
|
|
144
|
+
"""Get the formatted download URL."""
|
|
145
|
+
return self.url_tpl.format(version=self.version)
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def fallback_url(self) -> Optional[str]:
|
|
149
|
+
"""Get the formatted fallback URL if configured."""
|
|
150
|
+
if self.fallback_url_tpl:
|
|
151
|
+
return self.fallback_url_tpl.format(version=self.version)
|
|
152
|
+
return None
|
|
153
|
+
|
|
154
|
+
@property
|
|
155
|
+
def signature_url(self) -> str:
|
|
156
|
+
"""Get the formatted signature URL."""
|
|
157
|
+
if self.signature_tpl is None:
|
|
158
|
+
raise ConfigurationError("Signature template not configured")
|
|
159
|
+
return self.signature_tpl.format(version=self.version)
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def filepath(self) -> pathlib.Path:
|
|
163
|
+
"""Get the full file path where the download will be saved."""
|
|
164
|
+
_, name = self.url.rsplit("/", 1)
|
|
165
|
+
return self.destination / name
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def formatted_url(self) -> str:
|
|
169
|
+
"""Get the formatted URL (alias for url property)."""
|
|
170
|
+
return self.url_tpl.format(version=self.version)
|
|
171
|
+
|
|
172
|
+
def fetch_file(
|
|
173
|
+
self, progress_callback: Optional[Callable[[int, int], None]] = None
|
|
174
|
+
) -> Tuple[str, bool]:
|
|
175
|
+
"""
|
|
176
|
+
Download the file.
|
|
177
|
+
|
|
178
|
+
:param progress_callback: Optional callback(downloaded_bytes, total_bytes)
|
|
179
|
+
:type progress_callback: Optional[Callable[[int, int], None]]
|
|
180
|
+
:return: The path to the downloaded content, and whether it was downloaded.
|
|
181
|
+
:rtype: tuple(str, bool)
|
|
182
|
+
"""
|
|
183
|
+
try:
|
|
184
|
+
return (
|
|
185
|
+
download_url(
|
|
186
|
+
self.url,
|
|
187
|
+
self.destination,
|
|
188
|
+
CICD,
|
|
189
|
+
progress_callback=progress_callback,
|
|
190
|
+
),
|
|
191
|
+
True,
|
|
192
|
+
)
|
|
193
|
+
except Exception as exc:
|
|
194
|
+
fallback = self.fallback_url
|
|
195
|
+
if fallback:
|
|
196
|
+
print(f"Download failed {self.url} ({exc}); trying fallback url")
|
|
197
|
+
return (
|
|
198
|
+
download_url(
|
|
199
|
+
fallback,
|
|
200
|
+
self.destination,
|
|
201
|
+
CICD,
|
|
202
|
+
progress_callback=progress_callback,
|
|
203
|
+
),
|
|
204
|
+
True,
|
|
205
|
+
)
|
|
206
|
+
raise
|
|
207
|
+
|
|
208
|
+
def fetch_signature(self, version: Optional[str] = None) -> Tuple[str, bool]:
|
|
209
|
+
"""
|
|
210
|
+
Download the file signature.
|
|
211
|
+
|
|
212
|
+
:return: The path to the downloaded signature.
|
|
213
|
+
:rtype: str
|
|
214
|
+
"""
|
|
215
|
+
return download_url(self.signature_url, self.destination, CICD), True
|
|
216
|
+
|
|
217
|
+
def exists(self) -> bool:
|
|
218
|
+
"""
|
|
219
|
+
True when the artifact already exists on disk.
|
|
220
|
+
|
|
221
|
+
:return: True when the artifact already exists on disk
|
|
222
|
+
:rtype: bool
|
|
223
|
+
"""
|
|
224
|
+
return self.filepath.exists()
|
|
225
|
+
|
|
226
|
+
def valid_hash(self) -> None:
|
|
227
|
+
"""Validate the hash of the downloaded file (placeholder method)."""
|
|
228
|
+
pass
|
|
229
|
+
|
|
230
|
+
@staticmethod
|
|
231
|
+
def validate_signature(archive: PathLike, signature: Optional[PathLike]) -> bool:
|
|
232
|
+
"""
|
|
233
|
+
True when the archive's signature is valid.
|
|
234
|
+
|
|
235
|
+
:param archive: The path to the archive to validate
|
|
236
|
+
:type archive: str
|
|
237
|
+
:param signature: The path to the signature to validate against
|
|
238
|
+
:type signature: str
|
|
239
|
+
|
|
240
|
+
:return: True if it validated properly, else False
|
|
241
|
+
:rtype: bool
|
|
242
|
+
"""
|
|
243
|
+
if signature is None:
|
|
244
|
+
log.error("Can't check signature because none was given")
|
|
245
|
+
return False
|
|
246
|
+
try:
|
|
247
|
+
runcmd(
|
|
248
|
+
["gpg", "--verify", signature, archive],
|
|
249
|
+
stderr=subprocess.PIPE,
|
|
250
|
+
stdout=subprocess.PIPE,
|
|
251
|
+
)
|
|
252
|
+
return True
|
|
253
|
+
except RelenvException as exc:
|
|
254
|
+
log.error("Signature validation failed on %s: %s", archive, exc)
|
|
255
|
+
return False
|
|
256
|
+
|
|
257
|
+
@staticmethod
|
|
258
|
+
def validate_checksum(archive: PathLike, checksum: Optional[str]) -> bool:
|
|
259
|
+
"""
|
|
260
|
+
True when when the archive matches the sha1 hash.
|
|
261
|
+
|
|
262
|
+
:param archive: The path to the archive to validate
|
|
263
|
+
:type archive: str
|
|
264
|
+
:param checksum: The sha1 sum to validate against
|
|
265
|
+
:type checksum: str
|
|
266
|
+
:return: True if the sums matched, else False
|
|
267
|
+
:rtype: bool
|
|
268
|
+
"""
|
|
269
|
+
try:
|
|
270
|
+
verify_checksum(archive, checksum)
|
|
271
|
+
return True
|
|
272
|
+
except RelenvException as exc:
|
|
273
|
+
log.error("sha1 validation failed on %s: %s", archive, exc)
|
|
274
|
+
return False
|
|
275
|
+
|
|
276
|
+
def __call__(
|
|
277
|
+
self,
|
|
278
|
+
force_download: bool = False,
|
|
279
|
+
show_ui: bool = False,
|
|
280
|
+
exit_on_failure: bool = False,
|
|
281
|
+
progress_callback: Optional[Callable[[int, int], None]] = None,
|
|
282
|
+
) -> bool:
|
|
283
|
+
"""
|
|
284
|
+
Downloads the url and validates the signature and sha1 sum.
|
|
285
|
+
|
|
286
|
+
:param progress_callback: Optional callback(downloaded_bytes, total_bytes)
|
|
287
|
+
:type progress_callback: Optional[Callable[[int, int], None]]
|
|
288
|
+
:return: Whether or not validation succeeded
|
|
289
|
+
:rtype: bool
|
|
290
|
+
"""
|
|
291
|
+
os.makedirs(self.filepath.parent, exist_ok=True)
|
|
292
|
+
|
|
293
|
+
downloaded = False
|
|
294
|
+
if force_download:
|
|
295
|
+
_, downloaded = self.fetch_file(progress_callback)
|
|
296
|
+
else:
|
|
297
|
+
file_is_valid = False
|
|
298
|
+
dest = get_download_location(self.url, self.destination)
|
|
299
|
+
if self.checksum and os.path.exists(dest):
|
|
300
|
+
file_is_valid = self.validate_checksum(dest, self.checksum)
|
|
301
|
+
if file_is_valid:
|
|
302
|
+
log.debug("%s already downloaded, skipping.", self.url)
|
|
303
|
+
else:
|
|
304
|
+
_, downloaded = self.fetch_file(progress_callback)
|
|
305
|
+
valid = True
|
|
306
|
+
if downloaded:
|
|
307
|
+
if self.signature_tpl is not None:
|
|
308
|
+
sig, _ = self.fetch_signature()
|
|
309
|
+
valid_sig = self.validate_signature(self.filepath, sig)
|
|
310
|
+
valid = valid and valid_sig
|
|
311
|
+
if self.checksum is not None:
|
|
312
|
+
valid_checksum = self.validate_checksum(self.filepath, self.checksum)
|
|
313
|
+
valid = valid and valid_checksum
|
|
314
|
+
|
|
315
|
+
if not valid:
|
|
316
|
+
log.warning("Checksum did not match %s: %s", self.name, self.checksum)
|
|
317
|
+
if show_ui:
|
|
318
|
+
sys.stderr.write(
|
|
319
|
+
f"\nChecksum did not match {self.name}: {self.checksum}\n"
|
|
320
|
+
)
|
|
321
|
+
sys.stderr.flush()
|
|
322
|
+
if exit_on_failure and not valid:
|
|
323
|
+
sys.exit(1)
|
|
324
|
+
return valid
|