Skip to content

Commit bc7ba6d

Browse files
malt3stevebarrau
andcommitted
Support splicing manifests from multiple Cargo workspaces
When manifests come from different Cargo workspaces (e.g. a primary workspace depending on crates in a separate external workspace), splicing previously failed with "manifests are not allowed to come from different workspaces". This change: - Adds `main_manifest` to SplicingManifest so the splicer can disambiguate which workspace root is primary when multiple are found. - Nests the spliced workspace at its repo-relative depth inside the temp dir so that `../` path deps resolve within the temp dir instead of escaping into unwritable system directories. - Collects path deps from `[workspace.dependencies]` in addition to per-package deps. - Symlinks entire foreign workspace root directories (not just crate dirs) so Cargo can walk up and resolve `dep.workspace = true` inherited dependencies. - Wires `main_manifest` through extensions.bzl and crates_vendor.bzl. - Calls symlink_external_path_deps from the cargo_tree_resolver so `cargo tree` also sees external path deps. This fixes #3089, along with adding support for multiple workspaces in the splicer. Co-Authored-By: Steve Barrau <98589981+stevebarrau@users.noreply.github.com>
1 parent f5ae457 commit bc7ba6d

7 files changed

Lines changed: 547 additions & 13 deletions

File tree

crate_universe/extensions.bzl

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -562,7 +562,8 @@ def _generate_hub_and_spokes(
562562
strip_internal_dependencies_from_cargo_lockfile,
563563
cargo_lockfile = None,
564564
manifests = {},
565-
packages = {}):
565+
packages = {},
566+
main_manifest = None):
566567
"""Generates repositories for the transitive closure of crates defined by manifests and packages.
567568
568569
Args:
@@ -618,6 +619,7 @@ def _generate_hub_and_spokes(
618619
cargo_config = cfg.cargo_config,
619620
manifests = manifests,
620621
manifest_to_path = module_ctx.path,
622+
main_manifest = main_manifest,
621623
),
622624
)
623625

@@ -1155,10 +1157,13 @@ def _crate_impl(module_ctx):
11551157

11561158
manifests = {}
11571159
packages = {}
1160+
main_manifest = None
11581161

11591162
# Only `from_cargo` instances will have `manifests`.
11601163
if hasattr(cfg, "manifests"):
11611164
manifests = {str(module_ctx.path(m)): str(m) for m in cfg.manifests}
1165+
if cfg.manifests:
1166+
main_manifest = str(module_ctx.path(cfg.manifests[0]))
11621167

11631168
packages = {
11641169
p.package_alias or p.package: _package_to_json(p)
@@ -1178,6 +1183,7 @@ def _crate_impl(module_ctx):
11781183
packages = packages,
11791184
skip_cargo_lockfile_overwrite = cfg.skip_cargo_lockfile_overwrite,
11801185
strip_internal_dependencies_from_cargo_lockfile = cfg.strip_internal_dependencies_from_cargo_lockfile,
1186+
main_manifest = main_manifest,
11811187
)
11821188

11831189
metadata_kwargs = {}

crate_universe/private/crates_vendor.bzl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ def _write_splicing_manifest(ctx):
230230
cargo_config = ctx.attr.cargo_config,
231231
manifests = manifests,
232232
manifest_to_path = _prepare_manifest_path,
233+
main_manifest = _prepare_manifest_path(ctx.attr.manifests[0]) if ctx.attr.manifests else None,
233234
),
234235
)
235236

@@ -240,7 +241,7 @@ def _write_splicing_manifest(ctx):
240241
runfiles = [manifest] + ctx.files.manifests + ([ctx.file.cargo_config] if ctx.attr.cargo_config else [])
241242
return args, env, runfiles
242243

243-
def generate_splicing_manifest(*, packages, splicing_config, cargo_config, manifests, manifest_to_path):
244+
def generate_splicing_manifest(*, packages, splicing_config, cargo_config, manifests, manifest_to_path, main_manifest = None):
244245
# Deserialize information about direct packages
245246
direct_packages_info = {
246247
# Ensure the data is using kebab-case as that's what `cargo_toml::DependencyDetail` expects.
@@ -252,6 +253,7 @@ def generate_splicing_manifest(*, packages, splicing_config, cargo_config, manif
252253
"cargo_config": str(manifest_to_path(cargo_config)) if cargo_config else None,
253254
"direct_packages": direct_packages_info,
254255
"manifests": manifests,
256+
"main_manifest": main_manifest,
255257
}
256258

257259
return json.encode_indent(

crate_universe/src/metadata/cargo_tree_resolver.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use url::Url;
1616
use crate::config::CrateId;
1717
use crate::metadata::cargo_bin::Cargo;
1818
use crate::select::{Select, SelectableScalar};
19+
use crate::splicing::splicer::symlink_external_path_deps;
1920
use crate::utils::symlink::symlink;
2021
use crate::utils::target_triple::TargetTriple;
2122

@@ -526,6 +527,34 @@ impl TreeResolver {
526527
)
527528
})?;
528529

530+
// Symlink any path dependencies that live outside the workspace root
531+
symlink_external_path_deps(
532+
pristine_manifest_path.as_std_path(),
533+
pristine_root.as_std_path(),
534+
output_dir,
535+
)?;
536+
537+
// Also handle workspace members' external path deps
538+
let manifest = cargo_toml::Manifest::from_path(pristine_manifest_path.as_std_path())
539+
.with_context(|| format!("Failed to parse manifest at {}", pristine_manifest_path))?;
540+
if let Some(ref workspace) = manifest.workspace {
541+
for member_pattern in &workspace.members {
542+
let glob_pattern = pristine_root.join(member_pattern).to_string();
543+
if let Ok(entries) = glob::glob(&glob_pattern) {
544+
for entry in entries.flatten() {
545+
let member_manifest = entry.join("Cargo.toml");
546+
if member_manifest.exists() {
547+
symlink_external_path_deps(
548+
&member_manifest,
549+
pristine_root.as_std_path(),
550+
output_dir,
551+
)?;
552+
}
553+
}
554+
}
555+
}
556+
}
557+
529558
let cargo_metadata = self
530559
.cargo_bin
531560
.metadata_command_with_options(

crate_universe/src/splicing.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
pub(crate) mod cargo_config;
44
mod crate_index_lookup;
5-
mod splicer;
5+
pub(crate) mod splicer;
66

77
use std::collections::{BTreeMap, BTreeSet};
88
use std::fs;
@@ -41,6 +41,11 @@ pub(crate) struct SplicingManifest {
4141

4242
/// The Cargo resolver version to use for splicing
4343
pub(crate) resolver_version: cargo_toml::Resolver,
44+
45+
/// The path of the "main" manifest (first in the manifests list).
46+
/// Used to disambiguate when manifests come from multiple Cargo workspaces.
47+
#[serde(default)]
48+
pub(crate) main_manifest: Option<Utf8PathBuf>,
4449
}
4550

4651
impl FromStr for SplicingManifest {
@@ -61,6 +66,7 @@ impl SplicingManifest {
6166
let Self {
6267
manifests,
6368
cargo_config,
69+
main_manifest,
6470
..
6571
} = self;
6672

@@ -88,9 +94,19 @@ impl SplicingManifest {
8894
Utf8PathBuf::from(resolved_path)
8995
});
9096

97+
// Resolve the main manifest path
98+
let main_manifest = main_manifest.map(|path| {
99+
let resolved_path = path
100+
.to_string()
101+
.replace("${build_workspace_directory}", &workspace_dir_str)
102+
.replace("${output_base}", &output_base_str);
103+
Utf8PathBuf::from(resolved_path)
104+
});
105+
91106
Self {
92107
manifests,
93108
cargo_config,
109+
main_manifest,
94110
..self
95111
}
96112
}
@@ -678,6 +694,7 @@ mod test {
678694
]),
679695
cargo_config: None,
680696
resolver_version: cargo_toml::Resolver::V2,
697+
main_manifest: None,
681698
};
682699
let metadata = SplicingMetadata::try_from(manifest).unwrap();
683700
let metadata = serde_json::to_string(&metadata).unwrap();

0 commit comments

Comments
 (0)