diff --git a/colcon_cargo/task/cargo/build.py b/colcon_cargo/task/cargo/build.py index 46dcdf5..be42aaf 100644 --- a/colcon_cargo/task/cargo/build.py +++ b/colcon_cargo/task/cargo/build.py @@ -1,6 +1,7 @@ # Copyright 2018 Easymov Robotics # Licensed under the Apache License, Version 2.0 +import json from pathlib import Path import shutil @@ -80,12 +81,12 @@ async def build( # noqa: D102 if rc and rc.returncode: return rc.returncode + # colcon-ros-cargo overrides install command to return None. + # We also need to check if the package has any binaries, because if it + # has no binaries then cargo install will return an error. cmd = self._install_cmd(cargo_args) - - self.progress('install') - - # colcon-ros-cargo overrides install command to return None - if cmd is not None: + if cmd is not None and await self._has_binaries(env): + self.progress('install') rc = await run( self.context, cmd, cwd=self.context.pkg.path, env=env) if rc and rc.returncode: @@ -126,3 +127,42 @@ def _install_cmd(self, cargo_args): '--path', '.', '--root', args.install_base, ] + cargo_args + + # Identify if there are any binaries to install for the current package + async def _has_binaries(self, env): + cmd = [ + CARGO_EXECUTABLE, + 'metadata', + '--no-deps', + '--format-version', '1', + ] + + rc = await run( + self.context, + cmd, + cwd=self.context.pkg.path, + capture_output=True, + env=env + ) + if rc is None or rc.returncode != 0: + raise RuntimeError( + "Could not inspect package using 'cargo metadata'" + ) + + if rc.stdout is None: + raise RuntimeError( + "Failed to capture stdout from 'cargo metadata'" + ) + + metadata = json.loads(rc.stdout) + for package in metadata.get('packages', {}): + for target in package.get('targets', {}): + for crate_type in target.get('crate_types', {}): + if crate_type == 'bin': + # If any one binary exists in the package then we + # should go ahead and run cargo install + return True + + # If no binary target exists in the whole package, then skip running + # cargo install because it would produce an error. + return False diff --git a/test/rust-pure-library/Cargo.toml b/test/rust-pure-library/Cargo.toml new file mode 100644 index 0000000..e5b077f --- /dev/null +++ b/test/rust-pure-library/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "rust-pure-library" +version = "0.1.0" +authors = ["Luca Della Vedova"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/test/rust-pure-library/src/lib.rs b/test/rust-pure-library/src/lib.rs new file mode 100644 index 0000000..bcf0e27 --- /dev/null +++ b/test/rust-pure-library/src/lib.rs @@ -0,0 +1 @@ +pub struct Type; diff --git a/test/test_build.py b/test/test_build.py index 8fdb6ae..51993dd 100644 --- a/test/test_build.py +++ b/test/test_build.py @@ -19,8 +19,10 @@ import pytest TEST_PACKAGE_NAME = 'rust-sample-package' +PURE_LIBRARY_PACKAGE_NAME = 'rust-pure-library' test_project_path = Path(__file__).parent / TEST_PACKAGE_NAME +pure_library_path = Path(__file__).parent / PURE_LIBRARY_PACKAGE_NAME @pytest.fixture(autouse=True) @@ -107,6 +109,43 @@ def test_build_and_test_package(): event_loop.close() +@pytest.mark.skipif( + not shutil.which('cargo'), + reason='Rust must be installed to run this test') +def test_skip_pure_library_package(): + event_loop = new_event_loop() + asyncio.set_event_loop(event_loop) + + try: + cpi = CargoPackageIdentification() + package = PackageDescriptor(pure_library_path) + cpi.identify(package) + + with tempfile.TemporaryDirectory() as tmpdir: + tmpdir = Path(tmpdir) + # TODO(luca) Also test clean build and cargo args + context = TaskContext(pkg=package, + args=SimpleNamespace( + path=str(pure_library_path), + build_base=str(tmpdir / 'build'), + install_base=str(tmpdir / 'install'), + clean_build=None, + cargo_args=None, + ), + dependencies={} + ) + + task = CargoBuildTask() + task.set_context(context=context) + + # Make sure the task succeeds, even though there is no binary + rc = event_loop.run_until_complete(task.build()) + assert not rc + + finally: + event_loop.close() + + # Check the testing result file, expect cargo test and doc test to fail # but fmt to succeed def check_result_file(path):