Skip to content

Commit 6d0d74e

Browse files
committed
Improve workspace discovery logic
Previously we were assuming that any crate with a path dep lives in a workspace, and any that doesn't isn't. This isn't correct logic. https://doc.rust-lang.org/cargo/reference/workspaces.html describes the specification. We do filesystem traversal to detect implicit members. This is a little more expensive, but much more correct. This helps supporting path dependencies - the current incorrect logic incorrectly identifies any Cargo.toml file containing a path dependency as expecting to live in a workspace, which breaks some use-cases.
1 parent ba0c503 commit 6d0d74e

File tree

37 files changed

+780
-545
lines changed

37 files changed

+780
-545
lines changed

crate_universe/Cargo.toml

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,46 @@
11
[workspace]
22
members = ["tools/cross_installer", "tools/urls_generator"]
3-
exclude = ["test_data"]
3+
exclude = [
4+
"test_data/metadata/abspath",
5+
"test_data/metadata/aliases",
6+
"test_data/metadata/build_scripts",
7+
"test_data/metadata/common",
8+
"test_data/metadata/crate_combined_features",
9+
"test_data/metadata/crate_optional_deps_disabled",
10+
"test_data/metadata/crate_optional_deps_disabled_build_dep_enabled",
11+
"test_data/metadata/crate_optional_deps_enabled",
12+
"test_data/metadata/crate_renamed_optional_deps_disabled",
13+
"test_data/metadata/crate_renamed_optional_deps_enabled",
14+
"test_data/metadata/crate_types",
15+
"test_data/metadata/example_proc_macro_dep",
16+
"test_data/metadata/git_repos",
17+
"test_data/metadata/has_package_metadata",
18+
"test_data/metadata/host_specific_build_deps",
19+
"test_data/metadata/multi_cfg_dep",
20+
"test_data/metadata/multi_kind_proc_macro_dep",
21+
"test_data/metadata/nested_build_dependencies",
22+
"test_data/metadata/no_deps",
23+
"test_data/metadata/resolver_2_deps",
24+
"test_data/metadata/target_cfg_features",
25+
"test_data/metadata/target_features",
26+
"test_data/metadata/tree_data",
27+
"test_data/metadata/workspace",
28+
"test_data/metadata/workspace/child",
29+
"test_data/metadata/workspace_path",
30+
"test_data/metadata/workspace_path/child_a",
31+
"test_data/metadata/workspace_path/child_b",
32+
"test_data/test_data_passing_crate",
33+
"test_data/workspace_examples/non-ws",
34+
"test_data/workspace_examples/ws1",
35+
"test_data/workspace_examples/ws1/ws1c1",
36+
"test_data/workspace_examples/ws1/ws1c1/ws1c1c1",
37+
"test_data/workspace_examples/ws1/ws1c2",
38+
"test_data/workspace_examples/ws2",
39+
"test_data/workspace_examples/ws2/ws2c1",
40+
"test_data/workspace_examples/ws2/ws2excluded",
41+
"test_data/workspace_examples/ws2/ws2excluded/ws2excluded2",
42+
"test_data/workspace_examples/ws2/ws2excluded/ws2included",
43+
]
444

545
[package]
646
name = "cargo-bazel"

crate_universe/extensions.bzl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ def _generate_hub_and_spokes(*, module_ctx, cargo_bazel, cfg, annotations, cargo
112112
),
113113
)
114114

