build_info_build/build_script_options/
crate_info.rs1use std::collections::hash_map::{Entry, HashMap};
2
3use build_info_common::{CrateInfo, semver::Version};
4use cargo_metadata::*;
5use pretty_assertions::assert_eq;
6
7#[derive(Clone, Copy, Debug)]
11pub enum DependencyDepth {
12 None,
14 Full,
18 Depth(usize),
22}
23
24impl DependencyDepth {
25 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 pub fn collect_runtime_dependencies(mut self, collect_dependencies: DependencyDepth) -> Self {
41 self.collect_runtime_dependencies = collect_dependencies;
42 self
43 }
44
45 pub fn collect_build_dependencies(mut self, collect_dependencies: DependencyDepth) -> Self {
50 self.collect_build_dependencies = collect_dependencies;
51 self
52 }
53
54 pub fn collect_dev_dependencies(mut self, collect_dependencies: DependencyDepth) -> Self {
59 self.collect_build_dependencies = collect_dependencies;
60 self
61 }
62
63 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 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()); assert_eq!(
151 crate_info.version.to_string(),
152 std::env::var("CARGO_PKG_VERSION").unwrap()
153 ); assert_eq!(
155 crate_info.authors.join(":"),
156 std::env::var("CARGO_PKG_AUTHORS").unwrap()
157 ); 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}