Skip to content

Commit 55ce526

Browse files
committed
chore: Split crate into separate bin and lib targets
Splits the crate into separate lib and bin targets to set the stage for adding integration tests.
1 parent fa5e5b6 commit 55ce526

9 files changed

Lines changed: 737 additions & 715 deletions

File tree

Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,11 @@ anyhow = "1.0.100"
2828
[profile.release]
2929
debug = "full"
3030
strip = "none"
31+
32+
[lib]
33+
name = "cargo_subspace"
34+
path = "src/lib.rs"
35+
36+
[[bin]]
37+
name = "cargo_subspace"
38+
path = "src/main.rs"

src/cli.rs

Lines changed: 17 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -29,40 +29,28 @@ pub struct CargoSubspace {
2929
pub command: SubspaceCommand,
3030
}
3131

32-
#[derive(PartialEq, Clone, Debug, Parser)]
33-
pub struct FeatureArgs {
34-
/// Activate all features in the workspace.
35-
///
36-
/// Note that this flag applies to the whole workspace, not just the crate you're currently
37-
/// working on.
38-
#[arg(long, conflicts_with = "no_default_features")]
39-
pub all_features: bool,
40-
41-
/// Don't include default features during the workspace discovery process.
42-
///
43-
/// Note that this flag applies to the whole workspace, not just the crate you're currently
44-
/// working on.
45-
#[arg(long, conflicts_with = "all_features")]
46-
pub no_default_features: bool,
47-
}
48-
49-
#[derive(PartialEq, Clone, Debug, Parser)]
50-
pub struct DiscoverArgs {
51-
/// Profiles the discover process and writes a flamegraph to the given path
52-
#[arg(long, hide = true)]
53-
pub flamegraph: Option<PathBuf>,
54-
55-
#[command(flatten)]
56-
pub feature_args: FeatureArgs,
57-
}
58-
5932
#[derive(PartialEq, Clone, Debug, Parser)]
6033
pub enum SubspaceCommand {
6134
/// Print the cargo-subspace version and sysroot path and exit
6235
Version,
6336
Discover {
64-
#[command(flatten)]
65-
discover_args: DiscoverArgs,
37+
/// Activate all features in the workspace.
38+
///
39+
/// Note that this flag applies to the whole workspace, not just the crate you're currently
40+
/// working on.
41+
#[arg(long, conflicts_with = "no_default_features")]
42+
all_features: bool,
43+
44+
/// Don't include default features during the workspace discovery process.
45+
///
46+
/// Note that this flag applies to the whole workspace, not just the crate you're currently
47+
/// working on.
48+
#[arg(long, conflicts_with = "all_features")]
49+
no_default_features: bool,
50+
51+
/// Profiles the discover process and writes a flamegraph to the given path
52+
#[arg(long, hide = true)]
53+
flamegraph: Option<PathBuf>,
6654

6755
arg: DiscoverArgument,
6856
},

src/discover.rs

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
use std::{
2+
io::{BufRead, BufReader},
3+
process::Stdio,
4+
};
5+
6+
use anyhow::Result;
7+
use cargo_metadata::{Artifact, Message, Metadata, MetadataCommand, camino::Utf8PathBuf};
8+
9+
use crate::{
10+
graph::CrateGraph,
11+
util::{self, FilePathBuf, Toolchain},
12+
};
13+
14+
pub struct DiscoverRunner {
15+
toolchain: Toolchain,
16+
features: FeatureOption,
17+
manifest_path: FilePathBuf,
18+
}
19+
20+
impl DiscoverRunner {
21+
pub fn new(toolchain: Toolchain, manifest_path: FilePathBuf) -> Self {
22+
Self {
23+
manifest_path,
24+
toolchain,
25+
features: FeatureOption::Default,
26+
}
27+
}
28+
29+
pub fn with_all_features(mut self) -> Self {
30+
self.features = FeatureOption::All;
31+
self
32+
}
33+
34+
pub fn with_no_default_features(mut self) -> Self {
35+
self.features = FeatureOption::NoDefault;
36+
self
37+
}
38+
39+
pub fn with_default_features(mut self) -> Self {
40+
self.features = FeatureOption::Default;
41+
self
42+
}
43+
44+
/// Fetches the cargo metadata, constructs a crate graph, and prunes the graph such that it
45+
/// only contains dependencies of the crate for the given manifest path
46+
pub fn run(self) -> Result<CrateGraph> {
47+
// Get the cargo workspace metadata
48+
let metadata = self.get_metadata()?;
49+
50+
// Lower the metadata into our internal crate graph representation
51+
let mut graph = CrateGraph::from_metadata(metadata)?;
52+
53+
// Prune the graph such that the remaining nodes are only those reachable from the node
54+
// with the given manifest path
55+
graph.prune(self.manifest_path.as_file_path())?;
56+
57+
// Build the compile time dependencies (proc macros & build scripts) for the pruned graph
58+
self.build_compile_time_dependencies(&mut graph)?;
59+
60+
Ok(graph)
61+
}
62+
63+
fn get_metadata(&self) -> Result<Metadata> {
64+
util::log_progress("Fetching metadata")?;
65+
66+
let rustc_info = String::from_utf8(self.toolchain.rustc().arg("-vV").output()?.stdout)?;
67+
let mut cmd = MetadataCommand::new();
68+
cmd.manifest_path(self.manifest_path.as_std_path());
69+
70+
if let Some(cargo_home) = self.toolchain.cargo_home.as_ref() {
71+
cmd.cargo_path(cargo_home.join("bin/cargo"));
72+
}
73+
74+
let target_triple = rustc_info
75+
.lines()
76+
.find_map(|line| line.strip_prefix("host: "));
77+
if let Some(target_triple) = target_triple {
78+
cmd.other_options(["--filter-platform".into(), target_triple.into()]);
79+
}
80+
81+
match self.features {
82+
FeatureOption::All => {
83+
cmd.features(cargo_metadata::CargoOpt::AllFeatures);
84+
}
85+
FeatureOption::NoDefault => {
86+
cmd.features(cargo_metadata::CargoOpt::NoDefaultFeatures);
87+
}
88+
FeatureOption::Default => (),
89+
}
90+
91+
Ok(cmd.exec()?)
92+
}
93+
94+
fn build_compile_time_dependencies(&self, graph: &mut CrateGraph) -> Result<()> {
95+
// TODO: check rust version to decide whether to use --compile-time-deps, which allows us to
96+
// only build proc macros/build scripts during this step instead of building the whole crate
97+
let child = self
98+
.toolchain
99+
.cargo()
100+
// .arg("+nightly")
101+
.arg("check")
102+
// .arg("--compile-time-deps")
103+
.arg("--quiet")
104+
.arg("--message-format")
105+
.arg("json")
106+
.arg("--keep-going")
107+
.arg("--all-targets")
108+
.arg("--manifest-path")
109+
.arg(self.manifest_path.as_std_path())
110+
// .arg("-Zunstable-options")
111+
// .env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly")
112+
.stdout(Stdio::piped())
113+
.stderr(Stdio::null())
114+
.spawn()?;
115+
116+
for line in BufReader::new(child.stdout.unwrap()).lines() {
117+
let line = line?;
118+
let message = serde_json::from_str::<Message>(&line)?;
119+
120+
match message {
121+
Message::CompilerArtifact(Artifact {
122+
filenames,
123+
target,
124+
package_id,
125+
..
126+
}) => {
127+
if let Some(dylib) = filenames.into_iter().find(is_dylib)
128+
&& target.is_proc_macro()
129+
{
130+
util::log_progress(format!("proc-macro {} built", target.name))?;
131+
if let Some(pkg) = graph.get_mut(&package_id) {
132+
pkg.proc_macro_dylib = Some(dylib.try_into()?);
133+
}
134+
}
135+
}
136+
Message::BuildScriptExecuted(script) => {
137+
if let Some(pkg) = graph.get_mut(&script.package_id) {
138+
util::log_progress(format!("build script {} run", pkg.name))?;
139+
pkg.build_script = Some(script);
140+
}
141+
}
142+
_ => (),
143+
}
144+
}
145+
146+
Ok(())
147+
}
148+
}
149+
150+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
151+
enum FeatureOption {
152+
NoDefault,
153+
All,
154+
Default,
155+
}
156+
157+
fn is_dylib(path: &Utf8PathBuf) -> bool {
158+
path.extension()
159+
.map(|ext| ["dylib", "so", "dll"].contains(&ext))
160+
.unwrap_or(false)
161+
}

0 commit comments

Comments
 (0)