Skip to content

Commit

Permalink
Support workspaces
Browse files Browse the repository at this point in the history
Summary:
Closes #22.

A workspace has:

- At most 1 "root_pkg"
- At least 1 "workspace_members"
- At least 1 "default_workspace_members"

If there is no root_pkg, the workspace's top-level manifest is known as a "virtual manifest". For example the [serde repo](https://github.com/serde-rs/serde/blob/v1.0.183/Cargo.toml) uses a virtual manifest while the [thiserror repo](https://github.com/dtolnay/thiserror/blob/1.0.44/Cargo.toml) uses a workspace in which the root is not a virtual manifest.

The "default_workspace_members" are a subset of the "workspace_members". Most cargo commands run without `-p` will apply to only the default members, such as `cargo check` or `cargo test` or `cargo clippy`. Others apply to all members, like `cargo fmt`. I am not that familiar with how these are used in practice but I have seen non-default members for things like test fixtures. I chose to make Reindeer buckify only the default members for now.

Reviewed By: zertosh

Differential Revision: D48105958

fbshipit-source-id: 902a35c426ddb91988b1a86d2c01263459fdbe2b
  • Loading branch information
David Tolnay authored and facebook-github-bot committed Aug 7, 2023
1 parent e429137 commit f2145de
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 31 deletions.
22 changes: 12 additions & 10 deletions src/buckify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -781,7 +781,7 @@ pub(crate) fn buckify(config: &Config, args: &Args, paths: &Paths, stdout: bool)
log::trace!("Metadata {:#?}", metadata);
}

let index = index::Index::new(config.include_top_level, &metadata);
let index = index::Index::new(config.include_top_level, &metadata)?;

let context = &RuleContext {
config,
Expand All @@ -796,15 +796,17 @@ pub(crate) fn buckify(config: &Config, args: &Args, paths: &Paths, stdout: bool)
{
measure_time::trace_time!("generate_dep_rules");
rayon::scope(move |scope| {
generate_dep_rules(
context,
scope,
tx,
[
(context.index.root_pkg, TargetReq::Lib),
(context.index.root_pkg, TargetReq::EveryBin),
],
);
for &workspace_member in &context.index.workspace_members {
generate_dep_rules(
context,
scope,
tx.clone(),
[
(workspace_member, TargetReq::Lib),
(workspace_member, TargetReq::EveryBin),
],
);
}
});
}

Expand Down
4 changes: 1 addition & 3 deletions src/cargo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,10 +250,8 @@ impl Display for PkgId {
pub struct Metadata {
#[serde(deserialize_with = "deserialize_default_from_null")]
pub packages: BTreeSet<Manifest>,
pub target_directory: PathBuf,
pub version: u32,
pub workspace_root: PathBuf,
pub workspace_members: Vec<PkgId>,
pub workspace_default_members: Vec<PkgId>,
/// Resolved dependency graph
pub resolve: Resolve,
}
Expand Down
60 changes: 42 additions & 18 deletions src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use std::collections::HashMap;
use std::collections::HashSet;

use anyhow::Context as _;
use anyhow::Result;
use serde::Deserialize;

use crate::buck::Name;
Expand All @@ -32,8 +33,10 @@ pub struct Index<'meta> {
pkgid_to_pkg: HashMap<&'meta PkgId, &'meta Manifest>,
/// Map a PkgId to a Node (ie all the details of a resolve dependency)
pkgid_to_node: HashMap<&'meta PkgId, &'meta Node>,
/// Represents the Cargo.toml itself
pub root_pkg: &'meta Manifest,
/// Represents the Cargo.toml itself, if not a virtual manifest.
pub root_pkg: Option<&'meta Manifest>,
/// All packages considered part of the workspace.
pub workspace_members: Vec<&'meta Manifest>,
/// Set of packages from which at least one target is public.
public_packages: BTreeSet<&'meta PkgId>,
/// Set of public targets. These consist of:
Expand All @@ -60,40 +63,58 @@ impl<'meta> Index<'meta> {
/// Construct an index for a set of Cargo metadata to allow convenient and efficient
/// queries. The metadata represents a top level package and all its transitive
/// dependencies.
pub fn new(root_is_real: bool, metadata: &'meta Metadata) -> Index<'meta> {
pub fn new(root_is_real: bool, metadata: &'meta Metadata) -> Result<Index<'meta>> {
let pkgid_to_pkg: HashMap<_, _> = metadata.packages.iter().map(|m| (&m.id, m)).collect();

let root_pkg: &Manifest = pkgid_to_pkg
.get(&metadata.resolve.root.as_ref().expect("missing root pkg"))
.expect("couldn't identify unambiguous top-level crate");
let root_pkg = metadata.resolve.root.as_ref().map(|root_pkgid| {
*pkgid_to_pkg
.get(root_pkgid)
.expect("couldn't identify unambiguous top-level crate")
});

let mut top_levels = HashSet::new();
if root_is_real {
top_levels.insert(&root_pkg.id);
}
let top_levels = if root_is_real {
Some(
&root_pkg
.context("`include_top_level = true` is not supported on a virtual manifest")?
.id,
)
} else {
None
};

let workspace_members = metadata
.workspace_default_members
.iter()
.filter_map(|pkgid| pkgid_to_pkg.get(pkgid).copied())
.collect();

let mut tmp = Index {
pkgid_to_pkg,
pkgid_to_node: metadata.resolve.nodes.iter().map(|n| (&n.id, n)).collect(),
root_pkg,
workspace_members,
public_packages: BTreeSet::new(),
public_targets: BTreeMap::new(),
};

// Keep an index of renamed crates, mapping from _ normalized name to actual name
// Keep an index of renamed crates, mapping from _ normalized name to actual name.
// Only the root package's renames matter. We don't attempt to merge different
// rename choices made by different workspace members.
let dep_renamed: HashMap<String, &'meta str> = root_pkg
.dependencies
.iter()
.flat_map(|root_pkg| &root_pkg.dependencies)
.filter_map(|dep| {
let rename = dep.rename.as_deref()?;
Some((rename.replace('-', "_"), rename))
})
.collect();

// Compute public set, with pkgid mapped to rename if it has one. Public set is
// anything in top_levels, or first-order dependencies of root_pkg.
// anything in top_levels, or first-order dependencies of any workspace member.
let public_targets = tmp
.resolved_deps(tmp.root_pkg)
.workspace_members
.iter()
.flat_map(|member| tmp.resolved_deps(member))
.flat_map(|(rename, dep_kind, pkg)| {
let target_req = dep_kind.target_req();
let opt_rename = dep_renamed.get(rename).cloned();
Expand All @@ -111,15 +132,18 @@ impl<'meta> Index<'meta> {
tmp.public_packages.insert(pkg);
}

Index {
Ok(Index {
public_targets,
..tmp
}
})
}

/// Test if a package is the root package
pub fn is_root_package(&self, pkg: &Manifest) -> bool {
self.root_pkg.id == pkg.id
match self.root_pkg {
Some(root_pkg) => root_pkg.id == pkg.id,
None => false,
}
}

/// Test if there is any target from the package which is public
Expand Down

0 comments on commit f2145de

Please sign in to comment.