diff --git a/.changelog/_unreleased.toml b/.changelog/_unreleased.toml new file mode 100644 index 00000000..a57b66fb --- /dev/null +++ b/.changelog/_unreleased.toml @@ -0,0 +1,5 @@ +[[entries]] +id = "67c15e74-a142-482f-9d2e-972a50161129" +type = "feature" +description = "Make Rust builds respect Cargo.lock when present" +author = "jon.gjengset@helsing.ai" diff --git a/kraken-build/src/kraken/std/cargo/manifest.py b/kraken-build/src/kraken/std/cargo/manifest.py index c5d6536e..e1ad1879 100644 --- a/kraken-build/src/kraken/std/cargo/manifest.py +++ b/kraken-build/src/kraken/std/cargo/manifest.py @@ -70,7 +70,7 @@ class CargoMetadata: target_directory: Path @classmethod - def read(cls, project_dir: Path) -> CargoMetadata: + def read(cls, project_dir: Path, locked: bool | None = None) -> CargoMetadata: cmd = [ "cargo", "metadata", @@ -79,6 +79,16 @@ def read(cls, project_dir: Path) -> CargoMetadata: "--manifest-path", str(project_dir / "Cargo.toml"), ] + if locked is None: + project_dir = project_dir.absolute() + for parent in [project_dir, *project_dir.parents]: + if (parent / "Cargo.lock").exists(): + cmd.append("--locked") + break + elif locked: + # if locked is True, we should *always* pass --locked. + # the expectation is that the command will fail w/o Cargo.lock. + cmd.append("--locked") result = subprocess.run(cmd, stdout=subprocess.PIPE) if result.returncode != 0: logger.error("Stderr: %s", result.stderr) diff --git a/kraken-build/src/kraken/std/cargo/tasks/cargo_build_task.py b/kraken-build/src/kraken/std/cargo/tasks/cargo_build_task.py index fac0d015..3d019031 100644 --- a/kraken-build/src/kraken/std/cargo/tasks/cargo_build_task.py +++ b/kraken-build/src/kraken/std/cargo/tasks/cargo_build_task.py @@ -5,6 +5,7 @@ import subprocess as sp import time from dataclasses import dataclass +from pathlib import Path from kraken.core import Project, Property, Task, TaskStatus from kraken.std.cargo.manifest import ArtifactKind, CargoMetadata @@ -35,6 +36,11 @@ class CargoBuildTask(Task): #: Whether to build incrementally or not. incremental: Property[bool | None] = Property.default(None) + #: Whether to pass --locked to cargo or not. + #: + #: When set to None, --locked is passed if Cargo.lock exists. + locked: Property[bool | None] = Property.default(None) + #: Environment variables for the Cargo command. env: Property[dict[str, str]] = Property.default_factory(dict) @@ -58,11 +64,31 @@ def get_description(self) -> str | None: def get_cargo_command_additional_flags(self) -> list[str]: return shlex.split(os.environ.get("KRAKEN_CARGO_BUILD_FLAGS", "")) + def should_add_locked_flag(self) -> bool: + locked = self.locked.get() + if locked is None: + # pass --locked if we have a lock file + # since we may be in a workspace member, we need to search up! + for parent in (Path.cwd() / "Cargo.toml").parents: + if (parent / "Cargo.lock").exists(): + return True + elif locked: + # if locked is True, we should *always* pass --locked. + # the expectation is that the command will fail w/o Cargo.lock. + return True + return False + + def get_additional_args(self) -> list[str]: + args = self.additional_args.get() + if "--locked" not in args and self.should_add_locked_flag(): + args = ["--locked", *args] + return args + def get_cargo_command(self, env: dict[str, str]) -> list[str]: incremental = self.incremental.get() if incremental is not None: env["CARGO_INCREMENTAL"] = "1" if incremental else "0" - return ["cargo", "build"] + self.additional_args.get() + return ["cargo", "build"] + self.get_additional_args() def make_safe(self, args: list[str], env: dict[str, str]) -> None: pass diff --git a/kraken-build/src/kraken/std/cargo/tasks/cargo_publish_task.py b/kraken-build/src/kraken/std/cargo/tasks/cargo_publish_task.py index be57ab8a..332c7c21 100644 --- a/kraken-build/src/kraken/std/cargo/tasks/cargo_publish_task.py +++ b/kraken-build/src/kraken/std/cargo/tasks/cargo_publish_task.py @@ -35,6 +35,7 @@ def get_cargo_command(self, env: dict[str, str]) -> list[str]: raise ValueError(f'registry {registry.alias!r} missing a "publish_token"') command = ( ["cargo", "publish"] + + (["--locked"] if self.should_add_locked_flag() else []) + self.additional_args.get() + ["--registry", registry.alias, "--token", registry.publish_token] + ([] if self.verify.get() else ["--no-verify"]) diff --git a/kraken-build/src/kraken/std/cargo/tasks/cargo_test_task.py b/kraken-build/src/kraken/std/cargo/tasks/cargo_test_task.py index 8d0c4a03..1ad8daf2 100644 --- a/kraken-build/src/kraken/std/cargo/tasks/cargo_test_task.py +++ b/kraken-build/src/kraken/std/cargo/tasks/cargo_test_task.py @@ -9,4 +9,4 @@ class CargoTestTask(CargoBuildTask): def get_cargo_command(self, env: dict[str, str]) -> list[str]: super().get_cargo_command(env) - return ["cargo", "test"] + self.additional_args.get() + return ["cargo", "test"] + self.get_additional_args()