Skip to content

Commit 09924a9

Browse files
committed
Install a curated registry of crates with libraries
This largely mirrors the approach taken by major Linux distributions like Ubuntu and Fedora. We can curate the crates in a way that should allow us to use them in downstream package builds within this colcon workspace or downstream workspaces. This change doesn't yet add the code necessary to instruct cargo to use our curated registry.
1 parent ec2b28b commit 09924a9

3 files changed

Lines changed: 94 additions & 0 deletions

File tree

colcon_cargo/task/cargo/build.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from colcon_core.logging import colcon_logger
1111
from colcon_core.plugin_system import satisfies_version
1212
from colcon_core.shell import create_environment_hook, get_command_environment
13+
from colcon_core.task import install
1314
from colcon_core.task import run
1415
from colcon_core.task import TaskExtensionPoint
1516

@@ -96,6 +97,11 @@ async def build( # noqa: D102
9697
if rc and rc.returncode:
9798
return rc.returncode
9899

100+
if self._has_libraries(metadata, pkg.name):
101+
self.progress('package')
102+
await self._install_package(
103+
metadata['packages'][0]['version'], env)
104+
99105
if not skip_hook_creation:
100106
create_environment_scripts(
101107
pkg, args, additional_hooks=additional_hooks)
@@ -197,3 +203,79 @@ def _has_binaries(metadata, package_name):
197203
# If no binary target exists in the whole package, then skip running
198204
# cargo install because it would produce an error.
199205
return False
206+
207+
# Identify if there are any libraries to install for the current package
208+
@staticmethod
209+
def _has_libraries(metadata, package_name):
210+
for package in metadata.get('packages', {}):
211+
# If the package is part of a cargo workspace, the metadata
212+
# contains all members. We're only interested in our target
213+
# package - ignore the other workspace members here.
214+
if package.get('name') != package_name:
215+
continue
216+
for target in package.get('targets', {}):
217+
if {
218+
'lib',
219+
'rlib',
220+
'proc-macro',
221+
}.intersection(target.get('crate_types', ())):
222+
# If any one binary exists in the package then we
223+
# should go ahead and install the extracted crate
224+
return True
225+
226+
# If no library target exists in the whole package, then skip extracted
227+
# crate installation because it isn't useful.
228+
return False
229+
230+
# Determine what files would be part of a packaged crate
231+
async def _get_crate_contents(self, env):
232+
pkg = self.context.pkg
233+
cmd = [
234+
CARGO_EXECUTABLE,
235+
'package',
236+
'--list',
237+
'--allow-dirty',
238+
'--quiet',
239+
'--package', pkg.name,
240+
]
241+
242+
rc = await run(
243+
self.context,
244+
cmd,
245+
cwd=self.context.pkg.path,
246+
capture_output=True,
247+
env=env
248+
)
249+
if rc is None or rc.returncode != 0:
250+
raise RuntimeError(
251+
"Could not inspect package using 'cargo package'"
252+
)
253+
254+
if rc.stdout is None:
255+
raise RuntimeError(
256+
"Failed to capture stdout from 'cargo package'"
257+
)
258+
259+
contents = set(rc.stdout.decode().splitlines())
260+
contents.difference_update({
261+
# Ignore stuff that we wouldn't want to copy
262+
'',
263+
None,
264+
'Cargo.lock',
265+
'Cargo.toml.orig',
266+
'.cargo_vcs_info.json',
267+
})
268+
return contents
269+
270+
async def _install_package(self, version, env):
271+
contents = await self._get_crate_contents(env)
272+
crate_path = Path(
273+
'share', 'cargo', 'registry', f'{self.context.pkg.name}-{version}')
274+
crate_path.mkdir(parents=True, exist_ok=True)
275+
276+
for file in contents:
277+
dst = crate_path / file
278+
install(self.context.args, file, dst)
279+
280+
(crate_path / '.cargo-checksum.json').write_text(
281+
'{"files":{},"package":""}\n')

test/spell_check.words

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ apache
22
argcomplete
33
asyncio
44
autouse
5+
checksum
56
colcon
67
completers
78
cwpd
@@ -26,6 +27,7 @@ pydocstyle
2627
pytest
2728
returncode
2829
rglob
30+
rlib
2931
rmtree
3032
rtype
3133
rustfmt

test/test_build.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ def test_build_and_test_package():
166166
path=str(test_project_path),
167167
build_base=str(tmpdir / 'build'),
168168
install_base=str(tmpdir / 'install'),
169+
symlink_install=False,
169170
clean_build=None,
170171
cargo_args=None,
171172
),
@@ -232,6 +233,7 @@ def test_skip_pure_library_package():
232233
path=str(pure_library_path),
233234
build_base=str(tmpdir / 'build'),
234235
install_base=str(tmpdir / 'install'),
236+
symlink_install=False,
235237
clean_build=None,
236238
cargo_args=None,
237239
),
@@ -286,6 +288,7 @@ def test_workspace_with_package():
286288
path=str(workspace_project_path),
287289
build_base=str(tmpdir / 'build'),
288290
install_base=str(tmpdir / 'install'),
291+
symlink_install=False,
289292
clean_build=None,
290293
cargo_args=None,
291294
),
@@ -311,5 +314,12 @@ def test_workspace_with_package():
311314
# members didn't get installed as well
312315
assert len(tuple((install_base / 'bin').iterdir())) == 1
313316

317+
# There should also be an unpacked library create
318+
registry_path = install_base / 'share' / 'cargo' / 'registry'
319+
crate_path = registry_path / f'{WORKSPACE_PACKAGE_NAME}-0.1.0'
320+
assert tuple(registry_path.iterdir()) == (crate_path,)
321+
assert (crate_path / 'Cargo.toml').is_file()
322+
assert (crate_path / 'src' / 'lib.rs').is_file()
323+
314324
finally:
315325
event_loop.close()

0 commit comments

Comments
 (0)