Fixed `crates_repository` deleting `.cargo/config.toml` files. (#1227)
* Fixed `crates_repository` deleting `.cargo/config.toml` files.
* Added example `config.toml`
* Updated lockfile.
* Ensure parent directories do not have cargo configs
* Added tests
* Update crate_universe/src/splicing/splicer.rs
Co-authored-by: Daniel Wagner-Hall <dawagner@gmail.com>
* Run splicing in temp directories to avoid configs in home directories
* Update crate_universe/src/cli/splice.rs
Co-authored-by: Daniel Wagner-Hall <dawagner@gmail.com>
* rustfmt
Co-authored-by: Daniel Wagner-Hall <dawagner@gmail.com>
diff --git a/crate_universe/private/splicing_utils.bzl b/crate_universe/private/splicing_utils.bzl
index a419c75..249e7a4 100644
--- a/crate_universe/private/splicing_utils.bzl
+++ b/crate_universe/private/splicing_utils.bzl
@@ -2,6 +2,8 @@
load(":common_utils.bzl", "cargo_environ", "execute")
+CARGO_BAZEL_DEBUG = "CARGO_BAZEL_DEBUG"
+
def splicing_config(resolver_version = "1"):
"""arious settings used to configure Cargo manifest splicing behavior.
@@ -170,14 +172,14 @@
), indent = " " * 4),
)
- cargo_workspace = repository_ctx.path("{}/cargo-bazel-splicing".format(repo_dir))
+ splicing_output_dir = repository_ctx.path("splicing-output")
# Generate a workspace root which contains all workspace members
arguments = [
generator,
"splice",
- "--workspace-dir",
- cargo_workspace,
+ "--output-dir",
+ splicing_output_dir,
"--splicing-manifest",
splicing_manifest,
"--extra-manifests-manifest",
@@ -188,6 +190,14 @@
rustc,
]
+ # Optionally set the splicing workspace directory to somewhere within the repository directory
+ # to improve the debugging experience.
+ if CARGO_BAZEL_DEBUG in repository_ctx.os.environ:
+ arguments.extend([
+ "--workspace-dir",
+ repository_ctx.path("{}/splicing-workspace".format(repo_dir)),
+ ])
+
# Splicing accepts a Cargo.lock file in some scenarios. Ensure it's passed
# if the lockfile is a actually a Cargo lockfile.
if lockfile.kind == "cargo":
@@ -211,13 +221,12 @@
env = env,
)
- root_manifest = repository_ctx.path("{}/Cargo.toml".format(cargo_workspace))
- if not root_manifest.exists:
- fail("Root manifest does not exist: {}".format(root_manifest))
+ # This file must have been produced by the execution above.
+ spliced_lockfile = repository_ctx.path("{}/Cargo.lock".format(splicing_output_dir))
+ if not spliced_lockfile.exists:
+ fail("Lockfile file does not exist: {}".format(spliced_lockfile))
+ spliced_metadata = repository_ctx.path("{}/metadata.json".format(splicing_output_dir))
+ if not spliced_metadata.exists:
+ fail("Metadata file does not exist: {}".format(spliced_metadata))
- # This file must match the one generated in splicing
- metadata_path = repository_ctx.path("{}/cargo-bazel-spliced-metadata.json".format(cargo_workspace))
- if not metadata_path.exists:
- fail("Root metadata file does not exist: {}".format(metadata_path))
-
- return metadata_path
+ return spliced_metadata
diff --git a/crate_universe/src/cli/splice.rs b/crate_universe/src/cli/splice.rs
index cb8ba20..e8e72ee 100644
--- a/crate_universe/src/cli/splice.rs
+++ b/crate_universe/src/cli/splice.rs
@@ -2,6 +2,7 @@
use std::path::PathBuf;
+use anyhow::Context;
use clap::Parser;
use crate::cli::Result;
@@ -26,10 +27,14 @@
#[clap(long)]
pub cargo_lockfile: Option<PathBuf>,
- /// The directory in which to build the workspace. A `Cargo.toml` file
- /// should always be produced within this directory.
+ /// The directory in which to build the workspace. If this argument is not
+ /// passed, a temporary directory will be generated.
#[clap(long)]
- pub workspace_dir: PathBuf,
+ pub workspace_dir: Option<PathBuf>,
+
+ /// The location where the results of splicing are written.
+ #[clap(long)]
+ pub output_dir: PathBuf,
/// If true, outputs will be printed instead of written to disk.
#[clap(long)]
@@ -55,12 +60,18 @@
let extra_manifests_manifest =
ExtraManifestsManifest::try_from_path(opt.extra_manifests_manifest)?;
+ // Determine the splicing workspace
+ let temp_dir;
+ let splicing_dir = match &opt.workspace_dir {
+ Some(dir) => dir.clone(),
+ None => {
+ temp_dir = tempfile::tempdir().context("Failed to generate temporary directory")?;
+ temp_dir.as_ref().to_path_buf()
+ }
+ };
+
// Generate a splicer for creating a Cargo workspace manifest
- let splicer = Splicer::new(
- opt.workspace_dir,
- splicing_manifest,
- extra_manifests_manifest,
- )?;
+ let splicer = Splicer::new(splicing_dir, splicing_manifest, extra_manifests_manifest)?;
// Splice together the manifest
let manifest_path = splicer.splice_workspace()?;
@@ -72,19 +83,33 @@
// Write the registry url info to the manifest now that a lockfile has been generated
WorkspaceMetadata::write_registry_urls(&cargo_lockfile, &manifest_path)?;
+ let output_dir = opt.output_dir.clone();
+
// Write metadata to the workspace for future reuse
let (cargo_metadata, _) = Generator::new()
.with_cargo(opt.cargo)
.with_rustc(opt.rustc)
.generate(&manifest_path.as_path_buf())?;
- // Write metadata next to the manifest
- let metadata_path = manifest_path
+ let cargo_lockfile_path = manifest_path
.as_path_buf()
.parent()
- .expect("Newly spliced cargo manifest has no parent directory")
- .join("cargo-bazel-spliced-metadata.json");
- write_metadata(&metadata_path, &cargo_metadata)?;
+ .with_context(|| {
+ format!(
+ "The path {} is expected to have a parent directory",
+ manifest_path.as_path_buf().display()
+ )
+ })?
+ .join("Cargo.lock");
+
+ // Generate the consumable outputs of the splicing process
+ std::fs::create_dir_all(&output_dir)
+ .with_context(|| format!("Failed to create directories for {}", &output_dir.display()))?;
+
+ write_metadata(&opt.output_dir.join("metadata.json"), &cargo_metadata)?;
+
+ std::fs::copy(cargo_lockfile_path, output_dir.join("Cargo.lock"))
+ .context("Failed to copy lockfile")?;
Ok(())
}
diff --git a/crate_universe/src/splicing/splicer.rs b/crate_universe/src/splicing/splicer.rs
index f4afde7..747f3ef 100644
--- a/crate_universe/src/splicing/splicer.rs
+++ b/crate_universe/src/splicing/splicer.rs
@@ -344,21 +344,6 @@
/// A helper for installing Cargo config files into the spliced workspace while also
/// ensuring no other linked config file is available
fn setup_cargo_config(cargo_config_path: &Option<PathBuf>, workspace_dir: &Path) -> Result<()> {
- // Make sure no other config files exist
- for config in vec![
- workspace_dir.join("config"),
- workspace_dir.join("config.toml"),
- ] {
- if config.exists() {
- fs::remove_file(&config).with_context(|| {
- format!(
- "Failed to delete existing cargo config: {}",
- config.display()
- )
- })?;
- }
- }
-
// If the `.cargo` dir is a symlink, we'll need to relink it and ensure
// a Cargo config file is omitted
let dot_cargo_dir = workspace_dir.join(".cargo");
@@ -383,12 +368,53 @@
dot_cargo_dir.join("config.toml"),
] {
if config.exists() {
- fs::remove_file(&config)?;
+ remove_symlink(&config).with_context(|| {
+ format!(
+ "Failed to delete existing cargo config: {}",
+ config.display()
+ )
+ })?;
}
}
}
}
+ // Make sure no other config files exist
+ for config in vec![
+ workspace_dir.join("config"),
+ workspace_dir.join("config.toml"),
+ dot_cargo_dir.join("config"),
+ dot_cargo_dir.join("config.toml"),
+ ] {
+ if config.exists() {
+ remove_symlink(&config).with_context(|| {
+ format!(
+ "Failed to delete existing cargo config: {}",
+ config.display()
+ )
+ })?;
+ }
+ }
+
+ // Ensure no parent directory also has a cargo config
+ let mut current_parent = workspace_dir.parent();
+ while let Some(parent) = current_parent {
+ let dot_cargo_dir = parent.join(".cargo");
+ for config in vec![
+ dot_cargo_dir.join("config.toml"),
+ dot_cargo_dir.join("config"),
+ ] {
+ if config.exists() {
+ bail!(
+ "A Cargo config file was found in a parent directory to the current workspace. This is not allowed because these settings will leak into your Bazel build but will not be reproducible on other machines.\nWorkspace = {}\nCargo config = {}",
+ workspace_dir.display(),
+ config.display(),
+ )
+ }
+ }
+ current_parent = parent.parent()
+ }
+
// Install the new config file after having removed all others
if let Some(cargo_config_path) = cargo_config_path {
if !dot_cargo_dir.exists() {
@@ -1440,4 +1466,100 @@
// Ensure lockfile was successfully spliced
cargo_lock::Lockfile::load(workspace_root.as_ref().join("Cargo.lock")).unwrap();
}
+
+ #[test]
+ fn cargo_config_setup() {
+ let (mut splicing_manifest, _cache_dir) = mock_splicing_manifest_with_workspace_in_root();
+
+ // Write a cargo config
+ let temp_dir = tempfile::tempdir().unwrap();
+ let external_config = temp_dir.as_ref().join("config.toml");
+ fs::write(&external_config, "# Cargo configuration file").unwrap();
+ splicing_manifest.cargo_config = Some(external_config);
+
+ // Splice the workspace
+ let workspace_root = tempfile::tempdir().unwrap();
+ Splicer::new(
+ workspace_root.as_ref().to_path_buf(),
+ splicing_manifest,
+ ExtraManifestsManifest::default(),
+ )
+ .unwrap()
+ .splice_workspace()
+ .unwrap();
+
+ let cargo_config = workspace_root.as_ref().join(".cargo").join("config.toml");
+ assert!(cargo_config.exists());
+ assert_eq!(
+ fs::read_to_string(cargo_config).unwrap().trim(),
+ "# Cargo configuration file"
+ );
+ }
+
+ #[test]
+ fn unregistered_cargo_config_replaced() {
+ let (mut splicing_manifest, cache_dir) = mock_splicing_manifest_with_workspace_in_root();
+
+ // Generate a cargo config that is not tracked by the splicing manifest
+ fs::create_dir_all(cache_dir.as_ref().join(".cargo")).unwrap();
+ fs::write(
+ cache_dir.as_ref().join(".cargo").join("config.toml"),
+ "# Untracked Cargo configuration file",
+ )
+ .unwrap();
+
+ // Write a cargo config
+ let temp_dir = tempfile::tempdir().unwrap();
+ let external_config = temp_dir.as_ref().join("config.toml");
+ fs::write(&external_config, "# Cargo configuration file").unwrap();
+ splicing_manifest.cargo_config = Some(external_config);
+
+ // Splice the workspace
+ let workspace_root = tempfile::tempdir().unwrap();
+ Splicer::new(
+ workspace_root.as_ref().to_path_buf(),
+ splicing_manifest,
+ ExtraManifestsManifest::default(),
+ )
+ .unwrap()
+ .splice_workspace()
+ .unwrap();
+
+ let cargo_config = workspace_root.as_ref().join(".cargo").join("config.toml");
+ assert!(cargo_config.exists());
+ assert_eq!(
+ fs::read_to_string(cargo_config).unwrap().trim(),
+ "# Cargo configuration file"
+ );
+ }
+
+ #[test]
+ fn error_on_cargo_config_in_parent() {
+ let (mut splicing_manifest, _cache_dir) = mock_splicing_manifest_with_workspace_in_root();
+
+ // Write a cargo config
+ let temp_dir = tempfile::tempdir().unwrap();
+ let dot_cargo_dir = temp_dir.as_ref().join(".cargo");
+ fs::create_dir_all(&dot_cargo_dir).unwrap();
+ let external_config = dot_cargo_dir.join("config.toml");
+ fs::write(&external_config, "# Cargo configuration file").unwrap();
+ splicing_manifest.cargo_config = Some(external_config.clone());
+
+ // Splice the workspace
+ let workspace_root = temp_dir.as_ref().join("workspace_root");
+ let splicing_result = Splicer::new(
+ workspace_root.clone(),
+ splicing_manifest,
+ ExtraManifestsManifest::default(),
+ )
+ .unwrap()
+ .splice_workspace();
+
+ // Ensure cargo config files in parent directories lead to errors
+ assert!(splicing_result.is_err());
+ let err_str = splicing_result.err().unwrap().to_string();
+ assert!(err_str.starts_with("A Cargo config file was found in a parent directory"));
+ assert!(err_str.contains(&format!("Workspace = {}", workspace_root.display())));
+ assert!(err_str.contains(&format!("Cargo config = {}", external_config.display())));
+ }
}
diff --git a/examples/crate_universe/WORKSPACE.bazel b/examples/crate_universe/WORKSPACE.bazel
index f78b777..2ae229a 100644
--- a/examples/crate_universe/WORKSPACE.bazel
+++ b/examples/crate_universe/WORKSPACE.bazel
@@ -105,6 +105,7 @@
crates_repository(
name = "crate_index_cargo_workspace",
+ cargo_config = "//cargo_workspace:.cargo/config.toml",
# `generator` is not necessary in official releases.
# See load satement for `cargo_bazel_bootstrap`.
generator = "@cargo_bazel_bootstrap//:cargo-bazel",
diff --git a/examples/crate_universe/cargo_workspace/.cargo/config.toml b/examples/crate_universe/cargo_workspace/.cargo/config.toml
new file mode 100644
index 0000000..1e04f5c
--- /dev/null
+++ b/examples/crate_universe/cargo_workspace/.cargo/config.toml
@@ -0,0 +1,2 @@
+# This file can contain cargo settings. For more details see:
+# https://doc.rust-lang.org/cargo/reference/config.html
diff --git a/examples/crate_universe/cargo_workspace/BUILD.bazel b/examples/crate_universe/cargo_workspace/BUILD.bazel
index e69de29..22293b9 100644
--- a/examples/crate_universe/cargo_workspace/BUILD.bazel
+++ b/examples/crate_universe/cargo_workspace/BUILD.bazel
@@ -0,0 +1,4 @@
+exports_files(
+ [".cargo/config.toml"],
+ visibility = ["//visibility:public"],
+)
diff --git a/examples/crate_universe/cargo_workspace/Cargo.Bazel.lock b/examples/crate_universe/cargo_workspace/Cargo.Bazel.lock
index 87a70c0..b95ed75 100644
--- a/examples/crate_universe/cargo_workspace/Cargo.Bazel.lock
+++ b/examples/crate_universe/cargo_workspace/Cargo.Bazel.lock
@@ -1,5 +1,5 @@
{
- "checksum": "19de3c063492d2c19858f606defde13feda1682fb84231a931e4c7958bb6bcd0",
+ "checksum": "ed5260c74ff6f8ccb2f28cd0e31b248aab3e110c7fd0321a9e697cf96ab659eb",
"crates": {
"ansi_term 0.12.1": {
"name": "ansi_term",
@@ -84,7 +84,7 @@
],
"cfg(unix)": [
{
- "id": "libc 0.2.120",
+ "id": "libc 0.2.121",
"target": "libc"
}
],
@@ -389,7 +389,7 @@
],
"cfg(unix)": [
{
- "id": "libc 0.2.120",
+ "id": "libc 0.2.121",
"target": "libc"
}
]
@@ -439,7 +439,7 @@
"deps": {
"common": [
{
- "id": "libc 0.2.120",
+ "id": "libc 0.2.121",
"target": "libc"
}
],
@@ -450,13 +450,13 @@
},
"license": "MIT/Apache-2.0"
},
- "libc 0.2.120": {
+ "libc 0.2.121": {
"name": "libc",
- "version": "0.2.120",
+ "version": "0.2.121",
"repository": {
"Http": {
- "url": "https://crates.io/api/v1/crates/libc/0.2.120/download",
- "sha256": "ad5c14e80759d0939d013e6ca49930e59fc53dd8e5009132f76240c179380c09"
+ "url": "https://crates.io/api/v1/crates/libc/0.2.121/download",
+ "sha256": "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f"
}
},
"targets": [
@@ -493,14 +493,14 @@
"deps": {
"common": [
{
- "id": "libc 0.2.120",
+ "id": "libc 0.2.121",
"target": "build_script_build"
}
],
"selects": {}
},
"edition": "2015",
- "version": "0.2.120"
+ "version": "0.2.121"
},
"build_script_attrs": {
"data_glob": [
@@ -683,7 +683,7 @@
],
"cfg(unix)": [
{
- "id": "libc 0.2.120",
+ "id": "libc 0.2.121",
"target": "libc"
}
]