-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsearch.py
More file actions
196 lines (164 loc) · 6.95 KB
/
Copy pathsearch.py
File metadata and controls
196 lines (164 loc) · 6.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# Copyright 2025 Open Source Robotics Foundation, Inc.
# Licensed under the Apache License, Version 2.0
from collections import defaultdict
import os
from pathlib import Path
from pallet_patcher.manifest import get_dependencies
from pallet_patcher.manifest import load_manifest
def _get_available_crates(search_path):
"""
Create a list of crates available from a directory.
:param search_path: Local registry source to search for packages
:type search_path: Path
:returns: Collection of pkgs available in a directory and their versions
:rtype: dict
:returns: Collection of the directory and metadata information for a
specific pkgname+version
:rtype: dict
"""
versions = defaultdict(set) # Skip duplicates in versions dict
pkgs_metadata = {}
# Iterate over all the paths provided
for manifest_path in search_path.glob('*/Cargo.toml'):
manifest = load_manifest(manifest_path)
pkgname = manifest.get('package', {}).get('name')
# TO-DO: In some cases, we want to crash if we can't find the package
if not pkgname:
continue
version = manifest.get('package', {}).get('version') or '0.0.0'
versions[pkgname].add(version)
# We are assuming here there won't be duplicated crates+version within
# the same search_path.
pkgs_metadata[f'{pkgname}+{version}'] = (
manifest_path.parent, manifest)
return versions, pkgs_metadata
def _get_reference(specification):
if not isinstance(specification, dict):
return None
path = specification.get('path')
if path is not None:
return Path(path).as_uri()
git = specification.get('git')
if git is not None:
return git
return specification.get('registry')
def compose(dependencies, search_paths):
"""
Compose a collection of crates which may satisfy given dependencies.
:param dependencies: List of dependency tuples
(import name, specifications)
:type dependencies: tuple
:param search_paths: List of local registry sources to search for packages
:type search_paths: list
:returns: Collection of packages which may satisfy the required
dependencies.
:rtype: dict
"""
search_paths = list(search_paths)
composition = {}
candidates = {}
queue = list(dependencies)
while queue:
name, specifications = queue.pop(0)
if isinstance(specifications, dict):
name = specifications.get('package', name)
if name in composition:
reference = _get_reference(specifications)
composition[name][0].add(reference)
continue
candidate = candidates.get(name)
while candidate is None and search_paths:
search_path = search_paths.pop(0)
layer = {}
for manifest_path in search_path.glob('*/Cargo.toml'):
manifest = load_manifest(manifest_path)
pkgname = manifest.get('package', {}).get('name')
if pkgname in candidates:
continue
layer.setdefault(pkgname, []).append(
(manifest_path.parent, manifest))
candidate = layer.get(name)
candidates.update(layer)
if candidate is None:
continue
reference = _get_reference(specifications)
locations = set()
for location, manifest in candidate:
locations.add(location)
plain_deps, build_deps, _ = get_dependencies(manifest, location)
queue.extend(plain_deps.items())
queue.extend(build_deps.items())
composition[name] = ({reference}, locations)
return composition
def get_cargo_arguments(composition, default_registry=None):
"""
Get arguments to pass to 'cargo' which patch package references.
:param composition: The curated package composition
:type composition: dict
:param default_registry: The default package registry if none was specified
:type default_registry: str, optional
:returns: List of command line arguments
:rtype: list
"""
if default_registry is None:
default_registry = os.environ.get('CARGO_REGISTRY_DEFAULT')
if not default_registry:
default_registry = 'crates-io'
arguments = set()
for name, (references, candidates) in composition.items():
for reference in references:
if reference is None:
reference = default_registry
elif any(
candidate.as_uri() == reference
for candidate in candidates
):
# Cargo does not allow a patch to point to the same location as
# the original dependency specification. If we encounter this,
# just skip the reference entirely since it already points to
# at least one of our candidates.
continue
for idx, candidate in enumerate(candidates):
# Specifically use ~, which is valid in TOML but not in a
# Cargo package name to reduce the likelihood of a collision
section = f"patch.'{reference}'.'{name}~{idx}'"
arguments.add(f"--config={section}.package='{name}'")
arguments.add(f"--config={section}.path='{candidate}'")
return sorted(arguments)
def get_cargo_config(composition, default_registry=None):
"""
Get Cargo configuration to patch package references.
:param composition: The curated package composition
:type composition: dict
:param default_registry: The default package registry if none was specified
:type default_registry: str, optional
:returns: Raw TOML configuration
:rtype: str
"""
if default_registry is None:
default_registry = os.environ.get('CARGO_REGISTRY_DEFAULT')
if not default_registry:
default_registry = 'crates-io'
sections = set()
for name, (references, candidates) in composition.items():
for reference in references:
if reference is None:
reference = default_registry
elif any(
candidate.as_uri() == reference
for candidate in candidates
):
# Cargo does not allow a patch to point to the same location as
# the original dependency specification. If we encounter this,
# just skip the reference entirely since it already points to
# at least one of our candidates.
continue
for idx, candidate in enumerate(candidates):
# Specifically use ~, which is valid in TOML but not in a
# Cargo package name to reduce the likelihood of a collision
sections.add('\n'.join((
f"[patch.'{reference}'.'{name}~{idx}']",
f"package = '{name}'",
f"path = '{candidate}'",
)))
return '\n\n'.join(sorted(sections))