build_info_build/build_script_options/
crate_info.rs

1use std::collections::hash_map::{Entry, HashMap};
2
3use build_info_common::{CrateInfo, semver::Version};
4use cargo_metadata::*;
5use pretty_assertions::assert_eq;
6
7/// Depth of dependencies to collect
8///
9/// A dependency depth that is too high may crash the build process
10#[derive(Clone, Copy, Debug)]
11pub enum DependencyDepth {
12	/// Do not collect any dependencies
13	None,
14	/// Collect all dependencies
15	///
16	/// This may crash the build process if your dependencies are very deep
17	Full,
18	/// Collect dependencies to this depth
19	///
20	/// A too-high value may crash the build process
21	Depth(usize),
22}
23
24impl DependencyDepth {
25	/// Returns false if the dependency collection depth has been reached.
26	pub fn do_collect(&self, depth: usize) -> bool {
27		match self {
28			DependencyDepth::None => false,
29			DependencyDepth::Full => true,
30			DependencyDepth::Depth(limit) => depth <= *limit,
31		}
32	}
33}
34
35impl crate::BuildScriptOptions {
36	/// Enables and disables runtime dependency collection.
37	///
38	/// Dependency data is fairly large, which may cause problems, mainly by crashing the build process. If the project
39	/// compiles successfully with dependency collection enabled, you are probably fine.
40	pub fn collect_runtime_dependencies(mut self, collect_dependencies: DependencyDepth) -> Self {
41		self.collect_runtime_dependencies = collect_dependencies;
42		self
43	}
44
45	/// Enables and disables build dependency collection.
46	///
47	/// Dependency data is fairly large, which may cause problems, mainly by crashing the build process. If the project
48	/// compiles successfully with dependency collection enabled, you are probably fine.
49	pub fn collect_build_dependencies(mut self, collect_dependencies: DependencyDepth) -> Self {
50		self.collect_build_dependencies = collect_dependencies;
51		self
52	}
53
54	/// Enables and disables dev dependency collection.
55	///
56	/// Dependency data is fairly large, which may cause problems, mainly by crashing the build process. If the project
57	/// compiles successfully with dependency collection enabled, you are probably fine.
58	pub fn collect_dev_dependencies(mut self, collect_dependencies: DependencyDepth) -> Self {
59		self.collect_build_dependencies = collect_dependencies;
60		self
61	}
62
63	/// Enables and disables runtime, build and dev dependency collection.
64	///
65	/// Dependency data is fairly large, which may cause problems, mainly by crashing the build process. If the project
66	/// compiles successfully with dependency collection enabled, you are probably fine.
67	pub fn collect_dependencies(mut self, collect_dependencies: DependencyDepth) -> Self {
68		self.collect_runtime_dependencies = collect_dependencies;
69		self.collect_build_dependencies = collect_dependencies;
70		self.collect_dev_dependencies = collect_dependencies;
71		self
72	}
73}
74
75pub(crate) struct Manifest {
76	pub crate_info: CrateInfo,
77	pub workspace_root: String,
78}
79
80pub(crate) fn read_manifest(
81	target_platform: &str,
82	collect_runtime_dependencies: DependencyDepth,
83	collect_build_dependencies: DependencyDepth,
84	collect_dev_dependencies: DependencyDepth,
85) -> Manifest {
86	let mut args = vec!["--filter-platform".to_string(), target_platform.to_string()];
87
88	// Cargo does not provide a proper list of enabled features, so we collect metadata once to find all possible
89	// features, convert them to the equivalent `CARGO_FEATURE_` representation, check for collisions, and then rerun
90	// the command with the appropriate feature flags selected.
91	//
92	// We still expect this to fail for dependency flags that are enabled by hand (e.g.,
93	// `cargo run --features=serde/derive`), but so far there is no workaround for that.
94
95	let meta = MetadataCommand::new()
96		.cargo_path(std::env::var_os("CARGO").unwrap())
97		.manifest_path(super::cargo_toml())
98		.features(CargoOpt::NoDefaultFeatures)
99		.other_options(args.clone())
100		.exec()
101		.unwrap();
102
103	let root = &meta[meta.resolve.as_ref().unwrap().root.as_ref().unwrap()];
104	let mut map = HashMap::new();
105	for feature in root.features.keys() {
106		if !feature.is_ascii() {
107			panic!("The feature {feature:?} contains non-ascii characters.");
108		}
109		let env_var = format!("CARGO_FEATURE_{}", feature.to_ascii_uppercase().replace('-', "_"));
110		if std::env::var_os(&env_var).is_some() {
111			match map.entry(env_var) {
112				Entry::Vacant(entry) => {
113					entry.insert(feature);
114				}
115				Entry::Occupied(entry) => panic!(
116					"The features {:?} and {:?} have the same representation as cargo feature flags ({:?})",
117					feature,
118					entry.get(),
119					entry.key()
120				),
121			}
122		}
123	}
124
125	let mut feature_list = String::new();
126	for feature in map.values() {
127		if !feature_list.is_empty() {
128			feature_list += ",";
129		}
130		feature_list += feature;
131	}
132	args.push("--features".to_string());
133	args.push(feature_list);
134
135	let meta = MetadataCommand::new()
136		.cargo_path(std::env::var_os("CARGO").unwrap())
137		.manifest_path(super::cargo_toml())
138		.features(CargoOpt::NoDefaultFeatures)
139		.other_options(args)
140		.exec()
141		.unwrap();
142	let crate_info = make_crate_info(
143		&meta,
144		collect_runtime_dependencies,
145		collect_build_dependencies,
146		collect_dev_dependencies,
147	);
148
149	assert_eq!(crate_info.name, std::env::var("CARGO_PKG_NAME").unwrap()); // sanity check...
150	assert_eq!(
151		crate_info.version.to_string(),
152		std::env::var("CARGO_PKG_VERSION").unwrap()
153	); // sanity check...
154	assert_eq!(
155		crate_info.authors.join(":"),
156		std::env::var("CARGO_PKG_AUTHORS").unwrap()
157	); // sanity check...
158
159	Manifest {
160		crate_info,
161		workspace_root: meta.workspace_root.into(),
162	}
163}
164
165fn make_crate_info(
166	meta: &Metadata,
167	collect_runtime_dependencies: DependencyDepth,
168	collect_build_dependencies: DependencyDepth,
169	collect_dev_dependencies: DependencyDepth,
170) -> CrateInfo {
171	let resolve = meta.resolve.as_ref().unwrap();
172	let root_id = resolve.root.as_ref().unwrap();
173	let dependencies: HashMap<&PackageId, &Node> = resolve.nodes.iter().map(|node| (&node.id, node)).collect();
174
175	to_crate_info(
176		dependencies[&root_id],
177		&dependencies,
178		meta,
179		collect_runtime_dependencies,
180		collect_build_dependencies,
181		collect_dev_dependencies,
182		0,
183	)
184}
185
186fn to_crate_info(
187	node: &Node,
188	dependencies: &HashMap<&PackageId, &Node>,
189	meta: &Metadata,
190	collect_runtime_dependencies: DependencyDepth,
191	collect_build_dependencies: DependencyDepth,
192	collect_dev_dependencies: DependencyDepth,
193	depth: usize,
194) -> CrateInfo {
195	let pkg = &meta[&node.id];
196	let name = pkg.name.to_string();
197	let version = Version::parse(&pkg.version.to_string()).unwrap();
198	let authors = pkg.authors.clone();
199	let license = pkg.license.clone();
200	let available_features = pkg.features.keys().cloned().collect();
201	let enabled_features = node.features.iter().map(|name| name.to_string()).collect::<Vec<_>>();
202	let dependencies = if collect_runtime_dependencies.do_collect(depth) || collect_build_dependencies.do_collect(depth) {
203		node
204			.deps
205			.iter()
206			.flat_map(|dep| {
207				if dep.dep_kinds.iter().any(|kind| match kind.kind {
208					DependencyKind::Normal => collect_runtime_dependencies.do_collect(depth),
209					DependencyKind::Build => collect_build_dependencies.do_collect(depth),
210					DependencyKind::Development => collect_dev_dependencies.do_collect(depth),
211					DependencyKind::Unknown => unreachable!("Unknown dependency found"),
212				}) {
213					Some(to_crate_info(
214						dependencies[&dep.pkg],
215						dependencies,
216						meta,
217						collect_runtime_dependencies,
218						collect_build_dependencies,
219						collect_dev_dependencies,
220						depth + 1,
221					))
222				} else {
223					None
224				}
225			})
226			.collect()
227	} else {
228		Vec::new()
229	};
230
231	CrateInfo {
232		name,
233		version,
234		authors,
235		license,
236		enabled_features,
237		available_features,
238		dependencies,
239	}
240}