req-compile 1.1.2__tar.gz → 1.2.0__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.
- {req_compile-1.1.2 → req_compile-1.2.0}/CHANGELOG.rst +13 -0
- {req_compile-1.1.2/req_compile.egg-info → req_compile-1.2.0}/PKG-INFO +44 -1
- {req_compile-1.1.2 → req_compile-1.2.0}/README.rst +30 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/req_compile/candidates.py +18 -3
- {req_compile-1.1.2 → req_compile-1.2.0}/req_compile/cmdline.py +154 -10
- {req_compile-1.1.2 → req_compile-1.2.0}/req_compile/compile.py +7 -1
- {req_compile-1.1.2 → req_compile-1.2.0}/req_compile/containers.py +38 -9
- {req_compile-1.1.2 → req_compile-1.2.0}/req_compile/dists.py +55 -15
- {req_compile-1.1.2 → req_compile-1.2.0}/req_compile/metadata/extractor.py +6 -1
- {req_compile-1.1.2 → req_compile-1.2.0}/req_compile/repos/findlinks.py +3 -1
- {req_compile-1.1.2 → req_compile-1.2.0}/req_compile/repos/multi.py +14 -3
- {req_compile-1.1.2 → req_compile-1.2.0}/req_compile/repos/pypi.py +38 -26
- {req_compile-1.1.2 → req_compile-1.2.0}/req_compile/repos/repository.py +102 -170
- {req_compile-1.1.2 → req_compile-1.2.0}/req_compile/repos/solution.py +154 -8
- {req_compile-1.1.2 → req_compile-1.2.0}/req_compile/repos/source.py +6 -1
- req_compile-1.2.0/req_compile/target.py +561 -0
- {req_compile-1.1.2 → req_compile-1.2.0/req_compile.egg-info}/PKG-INFO +44 -1
- {req_compile-1.1.2 → req_compile-1.2.0}/req_compile.egg-info/SOURCES.txt +4 -0
- req_compile-1.2.0/tests/test_cross_platform.py +47 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/tests/test_extractor.py +22 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/tests/test_repositories.py +6 -14
- req_compile-1.2.0/tests/test_solution_target.py +182 -0
- req_compile-1.2.0/tests/test_target.py +406 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/version.bzl +1 -1
- {req_compile-1.1.2 → req_compile-1.2.0}/LICENSE.txt +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/MANIFEST.in +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/pyproject.toml +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/req_compile/__init__.py +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/req_compile/__main__.py +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/req_compile/config.py +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/req_compile/errors.py +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/req_compile/filename.py +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/req_compile/metadata/__init__.py +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/req_compile/metadata/dist_info.py +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/req_compile/metadata/metadata.py +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/req_compile/metadata/patch.py +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/req_compile/metadata/pyproject.py +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/req_compile/metadata/source.py +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/req_compile/py.typed +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/req_compile/repos/__init__.py +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/req_compile/utils.py +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/req_compile/versions.py +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/req_compile.egg-info/dependency_links.txt +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/req_compile.egg-info/entry_points.txt +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/req_compile.egg-info/requires.txt +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/req_compile.egg-info/top_level.txt +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/requirements.in +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/setup.cfg +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/setup.py +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/tests/test_cmdline.py +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/tests/test_compile.py +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/tests/test_containers.py +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/tests/test_dists.py +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/tests/test_egg_info.py +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/tests/test_filename.py +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/tests/test_integration.py +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/tests/test_merge_requirements.py +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/tests/test_metadata.py +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/tests/test_no_setup_py.py +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/tests/test_utils.py +0 -0
- {req_compile-1.1.2 → req_compile-1.2.0}/tests/test_versions.py +0 -0
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
New in version 1.2.0:
|
|
2
|
+
|
|
3
|
+
* Cross-platform compilation. ``--platform``, ``--python-version`` and ``--implementation`` select
|
|
4
|
+
wheels and evaluate environment markers for an environment other than the one running ``req-compile``.
|
|
5
|
+
``--platform`` takes a wheel platform tag such as ``manylinux_2_28_x86_64`` or ``macosx_11_0_arm64``,
|
|
6
|
+
which is recorded in the header of lock files compiled by Bazel.
|
|
7
|
+
* The ``py_reqs_compiler`` Bazel rule has new ``platform`` and ``python_version`` attributes, so the
|
|
8
|
+
solutions for every platform a project supports can be compiled and tested from a single machine.
|
|
9
|
+
Solutions record their platform and Python version in their header, and a solution compiled for
|
|
10
|
+
another target is not recycled when recompiling without ``--upgrade``.
|
|
11
|
+
* Removed the ``py_reqs_remote_compiler`` Bazel rule. Use the ``platform`` attribute of ``py_reqs_compiler``
|
|
12
|
+
instead.
|
|
13
|
+
|
|
1
14
|
New in version 1.1.0:
|
|
2
15
|
|
|
3
16
|
* Python 3.13 and 3.14 support.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: req-compile
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Python requirements compiler
|
|
5
5
|
Home-page: https://github.com/periareon/req-compile
|
|
6
6
|
Author: Spencer Putt
|
|
@@ -38,6 +38,19 @@ Dynamic: requires-dist
|
|
|
38
38
|
Dynamic: requires-python
|
|
39
39
|
Dynamic: summary
|
|
40
40
|
|
|
41
|
+
New in version 1.2.0:
|
|
42
|
+
|
|
43
|
+
* Cross-platform compilation. ``--platform``, ``--python-version`` and ``--implementation`` select
|
|
44
|
+
wheels and evaluate environment markers for an environment other than the one running ``req-compile``.
|
|
45
|
+
``--platform`` takes a wheel platform tag such as ``manylinux_2_28_x86_64`` or ``macosx_11_0_arm64``,
|
|
46
|
+
which is recorded in the header of lock files compiled by Bazel.
|
|
47
|
+
* The ``py_reqs_compiler`` Bazel rule has new ``platform`` and ``python_version`` attributes, so the
|
|
48
|
+
solutions for every platform a project supports can be compiled and tested from a single machine.
|
|
49
|
+
Solutions record their platform and Python version in their header, and a solution compiled for
|
|
50
|
+
another target is not recycled when recompiling without ``--upgrade``.
|
|
51
|
+
* Removed the ``py_reqs_remote_compiler`` Bazel rule. Use the ``platform`` attribute of ``py_reqs_compiler``
|
|
52
|
+
instead.
|
|
53
|
+
|
|
41
54
|
New in version 1.1.0:
|
|
42
55
|
|
|
43
56
|
* Python 3.13 and 3.14 support.
|
|
@@ -112,6 +125,9 @@ To get started with Bazel, add the following to your ``MODULE.bazel`` file:
|
|
|
112
125
|
)
|
|
113
126
|
use_repo(requirements, "my_pip_deps")
|
|
114
127
|
|
|
128
|
+
The solutions for every platform can be compiled from a single machine with the ``platform`` attribute of
|
|
129
|
+
``py_reqs_compiler``. See the `Bazel rules documentation <docs/docs.md>`_ for details.
|
|
130
|
+
|
|
115
131
|
|
|
116
132
|
Why use it?
|
|
117
133
|
-----------
|
|
@@ -229,6 +245,33 @@ was passed on the commandline.
|
|
|
229
245
|
By default, PyPI (https://pypi.org/) or the default pip index is added as a default repository. It can be removed by passing
|
|
230
246
|
``--no-index`` on the commandline or passing a different index via ``--index-url``.
|
|
231
247
|
|
|
248
|
+
Compiling for another platform
|
|
249
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
250
|
+
By default a solution is compiled for the interpreter running Req-Compile: wheels are selected and
|
|
251
|
+
environment markers are evaluated for its platform, Python version and implementation. A solution for
|
|
252
|
+
a different environment can be compiled from any machine with the target options::
|
|
253
|
+
|
|
254
|
+
> req-compile requirements.txt --platform macosx_11_0_arm64 --python-version 3.11 --only-binary :all:
|
|
255
|
+
|
|
256
|
+
* ``--platform``
|
|
257
|
+
|
|
258
|
+
The platform to compile for as a wheel platform tag, the same values ``pip --platform`` accepts:
|
|
259
|
+
``manylinux_2_28_x86_64``, ``manylinux2014_aarch64``, ``macosx_11_0_arm64``, ``macosx_10_15_x86_64``,
|
|
260
|
+
``win_amd64`` and so on. A manylinux tag accepts wheels built for that glibc or older, a macosx tag
|
|
261
|
+
wheels built for that release or older, and plain ``linux_x86_64`` accepts no manylinux wheels at all,
|
|
262
|
+
as with pip.
|
|
263
|
+
* ``--python-version``
|
|
264
|
+
|
|
265
|
+
The Python version to compile for, e.g. ``3.11`` or ``3.11.4``. This can be used on its own to
|
|
266
|
+
compile for a different version of Python on the current platform.
|
|
267
|
+
* ``--implementation``
|
|
268
|
+
|
|
269
|
+
The interpreter tag of the implementation to compile for, ``cp`` for CPython or ``pp`` for PyPy.
|
|
270
|
+
|
|
271
|
+
Source distributions have their metadata extracted by running them on the machine performing the
|
|
272
|
+
compilation, which may not match the target. Prefer ``--only-binary :all:`` when compiling for another
|
|
273
|
+
platform. ``req-candidates`` accepts the same options to show the candidates available for a target.
|
|
274
|
+
|
|
232
275
|
Identifying source of constraints
|
|
233
276
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
234
277
|
Why did I just get version 1.11.0 of ``six``? Find out by examining the output::
|
|
@@ -47,6 +47,9 @@ To get started with Bazel, add the following to your ``MODULE.bazel`` file:
|
|
|
47
47
|
)
|
|
48
48
|
use_repo(requirements, "my_pip_deps")
|
|
49
49
|
|
|
50
|
+
The solutions for every platform can be compiled from a single machine with the ``platform`` attribute of
|
|
51
|
+
``py_reqs_compiler``. See the `Bazel rules documentation <docs/docs.md>`_ for details.
|
|
52
|
+
|
|
50
53
|
|
|
51
54
|
Why use it?
|
|
52
55
|
-----------
|
|
@@ -164,6 +167,33 @@ was passed on the commandline.
|
|
|
164
167
|
By default, PyPI (https://pypi.org/) or the default pip index is added as a default repository. It can be removed by passing
|
|
165
168
|
``--no-index`` on the commandline or passing a different index via ``--index-url``.
|
|
166
169
|
|
|
170
|
+
Compiling for another platform
|
|
171
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
172
|
+
By default a solution is compiled for the interpreter running Req-Compile: wheels are selected and
|
|
173
|
+
environment markers are evaluated for its platform, Python version and implementation. A solution for
|
|
174
|
+
a different environment can be compiled from any machine with the target options::
|
|
175
|
+
|
|
176
|
+
> req-compile requirements.txt --platform macosx_11_0_arm64 --python-version 3.11 --only-binary :all:
|
|
177
|
+
|
|
178
|
+
* ``--platform``
|
|
179
|
+
|
|
180
|
+
The platform to compile for as a wheel platform tag, the same values ``pip --platform`` accepts:
|
|
181
|
+
``manylinux_2_28_x86_64``, ``manylinux2014_aarch64``, ``macosx_11_0_arm64``, ``macosx_10_15_x86_64``,
|
|
182
|
+
``win_amd64`` and so on. A manylinux tag accepts wheels built for that glibc or older, a macosx tag
|
|
183
|
+
wheels built for that release or older, and plain ``linux_x86_64`` accepts no manylinux wheels at all,
|
|
184
|
+
as with pip.
|
|
185
|
+
* ``--python-version``
|
|
186
|
+
|
|
187
|
+
The Python version to compile for, e.g. ``3.11`` or ``3.11.4``. This can be used on its own to
|
|
188
|
+
compile for a different version of Python on the current platform.
|
|
189
|
+
* ``--implementation``
|
|
190
|
+
|
|
191
|
+
The interpreter tag of the implementation to compile for, ``cp`` for CPython or ``pp`` for PyPy.
|
|
192
|
+
|
|
193
|
+
Source distributions have their metadata extracted by running them on the machine performing the
|
|
194
|
+
compilation, which may not match the target. Prefer ``--only-binary :all:`` when compiling for another
|
|
195
|
+
platform. ``req-candidates`` accepts the same options to show the candidates available for a target.
|
|
196
|
+
|
|
167
197
|
Identifying source of constraints
|
|
168
198
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
169
199
|
Why did I just get version 1.11.0 of ``six``? Find out by examining the output::
|
|
@@ -14,7 +14,9 @@ from req_compile.cmdline import (
|
|
|
14
14
|
SplitProjectsFilter,
|
|
15
15
|
add_logging_args,
|
|
16
16
|
add_repo_args,
|
|
17
|
+
add_target_args,
|
|
17
18
|
build_repo,
|
|
19
|
+
build_target,
|
|
18
20
|
)
|
|
19
21
|
from req_compile.repos.pypi import PyPIRepository
|
|
20
22
|
from req_compile.repos.repository import (
|
|
@@ -71,6 +73,7 @@ def candidates_main() -> None:
|
|
|
71
73
|
)
|
|
72
74
|
add_logging_args(parser)
|
|
73
75
|
add_repo_args(parser)
|
|
76
|
+
add_target_args(parser)
|
|
74
77
|
args = parser.parse_args()
|
|
75
78
|
|
|
76
79
|
logger = logging.getLogger("req_compile")
|
|
@@ -81,6 +84,12 @@ def candidates_main() -> None:
|
|
|
81
84
|
logging.basicConfig(level=logging.CRITICAL, stream=sys.stderr)
|
|
82
85
|
logging.getLogger().setLevel(level=logging.CRITICAL)
|
|
83
86
|
|
|
87
|
+
try:
|
|
88
|
+
target = build_target(args)
|
|
89
|
+
except ValueError as ex:
|
|
90
|
+
print(f"ERROR: {ex}", file=sys.stderr)
|
|
91
|
+
sys.exit(1)
|
|
92
|
+
|
|
84
93
|
start = time.monotonic()
|
|
85
94
|
wheeldir = tempfile.mkdtemp(suffix="-wheeldir")
|
|
86
95
|
repo = build_repo(
|
|
@@ -94,10 +103,13 @@ def candidates_main() -> None:
|
|
|
94
103
|
no_index=args.no_index,
|
|
95
104
|
wheeldir=wheeldir,
|
|
96
105
|
allow_prerelease=args.allow_prerelease,
|
|
106
|
+
target=target,
|
|
97
107
|
)
|
|
98
108
|
|
|
99
109
|
if isinstance(repo, PyPIRepository) and args.project_name is None:
|
|
100
|
-
repo = SourceRepository(
|
|
110
|
+
repo = SourceRepository(
|
|
111
|
+
".", excluded_paths=args.excluded_sources, target=target
|
|
112
|
+
)
|
|
101
113
|
|
|
102
114
|
total_candidates = 0
|
|
103
115
|
try:
|
|
@@ -112,10 +124,13 @@ def candidates_main() -> None:
|
|
|
112
124
|
candidates = repo.get_candidates(req)
|
|
113
125
|
if not args.all:
|
|
114
126
|
candidates = filter_candidates(
|
|
115
|
-
req,
|
|
127
|
+
req,
|
|
128
|
+
candidates,
|
|
129
|
+
allow_prereleases=args.allow_prerelease,
|
|
130
|
+
target=repo.target,
|
|
116
131
|
)
|
|
117
132
|
|
|
118
|
-
for candidate in sort_candidates(candidates):
|
|
133
|
+
for candidate in sort_candidates(candidates, target=repo.target):
|
|
119
134
|
if (
|
|
120
135
|
candidate.type == DistributionType.SDIST
|
|
121
136
|
and candidate.name in args.only_binary
|
|
@@ -45,6 +45,7 @@ from req_compile.repos.solution import (
|
|
|
45
45
|
SolutionRepository,
|
|
46
46
|
)
|
|
47
47
|
from req_compile.repos.source import SourceRepository
|
|
48
|
+
from req_compile.target import TargetEnvironment, resolve
|
|
48
49
|
from req_compile.utils import (
|
|
49
50
|
NormName,
|
|
50
51
|
normalize_project_name,
|
|
@@ -56,6 +57,8 @@ from req_compile.versions import is_possible
|
|
|
56
57
|
# Blacklist of requirements that will be filtered out of the output
|
|
57
58
|
BLACKLIST: Iterable[str] = []
|
|
58
59
|
|
|
60
|
+
LOG = logging.getLogger("req_compile.cmdline")
|
|
61
|
+
|
|
59
62
|
|
|
60
63
|
def _cantusereason_to_text(
|
|
61
64
|
reason: CantUseReason,
|
|
@@ -147,7 +150,7 @@ def _generate_no_candidate_display(
|
|
|
147
150
|
pinned_req = parse_requirement(f"{walkback_project}=={version}")
|
|
148
151
|
print(f" {pinned_req}", file=sys.stderr, end="")
|
|
149
152
|
try:
|
|
150
|
-
collection = DistributionCollection()
|
|
153
|
+
collection = DistributionCollection(target=repo.target)
|
|
151
154
|
collection.add_dist(
|
|
152
155
|
repo.get_dist(pinned_req)[0], None, pinned_req
|
|
153
156
|
)
|
|
@@ -263,7 +266,9 @@ def _dump_repo_candidates(
|
|
|
263
266
|
print(" {}:".format(repo), file=sys.stderr)
|
|
264
267
|
if candidates:
|
|
265
268
|
attempted_versions = set()
|
|
266
|
-
for num, candidate in enumerate(
|
|
269
|
+
for num, candidate in enumerate(
|
|
270
|
+
sort_candidates(candidates, target=repo.target)
|
|
271
|
+
):
|
|
267
272
|
if not req.specifier.contains(candidate.version):
|
|
268
273
|
continue
|
|
269
274
|
attempted_versions.add(candidate.version)
|
|
@@ -682,7 +687,13 @@ def build_repo(
|
|
|
682
687
|
extra_index_urls: Optional[Iterable[str]] = None,
|
|
683
688
|
no_index: bool = False,
|
|
684
689
|
allow_prerelease: bool = False,
|
|
690
|
+
target: Optional[TargetEnvironment] = None,
|
|
685
691
|
) -> Repository:
|
|
692
|
+
"""Build the repository to compile against from command line style options.
|
|
693
|
+
|
|
694
|
+
Args:
|
|
695
|
+
target: The environment to compile for. Defaults to the host.
|
|
696
|
+
"""
|
|
686
697
|
pooled_repos: List[Repository] = []
|
|
687
698
|
if find_links:
|
|
688
699
|
pooled_repos.extend(
|
|
@@ -692,6 +703,7 @@ def build_repo(
|
|
|
692
703
|
relative_to=(
|
|
693
704
|
find_links[find_link] if isinstance(find_links, Mapping) else None
|
|
694
705
|
),
|
|
706
|
+
target=target,
|
|
695
707
|
)
|
|
696
708
|
for find_link in find_links
|
|
697
709
|
)
|
|
@@ -704,6 +716,7 @@ def build_repo(
|
|
|
704
716
|
wheeldir,
|
|
705
717
|
allow_prerelease=allow_prerelease,
|
|
706
718
|
index_type=IndexType.DEFAULT,
|
|
719
|
+
target=target,
|
|
707
720
|
)
|
|
708
721
|
)
|
|
709
722
|
else:
|
|
@@ -713,6 +726,7 @@ def build_repo(
|
|
|
713
726
|
wheeldir,
|
|
714
727
|
allow_prerelease=allow_prerelease,
|
|
715
728
|
index_type=IndexType.INDEX_URL,
|
|
729
|
+
target=target,
|
|
716
730
|
)
|
|
717
731
|
for index_url in index_urls
|
|
718
732
|
)
|
|
@@ -723,38 +737,93 @@ def build_repo(
|
|
|
723
737
|
wheeldir,
|
|
724
738
|
allow_prerelease=allow_prerelease,
|
|
725
739
|
index_type=IndexType.EXTRA_INDEX_URL,
|
|
740
|
+
target=target,
|
|
726
741
|
)
|
|
727
742
|
for index_url in extra_index_urls
|
|
728
743
|
)
|
|
729
744
|
|
|
730
745
|
repos: List[Repository] = []
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
746
|
+
ignored_solutions: List[SolutionRepository] = []
|
|
747
|
+
for solution_repo in load_solutions(solutions, upgrade_packages, target):
|
|
748
|
+
if solution_repo.matches_target(target):
|
|
749
|
+
repos.append(solution_repo)
|
|
750
|
+
else:
|
|
751
|
+
ignored_solutions.append(solution_repo)
|
|
736
752
|
if sources:
|
|
737
753
|
repos.extend(
|
|
738
|
-
SourceRepository(source, excluded_paths=excluded_sources)
|
|
754
|
+
SourceRepository(source, excluded_paths=excluded_sources, target=target)
|
|
739
755
|
for source in sources
|
|
740
756
|
)
|
|
741
757
|
|
|
742
758
|
if len(pooled_repos) > 1:
|
|
743
|
-
repos.append(PooledCandidateMultiRepository(*pooled_repos))
|
|
759
|
+
repos.append(PooledCandidateMultiRepository(*pooled_repos, target=target))
|
|
744
760
|
elif len(pooled_repos) == 1:
|
|
745
761
|
repos.append(pooled_repos[0])
|
|
746
762
|
|
|
747
763
|
if not repos:
|
|
764
|
+
if ignored_solutions:
|
|
765
|
+
raise RepositoryInitializationError(
|
|
766
|
+
SolutionRepository,
|
|
767
|
+
"The solution {} was compiled for {}, not for {}. Recompile it for the new target.".format(
|
|
768
|
+
", ".join(repr(repo) for repo in ignored_solutions),
|
|
769
|
+
_recorded_target(ignored_solutions[0]),
|
|
770
|
+
resolve(target),
|
|
771
|
+
),
|
|
772
|
+
)
|
|
748
773
|
raise ValueError(
|
|
749
774
|
"At least one source of Python distributions must be provided."
|
|
750
775
|
)
|
|
751
776
|
if len(repos) > 1:
|
|
752
|
-
repo: Repository = MultiRepository(*repos)
|
|
777
|
+
repo: Repository = MultiRepository(*repos, target=target)
|
|
753
778
|
else:
|
|
754
779
|
repo = repos[0]
|
|
755
780
|
return repo
|
|
756
781
|
|
|
757
782
|
|
|
783
|
+
def _recorded_target(solution: SolutionRepository) -> str:
|
|
784
|
+
return "{} ({})".format(
|
|
785
|
+
solution.recorded_platform or "an unknown platform",
|
|
786
|
+
solution.recorded_python or "unknown Python version",
|
|
787
|
+
)
|
|
788
|
+
|
|
789
|
+
|
|
790
|
+
def load_solutions(
|
|
791
|
+
solutions: Iterable[str],
|
|
792
|
+
upgrade_packages: Iterable[str],
|
|
793
|
+
target: Optional[TargetEnvironment] = None,
|
|
794
|
+
) -> List[SolutionRepository]:
|
|
795
|
+
"""Load previous solutions, warning about any compiled for another target.
|
|
796
|
+
|
|
797
|
+
A solution compiled for another platform or Python version pins artifacts and
|
|
798
|
+
already evaluated dependencies that do not apply to this compilation. Such
|
|
799
|
+
solutions are returned, so callers can tell the user, but must not be used.
|
|
800
|
+
See `SolutionRepository.matches_target`.
|
|
801
|
+
|
|
802
|
+
Args:
|
|
803
|
+
solutions: Paths to solution files.
|
|
804
|
+
upgrade_packages: Packages to omit from the solutions.
|
|
805
|
+
target: The environment being compiled for. Defaults to the host.
|
|
806
|
+
|
|
807
|
+
Returns:
|
|
808
|
+
A repository per solution file.
|
|
809
|
+
"""
|
|
810
|
+
repos = []
|
|
811
|
+
for solution in solutions:
|
|
812
|
+
repo = SolutionRepository(
|
|
813
|
+
solution, excluded_packages=upgrade_packages, target=target
|
|
814
|
+
)
|
|
815
|
+
if not repo.matches_target(target):
|
|
816
|
+
LOG.warning(
|
|
817
|
+
"Ignoring the solution %s, it was compiled for %s but this compilation "
|
|
818
|
+
"is for %s. Requirements will be resolved again.",
|
|
819
|
+
solution,
|
|
820
|
+
_recorded_target(repo),
|
|
821
|
+
resolve(target),
|
|
822
|
+
)
|
|
823
|
+
repos.append(repo)
|
|
824
|
+
return repos
|
|
825
|
+
|
|
826
|
+
|
|
758
827
|
class IndentFilter(logging.Filter):
|
|
759
828
|
"""A filter to indent to a level specified by the depth attribute of a logging record"""
|
|
760
829
|
|
|
@@ -914,6 +983,7 @@ def compile_main(raw_args: Optional[Sequence[str]] = None) -> None:
|
|
|
914
983
|
)
|
|
915
984
|
add_logging_args(parser)
|
|
916
985
|
add_repo_args(parser)
|
|
986
|
+
add_target_args(parser)
|
|
917
987
|
|
|
918
988
|
args = parser.parse_args(args=raw_args)
|
|
919
989
|
logger = logging.getLogger("req_compile")
|
|
@@ -926,6 +996,12 @@ def compile_main(raw_args: Optional[Sequence[str]] = None) -> None:
|
|
|
926
996
|
else:
|
|
927
997
|
logging.basicConfig(level=logging.CRITICAL, stream=sys.stderr)
|
|
928
998
|
|
|
999
|
+
try:
|
|
1000
|
+
target = build_target(args)
|
|
1001
|
+
except ValueError as ex:
|
|
1002
|
+
print(f"ERROR: {ex}", file=sys.stderr)
|
|
1003
|
+
sys.exit(1)
|
|
1004
|
+
|
|
929
1005
|
wheeldir = args.wheel_dir
|
|
930
1006
|
if wheeldir:
|
|
931
1007
|
try:
|
|
@@ -1018,6 +1094,7 @@ def compile_main(raw_args: Optional[Sequence[str]] = None) -> None:
|
|
|
1018
1094
|
extra_index_urls=args.extra_index_urls,
|
|
1019
1095
|
no_index=args.no_index,
|
|
1020
1096
|
allow_prerelease=args.allow_prerelease,
|
|
1097
|
+
target=target,
|
|
1021
1098
|
)
|
|
1022
1099
|
try:
|
|
1023
1100
|
results, roots = perform_compile(
|
|
@@ -1027,6 +1104,7 @@ def compile_main(raw_args: Optional[Sequence[str]] = None) -> None:
|
|
|
1027
1104
|
constraint_reqs=constraint_reqs,
|
|
1028
1105
|
remove_constraints=args.remove_constraints,
|
|
1029
1106
|
only_binary=args.only_binary,
|
|
1107
|
+
target=target,
|
|
1030
1108
|
)
|
|
1031
1109
|
except RepositoryInitializationError as ex:
|
|
1032
1110
|
logger.exception("Error initialization repository")
|
|
@@ -1113,6 +1191,72 @@ def norm_index_url(index_url: str) -> str:
|
|
|
1113
1191
|
return index_url.rstrip("/")
|
|
1114
1192
|
|
|
1115
1193
|
|
|
1194
|
+
def add_target_args(parser: argparse.ArgumentParser) -> None:
|
|
1195
|
+
"""Add arguments describing the environment to compile for."""
|
|
1196
|
+
group = parser.add_argument_group(
|
|
1197
|
+
"target environment",
|
|
1198
|
+
description=(
|
|
1199
|
+
"Compile for an environment other than the one running req-compile. "
|
|
1200
|
+
"Wheels are selected and environment markers are evaluated as if "
|
|
1201
|
+
"installing on the target. Source distributions still have their "
|
|
1202
|
+
"metadata extracted on this machine, so prefer --only-binary when "
|
|
1203
|
+
"compiling for another platform."
|
|
1204
|
+
),
|
|
1205
|
+
)
|
|
1206
|
+
group.add_argument(
|
|
1207
|
+
"--platform",
|
|
1208
|
+
dest="target_platform",
|
|
1209
|
+
default=None,
|
|
1210
|
+
metavar="platform",
|
|
1211
|
+
help=(
|
|
1212
|
+
"Platform to compile for as a wheel platform tag, as accepted by pip, e.g. "
|
|
1213
|
+
"manylinux_2_28_x86_64, manylinux2014_aarch64, macosx_11_0_arm64 or win_amd64. "
|
|
1214
|
+
"A manylinux tag accepts wheels for that glibc or older, a macosx tag wheels for "
|
|
1215
|
+
"that release or older. Defaults to the current platform."
|
|
1216
|
+
),
|
|
1217
|
+
)
|
|
1218
|
+
group.add_argument(
|
|
1219
|
+
"--python-version",
|
|
1220
|
+
dest="target_python_version",
|
|
1221
|
+
default=None,
|
|
1222
|
+
metavar="X.Y[.Z]",
|
|
1223
|
+
help="Python version to compile for, e.g. 3.11. Defaults to the running interpreter.",
|
|
1224
|
+
)
|
|
1225
|
+
group.add_argument(
|
|
1226
|
+
"--implementation",
|
|
1227
|
+
dest="target_implementation",
|
|
1228
|
+
default=None,
|
|
1229
|
+
metavar="tag",
|
|
1230
|
+
help=(
|
|
1231
|
+
"Python implementation to compile for as a wheel interpreter tag, e.g. cp "
|
|
1232
|
+
"for CPython or pp for PyPy. Defaults to cp when --platform or "
|
|
1233
|
+
"--python-version is given, otherwise the running interpreter."
|
|
1234
|
+
),
|
|
1235
|
+
)
|
|
1236
|
+
|
|
1237
|
+
|
|
1238
|
+
def build_target(args: argparse.Namespace) -> Optional[TargetEnvironment]:
|
|
1239
|
+
"""Build the target environment from parsed `add_target_args` arguments.
|
|
1240
|
+
|
|
1241
|
+
Returns:
|
|
1242
|
+
The target, or None when compiling for the host.
|
|
1243
|
+
|
|
1244
|
+
Raises:
|
|
1245
|
+
ValueError: If the arguments do not describe a supported target.
|
|
1246
|
+
"""
|
|
1247
|
+
if (
|
|
1248
|
+
args.target_platform is None
|
|
1249
|
+
and args.target_python_version is None
|
|
1250
|
+
and args.target_implementation is None
|
|
1251
|
+
):
|
|
1252
|
+
return None
|
|
1253
|
+
return TargetEnvironment.from_spec(
|
|
1254
|
+
platform_spec=args.target_platform,
|
|
1255
|
+
python_version=args.target_python_version,
|
|
1256
|
+
implementation=args.target_implementation,
|
|
1257
|
+
)
|
|
1258
|
+
|
|
1259
|
+
|
|
1116
1260
|
def add_repo_args(parser: argparse.ArgumentParser) -> None:
|
|
1117
1261
|
"""Add arguments related to adding repositories to the command line"""
|
|
1118
1262
|
group = parser.add_argument_group("repositories")
|
|
@@ -17,6 +17,7 @@ from req_compile.dists import DependencyNode, DistributionCollection
|
|
|
17
17
|
from req_compile.errors import MetadataError, NoCandidateException
|
|
18
18
|
from req_compile.repos.repository import Repository
|
|
19
19
|
from req_compile.repos.source import SourceRepository
|
|
20
|
+
from req_compile.target import TargetEnvironment
|
|
20
21
|
from req_compile.utils import (
|
|
21
22
|
NormName,
|
|
22
23
|
is_pinned_requirement,
|
|
@@ -310,6 +311,7 @@ def perform_compile(
|
|
|
310
311
|
allow_circular_dependencies: bool = True,
|
|
311
312
|
only_binary: Optional[Set[NormName]] = None,
|
|
312
313
|
max_downgrade: Optional[int] = None,
|
|
314
|
+
target: Optional[TargetEnvironment] = None,
|
|
313
315
|
) -> Tuple[DistributionCollection, Set[DependencyNode]]:
|
|
314
316
|
"""Perform a compilation using the given inputs and constraints.
|
|
315
317
|
|
|
@@ -327,11 +329,15 @@ def perform_compile(
|
|
|
327
329
|
allow_circular_dependencies: Whether to allow circular dependencies
|
|
328
330
|
only_binary: Set of projects that should only consider binary distributions.
|
|
329
331
|
max_downgrade: The maximum number of version downgrades that will be allowed for conflicts.
|
|
332
|
+
target: The environment to compile for. Defaults to the target of `repo`, which
|
|
333
|
+
itself defaults to the host.
|
|
330
334
|
|
|
331
335
|
Returns:
|
|
332
336
|
the solution and root nodes used to generate it
|
|
333
337
|
"""
|
|
334
|
-
|
|
338
|
+
if target is None:
|
|
339
|
+
target = repo.target
|
|
340
|
+
results = req_compile.dists.DistributionCollection(target=target)
|
|
335
341
|
|
|
336
342
|
constraint_nodes = set()
|
|
337
343
|
nodes = set()
|
|
@@ -1,32 +1,42 @@
|
|
|
1
1
|
import itertools
|
|
2
2
|
import os
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import Any, Iterable, Iterator, List, Optional, Tuple, Union
|
|
4
|
+
from typing import Any, Iterable, Iterator, List, Optional, Set, Tuple, Union
|
|
5
5
|
|
|
6
6
|
import packaging.requirements
|
|
7
7
|
import packaging.version
|
|
8
8
|
from packaging.requirements import InvalidRequirement
|
|
9
9
|
|
|
10
|
-
from req_compile.
|
|
10
|
+
from req_compile.target import TargetEnvironment, resolve
|
|
11
|
+
from req_compile.utils import (
|
|
12
|
+
NormName,
|
|
13
|
+
normalize_project_name,
|
|
14
|
+
reduce_requirements,
|
|
15
|
+
req_iter_from_file,
|
|
16
|
+
)
|
|
11
17
|
|
|
12
18
|
|
|
13
19
|
def req_uses_extra(
|
|
14
|
-
req: packaging.requirements.Requirement,
|
|
20
|
+
req: packaging.requirements.Requirement,
|
|
21
|
+
extra: Optional[str],
|
|
22
|
+
target: Optional[TargetEnvironment] = None,
|
|
15
23
|
) -> bool:
|
|
16
24
|
"""Determine if this requirement would be used with the given extra.
|
|
17
25
|
|
|
18
26
|
If a distribution is requested with one of its extras, this filter will determine
|
|
19
27
|
if the given requirement in its install requirements is provided. All base
|
|
20
28
|
requirements that don't require the extra will also be included.
|
|
29
|
+
|
|
30
|
+
Environment markers other than ``extra`` are evaluated against ``target``,
|
|
31
|
+
which defaults to the host.
|
|
21
32
|
"""
|
|
22
33
|
if extra and not req.marker:
|
|
23
34
|
return False
|
|
24
35
|
keep_req = True
|
|
25
36
|
if req.marker:
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
keep_req = req.marker.evaluate(extras)
|
|
37
|
+
keep_req = req.marker.evaluate(
|
|
38
|
+
resolve(target).marker_environment_with_extra(extra)
|
|
39
|
+
)
|
|
30
40
|
return keep_req
|
|
31
41
|
|
|
32
42
|
|
|
@@ -48,17 +58,36 @@ class RequirementContainer:
|
|
|
48
58
|
self.version: Optional[packaging.version.Version] = None
|
|
49
59
|
self.hash: Optional[str] = None
|
|
50
60
|
self.candidate: Any = None
|
|
61
|
+
# The extras that `reqs` is known to fully describe. `None` means the
|
|
62
|
+
# metadata is complete, so every extra the distribution provides is
|
|
63
|
+
# described. Solutions only record the extras that were active when they
|
|
64
|
+
# were compiled, so they populate this to mark the rest as unknown.
|
|
65
|
+
self.known_extras: Optional[Set[NormName]] = None
|
|
51
66
|
|
|
52
67
|
def __iter__(self) -> Iterator[packaging.requirements.Requirement]:
|
|
53
68
|
return iter(self.reqs)
|
|
54
69
|
|
|
55
70
|
def requires(
|
|
56
|
-
self, extra: Optional[str] = None
|
|
71
|
+
self, extra: Optional[str] = None, target: Optional[TargetEnvironment] = None
|
|
57
72
|
) -> Iterable[packaging.requirements.Requirement]:
|
|
73
|
+
"""The requirements that apply for the given extra on the given target."""
|
|
74
|
+
target = resolve(target)
|
|
58
75
|
return reduce_requirements(
|
|
59
|
-
req for req in self.reqs if req_uses_extra(req, extra)
|
|
76
|
+
req for req in self.reqs if req_uses_extra(req, extra, target)
|
|
60
77
|
)
|
|
61
78
|
|
|
79
|
+
def describes_extras(self, extras: Iterable[str]) -> bool:
|
|
80
|
+
"""Whether this container describes the requirements of all the given extras.
|
|
81
|
+
|
|
82
|
+
A container built from incomplete metadata, such as a solution file, only
|
|
83
|
+
knows about the extras that were recorded in it. Asking it for the
|
|
84
|
+
requirements of any other extra silently yields nothing, so callers need
|
|
85
|
+
to check first and go find complete metadata instead.
|
|
86
|
+
"""
|
|
87
|
+
if self.known_extras is None:
|
|
88
|
+
return True
|
|
89
|
+
return {normalize_project_name(extra) for extra in extras} <= self.known_extras
|
|
90
|
+
|
|
62
91
|
def to_definition(
|
|
63
92
|
self, extras: Optional[Iterable[str]]
|
|
64
93
|
) -> Tuple[str, Optional[packaging.version.Version]]:
|