Skip to content

Commit 6403b53

Browse files
Merge #7387
7387: Refactor build script specific data r=edwin0cheng a=edwin0cheng It refactors for separating logic for build script specific data. bors r+ Co-authored-by: Edwin Cheng <[email protected]>
2 parents 2472851 + 6bdb678 commit 6403b53

File tree

4 files changed

+228
-196
lines changed

4 files changed

+228
-196
lines changed
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
//! Handles build script specific information
2+
3+
use std::{
4+
ffi::OsStr,
5+
io::BufReader,
6+
path::{Path, PathBuf},
7+
process::{Command, Stdio},
8+
};
9+
10+
use anyhow::Result;
11+
use cargo_metadata::{BuildScript, Message, Package, PackageId};
12+
use itertools::Itertools;
13+
use paths::AbsPathBuf;
14+
use rustc_hash::FxHashMap;
15+
use stdx::JodChild;
16+
17+
use crate::{cfg_flag::CfgFlag, CargoConfig};
18+
19+
#[derive(Debug, Clone, Default)]
20+
pub(crate) struct BuildDataMap {
21+
data: FxHashMap<PackageId, BuildData>,
22+
}
23+
#[derive(Debug, Clone, Default, PartialEq, Eq)]
24+
pub struct BuildData {
25+
/// List of config flags defined by this package's build script
26+
pub cfgs: Vec<CfgFlag>,
27+
/// List of cargo-related environment variables with their value
28+
///
29+
/// If the package has a build script which defines environment variables,
30+
/// they can also be found here.
31+
pub envs: Vec<(String, String)>,
32+
/// Directory where a build script might place its output
33+
pub out_dir: Option<AbsPathBuf>,
34+
/// Path to the proc-macro library file if this package exposes proc-macros
35+
pub proc_macro_dylib_path: Option<AbsPathBuf>,
36+
}
37+
38+
impl BuildDataMap {
39+
pub(crate) fn new(
40+
cargo_toml: &Path,
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);
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+
}
57+
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+
}
71+
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+
}
109+
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+
}
135+
}
136+
res.inject_cargo_env(packages);
137+
Ok(res)
138+
}
139+
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
144+
}
145+
146+
pub(crate) fn get(&self, id: &PackageId) -> Option<&BuildData> {
147+
self.data.get(id)
148+
}
149+
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);
154+
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));
159+
}
160+
}
161+
}
162+
}
163+
}
164+
165+
// FIXME: File a better way to know if it is a dylib
166+
fn is_dylib(path: &Path) -> bool {
167+
match path.extension().and_then(OsStr::to_str).map(|it| it.to_string().to_lowercase()) {
168+
None => false,
169+
Some(ext) => matches!(ext.as_str(), "dll" | "dylib" | "so"),
170+
}
171+
}
172+
173+
/// Recreates the compile-time environment variables that Cargo sets.
174+
///
175+
/// 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)>) {
177+
// FIXME: Missing variables:
178+
// CARGO, CARGO_PKG_HOMEPAGE, CARGO_CRATE_NAME, CARGO_BIN_NAME, CARGO_BIN_EXE_<name>
179+
180+
let mut manifest_dir = package.manifest_path.clone();
181+
manifest_dir.pop();
182+
if let Some(cargo_manifest_dir) = manifest_dir.to_str() {
183+
env.push(("CARGO_MANIFEST_DIR".into(), cargo_manifest_dir.into()));
184+
}
185+
186+
env.push(("CARGO_PKG_VERSION".into(), package.version.to_string()));
187+
env.push(("CARGO_PKG_VERSION_MAJOR".into(), package.version.major.to_string()));
188+
env.push(("CARGO_PKG_VERSION_MINOR".into(), package.version.minor.to_string()));
189+
env.push(("CARGO_PKG_VERSION_PATCH".into(), package.version.patch.to_string()));
190+
191+
let pre = package.version.pre.iter().map(|id| id.to_string()).format(".");
192+
env.push(("CARGO_PKG_VERSION_PRE".into(), pre.to_string()));
193+
194+
let authors = package.authors.join(";");
195+
env.push(("CARGO_PKG_AUTHORS".into(), authors));
196+
197+
env.push(("CARGO_PKG_NAME".into(), package.name.clone()));
198+
env.push(("CARGO_PKG_DESCRIPTION".into(), package.description.clone().unwrap_or_default()));
199+
//env.push(("CARGO_PKG_HOMEPAGE".into(), package.homepage.clone().unwrap_or_default()));
200+
env.push(("CARGO_PKG_REPOSITORY".into(), package.repository.clone().unwrap_or_default()));
201+
env.push(("CARGO_PKG_LICENSE".into(), package.license.clone().unwrap_or_default()));
202+
203+
let license_file =
204+
package.license_file.as_ref().map(|buf| buf.display().to_string()).unwrap_or_default();
205+
env.push(("CARGO_PKG_LICENSE_FILE".into(), license_file));
206+
}

0 commit comments

Comments
 (0)