Skip to content

Commit 79c34e0

Browse files
committed
Async Loading outdir and proc-macro
1 parent ad3cb21 commit 79c34e0

File tree

11 files changed

+423
-175
lines changed

11 files changed

+423
-175
lines changed

crates/project_model/src/build_data.rs

Lines changed: 155 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,20 @@ use std::{
55
io::BufReader,
66
path::{Path, PathBuf},
77
process::{Command, Stdio},
8+
sync::Arc,
89
};
910

1011
use anyhow::Result;
11-
use cargo_metadata::{BuildScript, Message, Package, PackageId};
12+
use cargo_metadata::{BuildScript, Message};
1213
use itertools::Itertools;
1314
use paths::{AbsPath, AbsPathBuf};
1415
use rustc_hash::FxHashMap;
1516
use stdx::JodChild;
1617

1718
use crate::{cfg_flag::CfgFlag, CargoConfig};
1819

19-
#[derive(Debug, Clone, Default)]
20-
pub(crate) struct BuildDataMap {
21-
data: FxHashMap<PackageId, BuildData>,
22-
}
2320
#[derive(Debug, Clone, Default, PartialEq, Eq)]
24-
pub struct BuildData {
21+
pub(crate) struct BuildData {
2522
/// List of config flags defined by this package's build script
2623
pub cfgs: Vec<CfgFlag>,
2724
/// List of cargo-related environment variables with their value
@@ -35,131 +32,171 @@ pub struct BuildData {
3532
pub proc_macro_dylib_path: Option<AbsPathBuf>,
3633
}
3734

38-
impl BuildDataMap {
39-
pub(crate) fn new(
40-
cargo_toml: &AbsPath,
41-
cargo_features: &CargoConfig,
42-
packages: &Vec<Package>,
43-
progress: &dyn Fn(String),
44-
) -> Result<BuildDataMap> {
45-
let mut cmd = Command::new(toolchain::cargo());
46-
cmd.args(&["check", "--workspace", "--message-format=json", "--manifest-path"])
47-
.arg(cargo_toml.as_ref());
48-
49-
// --all-targets includes tests, benches and examples in addition to the
50-
// default lib and bins. This is an independent concept from the --targets
51-
// flag below.
52-
cmd.arg("--all-targets");
53-
54-
if let Some(target) = &cargo_features.target {
55-
cmd.args(&["--target", target]);
56-
}
35+
#[derive(Clone, Debug)]
36+
pub(crate) struct BuildDataConfig {
37+
pub(crate) workspace_root: AbsPathBuf,
38+
pub(crate) cargo_toml: AbsPathBuf,
39+
pub(crate) cargo_features: CargoConfig,
40+
pub(crate) packages: Arc<Vec<cargo_metadata::Package>>,
41+
}
5742

58-
if cargo_features.all_features {
59-
cmd.arg("--all-features");
60-
} else {
61-
if cargo_features.no_default_features {
62-
// FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures`
63-
// https://github.com/oli-obk/cargo_metadata/issues/79
64-
cmd.arg("--no-default-features");
65-
}
66-
if !cargo_features.features.is_empty() {
67-
cmd.arg("--features");
68-
cmd.arg(cargo_features.features.join(" "));
69-
}
70-
}
43+
impl PartialEq for BuildDataConfig {
44+
fn eq(&self, other: &Self) -> bool {
45+
Arc::ptr_eq(&self.packages, &other.packages)
46+
}
47+
}
7148

72-
cmd.stdout(Stdio::piped()).stderr(Stdio::null()).stdin(Stdio::null());
73-
74-
let mut child = cmd.spawn().map(JodChild)?;
75-
let child_stdout = child.stdout.take().unwrap();
76-
let stdout = BufReader::new(child_stdout);
77-
78-
let mut res = BuildDataMap::default();
79-
for message in cargo_metadata::Message::parse_stream(stdout) {
80-
if let Ok(message) = message {
81-
match message {
82-
Message::BuildScriptExecuted(BuildScript {
83-
package_id,
84-
out_dir,
85-
cfgs,
86-
env,
87-
..
88-
}) => {
89-
let cfgs = {
90-
let mut acc = Vec::new();
91-
for cfg in cfgs {
92-
match cfg.parse::<CfgFlag>() {
93-
Ok(it) => acc.push(it),
94-
Err(err) => {
95-
anyhow::bail!("invalid cfg from cargo-metadata: {}", err)
96-
}
97-
};
98-
}
99-
acc
100-
};
101-
let res = res.data.entry(package_id.clone()).or_default();
102-
// cargo_metadata crate returns default (empty) path for
103-
// older cargos, which is not absolute, so work around that.
104-
if out_dir != PathBuf::default() {
105-
let out_dir = AbsPathBuf::assert(out_dir);
106-
res.out_dir = Some(out_dir);
107-
res.cfgs = cfgs;
108-
}
49+
impl Eq for BuildDataConfig {}
10950

110-
res.envs = env;
111-
}
112-
Message::CompilerArtifact(message) => {
113-
progress(format!("metadata {}", message.target.name));
114-
115-
if message.target.kind.contains(&"proc-macro".to_string()) {
116-
let package_id = message.package_id;
117-
// Skip rmeta file
118-
if let Some(filename) =
119-
message.filenames.iter().find(|name| is_dylib(name))
120-
{
121-
let filename = AbsPathBuf::assert(filename.clone());
122-
let res = res.data.entry(package_id.clone()).or_default();
123-
res.proc_macro_dylib_path = Some(filename);
124-
}
125-
}
126-
}
127-
Message::CompilerMessage(message) => {
128-
progress(message.target.name.clone());
129-
}
130-
Message::Unknown => (),
131-
Message::BuildFinished(_) => {}
132-
Message::TextLine(_) => {}
133-
}
134-
}
51+
#[derive(Debug, Default)]
52+
pub struct BuildDataCollector {
53+
configs: FxHashMap<AbsPathBuf, BuildDataConfig>,
54+
}
55+
56+
#[derive(Debug, Default)]
57+
pub struct BuildDataResult {
58+
data: FxHashMap<AbsPathBuf, BuildDataMap>,
59+
}
60+
61+
pub(crate) type BuildDataMap = FxHashMap<String, BuildData>;
62+
63+
impl BuildDataCollector {
64+
pub(crate) fn add_config(&mut self, config: BuildDataConfig) {
65+
self.configs.insert(config.workspace_root.to_path_buf().clone(), config);
66+
}
67+
68+
pub fn collect(&mut self, progress: &dyn Fn(String)) -> Result<BuildDataResult> {
69+
let mut res = BuildDataResult::default();
70+
for (path, config) in self.configs.iter() {
71+
res.data.insert(
72+
path.clone(),
73+
collect_from_workspace(
74+
&config.cargo_toml,
75+
&config.cargo_features,
76+
&config.packages,
77+
progress,
78+
)?,
79+
);
13580
}
136-
res.inject_cargo_env(packages);
13781
Ok(res)
13882
}
83+
}
84+
85+
impl BuildDataResult {
86+
pub(crate) fn get(&self, root: &AbsPath) -> Option<&BuildDataMap> {
87+
self.data.get(&root.to_path_buf())
88+
}
89+
}
13990

140-
pub(crate) fn with_cargo_env(packages: &Vec<Package>) -> Self {
141-
let mut res = Self::default();
142-
res.inject_cargo_env(packages);
143-
res
91+
fn collect_from_workspace(
92+
cargo_toml: &AbsPath,
93+
cargo_features: &CargoConfig,
94+
packages: &Vec<cargo_metadata::Package>,
95+
progress: &dyn Fn(String),
96+
) -> Result<BuildDataMap> {
97+
let mut cmd = Command::new(toolchain::cargo());
98+
cmd.args(&["check", "--workspace", "--message-format=json", "--manifest-path"])
99+
.arg(cargo_toml.as_ref());
100+
101+
// --all-targets includes tests, benches and examples in addition to the
102+
// default lib and bins. This is an independent concept from the --targets
103+
// flag below.
104+
cmd.arg("--all-targets");
105+
106+
if let Some(target) = &cargo_features.target {
107+
cmd.args(&["--target", target]);
144108
}
145109

146-
pub(crate) fn get(&self, id: &PackageId) -> Option<&BuildData> {
147-
self.data.get(id)
110+
if cargo_features.all_features {
111+
cmd.arg("--all-features");
112+
} else {
113+
if cargo_features.no_default_features {
114+
// FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures`
115+
// https://github.com/oli-obk/cargo_metadata/issues/79
116+
cmd.arg("--no-default-features");
117+
}
118+
if !cargo_features.features.is_empty() {
119+
cmd.arg("--features");
120+
cmd.arg(cargo_features.features.join(" "));
121+
}
148122
}
149123

150-
fn inject_cargo_env(&mut self, packages: &Vec<Package>) {
151-
for meta_pkg in packages {
152-
let resource = self.data.entry(meta_pkg.id.clone()).or_default();
153-
inject_cargo_env(meta_pkg, &mut resource.envs);
124+
cmd.stdout(Stdio::piped()).stderr(Stdio::null()).stdin(Stdio::null());
125+
126+
let mut child = cmd.spawn().map(JodChild)?;
127+
let child_stdout = child.stdout.take().unwrap();
128+
let stdout = BufReader::new(child_stdout);
129+
130+
let mut res = BuildDataMap::default();
131+
for message in cargo_metadata::Message::parse_stream(stdout) {
132+
if let Ok(message) = message {
133+
match message {
134+
Message::BuildScriptExecuted(BuildScript {
135+
package_id,
136+
out_dir,
137+
cfgs,
138+
env,
139+
..
140+
}) => {
141+
let cfgs = {
142+
let mut acc = Vec::new();
143+
for cfg in cfgs {
144+
match cfg.parse::<CfgFlag>() {
145+
Ok(it) => acc.push(it),
146+
Err(err) => {
147+
anyhow::bail!("invalid cfg from cargo-metadata: {}", err)
148+
}
149+
};
150+
}
151+
acc
152+
};
153+
let res = res.entry(package_id.repr.clone()).or_default();
154+
// cargo_metadata crate returns default (empty) path for
155+
// older cargos, which is not absolute, so work around that.
156+
if out_dir != PathBuf::default() {
157+
let out_dir = AbsPathBuf::assert(out_dir);
158+
res.out_dir = Some(out_dir);
159+
res.cfgs = cfgs;
160+
}
154161

155-
if let Some(out_dir) = &resource.out_dir {
156-
// NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
157-
if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) {
158-
resource.envs.push(("OUT_DIR".to_string(), out_dir));
162+
res.envs = env;
159163
}
164+
Message::CompilerArtifact(message) => {
165+
progress(format!("metadata {}", message.target.name));
166+
167+
if message.target.kind.contains(&"proc-macro".to_string()) {
168+
let package_id = message.package_id;
169+
// Skip rmeta file
170+
if let Some(filename) = message.filenames.iter().find(|name| is_dylib(name))
171+
{
172+
let filename = AbsPathBuf::assert(filename.clone());
173+
let res = res.entry(package_id.repr.clone()).or_default();
174+
res.proc_macro_dylib_path = Some(filename);
175+
}
176+
}
177+
}
178+
Message::CompilerMessage(message) => {
179+
progress(message.target.name.clone());
180+
}
181+
Message::Unknown => (),
182+
Message::BuildFinished(_) => {}
183+
Message::TextLine(_) => {}
184+
}
185+
}
186+
}
187+
188+
for package in packages {
189+
let build_data = res.entry(package.id.repr.clone()).or_default();
190+
inject_cargo_env(package, build_data);
191+
if let Some(out_dir) = &build_data.out_dir {
192+
// NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
193+
if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) {
194+
build_data.envs.push(("OUT_DIR".to_string(), out_dir));
160195
}
161196
}
162197
}
198+
199+
Ok(res)
163200
}
164201

165202
// FIXME: File a better way to know if it is a dylib
@@ -173,7 +210,9 @@ fn is_dylib(path: &Path) -> bool {
173210
/// Recreates the compile-time environment variables that Cargo sets.
174211
///
175212
/// Should be synced with <https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates>
176-
fn inject_cargo_env(package: &cargo_metadata::Package, env: &mut Vec<(String, String)>) {
213+
fn inject_cargo_env(package: &cargo_metadata::Package, build_data: &mut BuildData) {
214+
let env = &mut build_data.envs;
215+
177216
// FIXME: Missing variables:
178217
// CARGO_PKG_HOMEPAGE, CARGO_CRATE_NAME, CARGO_BIN_NAME, CARGO_BIN_EXE_<name>
179218

0 commit comments

Comments
 (0)