Skip to content

Commit cffbe77

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 518a4f9 commit cffbe77

3 files changed

Lines changed: 102 additions & 0 deletions

File tree

colcon_cargo/task/cargo/build.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
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 create_file
14+
from colcon_core.task import install
1315
from colcon_core.task import run
1416
from colcon_core.task import TaskExtensionPoint
1517

@@ -96,6 +98,11 @@ async def build( # noqa: D102
9698
if rc and rc.returncode:
9799
return rc.returncode
98100

101+
if self._has_libraries(metadata, pkg.name):
102+
self.progress('package')
103+
await self._install_package(
104+
metadata['packages'][0]['version'], env)
105+
99106
if not skip_hook_creation:
100107
create_environment_scripts(
101108
pkg, args, additional_hooks=additional_hooks)
@@ -197,3 +204,82 @@ def _has_binaries(metadata, package_name):
197204
# If no binary target exists in the whole package, then skip running
198205
# cargo install because it would produce an error.
199206
return False
207+
208+
# Identify if there are any libraries to install for the current package
209+
@staticmethod
210+
def _has_libraries(metadata, package_name):
211+
for package in metadata.get('packages', {}):
212+
# If the package is part of a cargo workspace, the metadata
213+
# contains all members. We're only interested in our target
214+
# package - ignore the other workspace members here.
215+
if package.get('name') != package_name:
216+
continue
217+
for target in package.get('targets', {}):
218+
if {
219+
'lib',
220+
'rlib',
221+
'proc-macro',
222+
}.intersection(target.get('crate_types', ())):
223+
# If any one binary exists in the package then we
224+
# should go ahead and install the extracted crate
225+
return True
226+
227+
# If no library target exists in the whole package, then skip extracted
228+
# crate installation because it isn't useful.
229+
return False
230+
231+
# Determine what files would be part of a packaged crate
232+
async def _get_crate_contents(self, env):
233+
pkg = self.context.pkg
234+
cmd = [
235+
CARGO_EXECUTABLE,
236+
'package',
237+
'--list',
238+
'--allow-dirty',
239+
'--quiet',
240+
'--package', pkg.name,
241+
]
242+
243+
rc = await run(
244+
self.context,
245+
cmd,
246+
cwd=self.context.pkg.path,
247+
capture_output=True,
248+
env=env
249+
)
250+
if rc is None or rc.returncode != 0:
251+
raise RuntimeError(
252+
"Could not inspect package using 'cargo package'"
253+
)
254+
255+
if rc.stdout is None:
256+
raise RuntimeError(
257+
"Failed to capture stdout from 'cargo package'"
258+
)
259+
260+
contents = set(rc.stdout.decode().splitlines())
261+
contents.difference_update({
262+
# Ignore stuff that we wouldn't want to copy
263+
'',
264+
None,
265+
'Cargo.lock',
266+
'Cargo.toml.orig',
267+
'.cargo_vcs_info.json',
268+
})
269+
return contents
270+
271+
async def _install_package(self, version, env):
272+
contents = await self._get_crate_contents(env)
273+
crate_path = Path(
274+
'share', 'cargo', 'registry', f'{self.context.pkg.name}-{version}')
275+
276+
for file in contents:
277+
dst = crate_path / file
278+
install(self.context.args, file, dst)
279+
280+
# Cargo "directory sources" require a checksum file to be included in
281+
# the package metadata (though it need not list all of the files).
282+
create_file(
283+
self.context.args,
284+
crate_path / '.cargo-checksum.json',
285+
content='{"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: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
TEST_PACKAGE_NAME = 'rust-sample-package'
2828
PURE_LIBRARY_PACKAGE_NAME = 'rust-pure-library'
2929
WORKSPACE_PACKAGE_NAME = 'rust-workspace'
30+
WORKSPACE_PACKAGE_VERSION = '0.1.0'
3031

3132
test_project_path = Path(__file__).parent / TEST_PACKAGE_NAME
3233
pure_library_path = Path(__file__).parent / PURE_LIBRARY_PACKAGE_NAME
@@ -170,6 +171,7 @@ def test_build_and_test_package():
170171
path=str(test_project_path),
171172
build_base=str(tmpdir / 'build'),
172173
install_base=str(tmpdir / 'install'),
174+
symlink_install=False,
173175
clean_build=None,
174176
cargo_args=None,
175177
),
@@ -236,6 +238,7 @@ def test_skip_pure_library_package():
236238
path=str(pure_library_path),
237239
build_base=str(tmpdir / 'build'),
238240
install_base=str(tmpdir / 'install'),
241+
symlink_install=False,
239242
clean_build=None,
240243
cargo_args=None,
241244
),
@@ -290,6 +293,7 @@ def test_workspace_with_package():
290293
path=str(workspace_project_path),
291294
build_base=str(tmpdir / 'build'),
292295
install_base=str(tmpdir / 'install'),
296+
symlink_install=False,
293297
clean_build=None,
294298
cargo_args=None,
295299
),
@@ -315,5 +319,15 @@ def test_workspace_with_package():
315319
# members didn't get installed as well
316320
assert len(tuple((install_base / 'bin').iterdir())) == 1
317321

322+
# There should also be an unpacked library create
323+
registry_path = install_base / 'share' / 'cargo' / 'registry'
324+
crate_path = registry_path / '-'.join((
325+
WORKSPACE_PACKAGE_NAME,
326+
WORKSPACE_PACKAGE_VERSION,
327+
))
328+
assert tuple(registry_path.iterdir()) == (crate_path,)
329+
assert (crate_path / 'Cargo.toml').is_file()
330+
assert (crate_path / 'src' / 'lib.rs').is_file()
331+
318332
finally:
319333
event_loop.close()

0 commit comments

Comments
 (0)