115+
nonhermetic_root_bazel_workspace_dir = module_ctx.path(Label("@@//:MODULE.bazel")).dirname
116+
115117
splicing_output_dir = tag_path.get_child("splicing-output")
116118
splice_args = [
117119
"splice",
@@ -121,6 +123,8 @@ def _generate_hub_and_spokes(*, module_ctx, cargo_bazel, cfg, annotations, cargo
121123
config_file,
122124
"--splicing-manifest",
123125
splicing_manifest,
126+
"--nonhermetic-root-bazel-workspace-dir",
127+
nonhermetic_root_bazel_workspace_dir,
124128
]
125129
if cargo_lockfile:
126130
splice_args.extend([
@@ -153,7 +157,7 @@ def _generate_hub_and_spokes(*, module_ctx, cargo_bazel, cfg, annotations, cargo
153157
"--lockfile",
154158
lockfile_path,
155159
"--nonhermetic-root-bazel-workspace-dir",
156-
module_ctx.path(Label("@@//:MODULE.bazel")).dirname,
160+
nonhermetic_root_bazel_workspace_dir,
157161
"--paths-to-track",
158162
paths_to_track_file,
159163
"--warnings-output-path",

crate_universe/private/splicing_utils.bzl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ def splice_workspace_manifest(repository_ctx, generator, cargo_lockfile, splicin
149149
rustc,
150150
"--cargo-lockfile",
151151
cargo_lockfile,
152+
"--nonhermetic-root-bazel-workspace-dir",
153+
repository_ctx.workspace_root,
152154
]
153155

154156
# Optionally set the splicing workspace directory to somewhere within the repository directory

crate_universe/private/srcs.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ CARGO_BAZEL_SRCS = [
2828
Label("//crate_universe:src/metadata/cargo_tree_rustc_wrapper.sh"),
2929
Label("//crate_universe:src/metadata/dependency.rs"),
3030
Label("//crate_universe:src/metadata/metadata_annotation.rs"),
31+
Label("//crate_universe:src/metadata/workspace_discoverer.rs"),
3132
Label("//crate_universe:src/rendering.rs"),
3233
Label("//crate_universe:src/rendering/template_engine.rs"),
3334
Label("//crate_universe:src/rendering/templates/module_bzl.j2"),

crate_universe/src/cli/splice.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use std::path::PathBuf;
44

55
use anyhow::Context;
6+
use camino::Utf8PathBuf;
67
use clap::Parser;
78

89
use crate::cli::Result;
@@ -31,7 +32,7 @@ pub struct SpliceOptions {
3132
/// The directory in which to build the workspace. If this argument is not
3233
/// passed, a temporary directory will be generated.
3334
#[clap(long)]
34-
pub workspace_dir: Option<PathBuf>,
35+
pub workspace_dir: Option<Utf8PathBuf>,
3536

3637
/// The location where the results of splicing are written.
3738
#[clap(long)]
@@ -56,6 +57,9 @@ pub struct SpliceOptions {
5657
/// The path to a rustc binary for use with Cargo
5758
#[clap(long, env = "RUSTC")]
5859
pub rustc: PathBuf,
60+
61+
#[clap(long)]
62+
pub nonhermetic_root_bazel_workspace_dir: PathBuf,
5963
}
6064

6165
/// Combine a set of disjoint manifests into a single workspace.
@@ -70,7 +74,8 @@ pub fn splice(opt: SpliceOptions) -> Result<()> {
7074
Some(dir) => dir.clone(),
7175
None => {
7276
temp_dir = tempfile::tempdir().context("Failed to generate temporary directory")?;
73-
temp_dir.as_ref().to_path_buf()
77+
Utf8PathBuf::from_path_buf(temp_dir.as_ref().to_path_buf())
78+
.unwrap_or_else(|path| panic!("Temporary directory wasn't valid UTF-8: {:?}", path))
7479
}
7580
};
7681

@@ -81,7 +86,7 @@ pub fn splice(opt: SpliceOptions) -> Result<()> {
8186

8287
// Splice together the manifest
8388
let manifest_path = splicer
84-
.splice_workspace(&cargo)
89+
.splice_workspace(&opt.nonhermetic_root_bazel_workspace_dir)
8590
.context("Failed to splice workspace")?;
8691

8792
// Generate a lockfile
@@ -126,7 +131,7 @@ pub fn splice(opt: SpliceOptions) -> Result<()> {
126131
.with_context(|| {
127132
format!(
128133
"The path {} is expected to have a parent directory",
129-
manifest_path.as_path_buf().display()
134+
manifest_path.as_path_buf()
130135
)
131136
})?
132137
.join("Cargo.lock");

crate_universe/src/cli/vendor.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,16 +130,18 @@ pub fn vendor(opt: VendorOptions) -> Result<()> {
130130
.resolve(&opt.workspace_dir, &output_base);
131131

132132
let temp_dir = tempfile::tempdir().context("Failed to create temporary directory")?;
133+
let temp_dir_path = Utf8PathBuf::from_path_buf(temp_dir.as_ref().to_path_buf())
134+
.unwrap_or_else(|path| panic!("Temporary directory wasn't valid UTF-8: {:?}", path));
133135

134136
// Generate a splicer for creating a Cargo workspace manifest
135-
let splicer = Splicer::new(PathBuf::from(temp_dir.as_ref()), splicing_manifest)
136-
.context("Failed to create splicer")?;
137+
let splicer =
138+
Splicer::new(temp_dir_path, splicing_manifest).context("Failed to create splicer")?;
137139

138140
let cargo = Cargo::new(opt.cargo, opt.rustc.clone());
139141

140142
// Splice together the manifest
141143
let manifest_path = splicer
142-
.splice_workspace(&cargo)
144+
.splice_workspace(opt.nonhermetic_root_bazel_workspace_dir.as_std_path())
143145
.context("Failed to splice workspace")?;
144146

145147
// Gather a cargo lockfile

crate_universe/src/metadata.rs

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ mod cargo_bin;
44
mod cargo_tree_resolver;
55
mod dependency;
66
mod metadata_annotation;
7+
mod workspace_discoverer;
78

89
use std::env;
910
use std::fs;
1011
use std::path::{Path, PathBuf};
1112
use std::str::FromStr;
1213

1314
use anyhow::{bail, Context, Result};
15+
use camino::Utf8Path;
1416
use cargo_lock::Lockfile as CargoLockfile;
1517
use cargo_metadata::Metadata as CargoMetadata;
1618
use tracing::debug;
@@ -19,6 +21,7 @@ pub(crate) use self::cargo_bin::*;
1921
pub(crate) use self::cargo_tree_resolver::*;
2022
pub(crate) use self::dependency::*;
2123
pub(crate) use self::metadata_annotation::*;
24+
pub(crate) use self::workspace_discoverer::*;
2225

2326
// TODO: This should also return a set of [crate-index::IndexConfig]s for packages in metadata.packages
2427
/// A Trait for generating metadata (`cargo metadata` output and a lock file) from a Cargo manifest.
@@ -187,11 +190,11 @@ impl LockGenerator {
187190
#[tracing::instrument(name = "LockGenerator::generate", skip_all)]
188191
pub(crate) fn generate(
189192
&self,
190-
manifest_path: &Path,
193+
manifest_path: &Utf8Path,
191194
existing_lock: &Option<PathBuf>,
192195
update_request: &Option<CargoUpdateRequest>,
193196
) -> Result<cargo_lock::Lockfile> {
194-
debug!("Generating Cargo Lockfile for {}", manifest_path.display());
197+
debug!("Generating Cargo Lockfile for {}", manifest_path);
195198

196199
let manifest_dir = manifest_path.parent().unwrap();
197200
let generated_lockfile_path = manifest_dir.join("Cargo.lock");
@@ -212,7 +215,7 @@ impl LockGenerator {
212215
fs::copy(lock, &generated_lockfile_path)?;
213216

214217
if let Some(request) = update_request {
215-
request.update(manifest_path, &self.cargo_bin)?;
218+
request.update(manifest_path.as_std_path(), &self.cargo_bin)?;
216219
}
217220

218221
// Ensure the Cargo cache is up to date to simulate the behavior
@@ -223,14 +226,14 @@ impl LockGenerator {
223226
// Cargo detects config files based on `pwd` when running so
224227
// to ensure user provided Cargo config files are used, it's
225228
// critical to set the working directory to the manifest dir.
226-
.current_dir(manifest_dir)
229+
.current_dir(manifest_dir.as_std_path())
227230
.arg("fetch")
228231
.arg("--manifest-path")
229-
.arg(manifest_path)
232+
.arg(manifest_path.as_std_path())
230233
.output()
231234
.context(format!(
232235
"Error running cargo to fetch crates '{}'",
233-
manifest_path.display()
236+
manifest_path
234237
))?;
235238

236239
if !output.status.success() {
@@ -250,14 +253,14 @@ impl LockGenerator {
250253
// Cargo detects config files based on `pwd` when running so
251254
// to ensure user provided Cargo config files are used, it's
252255
// critical to set the working directory to the manifest dir.
253-
.current_dir(manifest_dir)
256+
.current_dir(manifest_dir.as_std_path())
254257
.arg("generate-lockfile")
255258
.arg("--manifest-path")
256-
.arg(manifest_path)
259+
.arg(manifest_path.as_std_path())
257260
.output()
258261
.context(format!(
259262
"Error running cargo to generate lockfile '{}'",
260-
manifest_path.display()
263+
manifest_path
261264
))?;
262265

263266
if !output.status.success() {
@@ -269,7 +272,7 @@ impl LockGenerator {
269272

270273
cargo_lock::Lockfile::load(&generated_lockfile_path).context(format!(
271274
"Failed to load lockfile: {}",
272-
generated_lockfile_path.display()
275+
generated_lockfile_path
273276
))
274277
}
275278
}
@@ -291,12 +294,8 @@ impl VendorGenerator {
291294
}
292295
}
293296
#[tracing::instrument(name = "VendorGenerator::generate", skip_all)]
294-
pub(crate) fn generate(&self, manifest_path: &Path, output_dir: &Path) -> Result<()> {
295-
debug!(
296-
"Vendoring {} to {}",
297-
manifest_path.display(),
298-
output_dir.display()
299-
);
297+
pub(crate) fn generate(&self, manifest_path: &Utf8Path, output_dir: &Path) -> Result<()> {
298+
debug!("Vendoring {} to {}", manifest_path, output_dir.display());
300299
let manifest_dir = manifest_path.parent().unwrap();
301300

302301
// Simply invoke `cargo generate-lockfile`
@@ -306,10 +305,10 @@ impl VendorGenerator {
306305
// Cargo detects config files based on `pwd` when running so
307306
// to ensure user provided Cargo config files are used, it's
308307
// critical to set the working directory to the manifest dir.
309-
.current_dir(manifest_dir)
308+
.current_dir(manifest_dir.as_std_path())
310309
.arg("vendor")
311310
.arg("--manifest-path")
312-
.arg(manifest_path)
311+
.arg(manifest_path.as_std_path())
313312
.arg("--locked")
314313
.arg("--versioned-dirs")
315314
.arg(output_dir)
@@ -318,7 +317,7 @@ impl VendorGenerator {
318317
.with_context(|| {
319318
format!(
320319
"Error running cargo to vendor sources for manifest '{}'",
321-
manifest_path.display()
320+
manifest_path
322321
)
323322
})?;
324323

crate_universe/src/metadata/cargo_bin.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,6 @@ impl Cargo {
3333
}
3434
}
3535

36-
#[cfg(test)]
37-
pub(crate) fn with_cargo_home(mut self, path: PathBuf) -> Cargo {
38-
self.cargo_home = Some(path);
39-
self
40-
}
41-
4236
/// Returns a new `Command` for running this cargo.
4337
pub(crate) fn command(&self) -> Result<Command> {
4438
let mut command = Command::new(&self.path);

crate_universe/src/metadata/cargo_tree_resolver.rs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::path::{Path, PathBuf};
66
use std::process::Child;
77

88
use anyhow::{anyhow, bail, Context, Result};
9+
use camino::Utf8Path;
910
use semver::Version;
1011
use serde::{Deserialize, Serialize};
1112
use tracing::debug;
@@ -303,12 +304,12 @@ impl TreeResolver {
303304
#[tracing::instrument(name = "TreeResolver::generate", skip_all)]
304305
pub(crate) fn generate(
305306
&self,
306-
pristine_manifest_path: &Path,
307+
pristine_manifest_path: &Utf8Path,
307308
target_triples: &BTreeSet<TargetTriple>,
308309
) -> Result<TreeResolverMetadata> {
309310
debug!(
310311
"Generating features for manifest {}",
311-
pristine_manifest_path.display()
312+
pristine_manifest_path
312313
);
313314

314315
let tempdir = tempfile::tempdir().context("Failed to make tempdir")?;
@@ -446,7 +447,7 @@ impl TreeResolver {
446447
// and if we don't have this fake root injection, cross-compiling from Darwin to Linux won't work because features don't get correctly resolved for the exec=darwin case.
447448
fn copy_project_with_explicit_deps_on_all_transitive_proc_macros(
448449
&self,
449-
pristine_manifest_path: &Path,
450+
pristine_manifest_path: &Utf8Path,
450451
output_dir: &Path,
451452
) -> Result<PathBuf> {
452453
if !output_dir.exists() {
@@ -480,8 +481,11 @@ impl TreeResolver {
480481

481482
let cargo_metadata = self
482483
.cargo_bin
483-
.metadata_command_with_options(pristine_manifest_path, vec!["--locked".to_owned()])?
484-
.manifest_path(pristine_manifest_path)
484+
.metadata_command_with_options(
485+
pristine_manifest_path.as_std_path(),
486+
vec!["--locked".to_owned()],
487+
)?
488+
.manifest_path(pristine_manifest_path.as_std_path())
485489
.exec()
486490
.context("Failed to run cargo metadata to list transitive proc macros")?;
487491
let proc_macros = cargo_metadata
@@ -517,10 +521,10 @@ impl TreeResolver {
517521
})
518522
.collect::<Result<BTreeSet<_>>>()?;
519523

520-
let mut manifest =
521-
cargo_toml::Manifest::from_path(pristine_manifest_path).with_context(|| {
524+
let mut manifest = cargo_toml::Manifest::from_path(pristine_manifest_path.as_std_path())
525+
.with_context(|| {
522526
format!(
523-
"Failed to parse Cargo.toml file at {:?}",
527+
"Failed to parse Cargo.toml file at {}",
524528
pristine_manifest_path
525529
)
526530
})?;

0 commit comments

Comments
 (0)