1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
|
use cargo_metadata::{camino::Utf8PathBuf, DependencyKind};
use cargo_platform::Cfg;
use color_eyre::eyre::{bail, Result};
use std::{
collections::{HashMap, HashSet},
path::PathBuf,
process::Command,
str::FromStr,
};
use crate::{Config, Mode, OutputConflictHandling};
#[derive(Default, Debug)]
pub struct Dependencies {
/// All paths that must be imported with `-L dependency=`. This is for
/// finding proc macros run on the host and dependencies for the target.
pub import_paths: Vec<PathBuf>,
/// The name as chosen in the `Cargo.toml` and its corresponding rmeta file.
pub dependencies: Vec<(String, Vec<Utf8PathBuf>)>,
}
fn cfgs(config: &Config) -> Result<Vec<Cfg>> {
let mut cmd = config.cfgs.build(&config.out_dir);
cmd.arg("--target").arg(config.target.as_ref().unwrap());
let output = cmd.output()?;
let stdout = String::from_utf8(output.stdout)?;
if !output.status.success() {
let stderr = String::from_utf8(output.stderr)?;
bail!(
"failed to obtain `cfg` information from {cmd:?}:\nstderr:\n{stderr}\n\nstdout:{stdout}"
);
}
let mut cfgs = vec![];
for line in stdout.lines() {
cfgs.push(Cfg::from_str(line)?);
}
Ok(cfgs)
}
/// Compiles dependencies and returns the crate names and corresponding rmeta files.
pub fn build_dependencies(config: &mut Config) -> Result<Dependencies> {
let manifest_path = match &config.dependencies_crate_manifest_path {
Some(path) => path.to_owned(),
None => return Ok(Default::default()),
};
let manifest_path = &manifest_path;
config.fill_host_and_target()?;
eprintln!(" Building test dependencies...");
let mut build = config.dependency_builder.build(&config.out_dir);
build.arg(manifest_path);
if let Some(target) = &config.target {
build.arg(format!("--target={target}"));
}
// Reusable closure for setting up the environment both for artifact generation and `cargo_metadata`
let set_locking = |cmd: &mut Command| match (&config.output_conflict_handling, &config.mode) {
(_, Mode::Yolo) => {}
(OutputConflictHandling::Error(_), _) => {
cmd.arg("--locked");
}
_ => {}
};
set_locking(&mut build);
build.arg("--message-format=json");
let output = build.output()?;
if !output.status.success() {
let stdout = String::from_utf8(output.stdout)?;
let stderr = String::from_utf8(output.stderr)?;
bail!("failed to compile dependencies:\ncommand: {build:?}\nstderr:\n{stderr}\n\nstdout:{stdout}");
}
// Collect all artifacts generated
let artifact_output = output.stdout;
let artifact_output = String::from_utf8(artifact_output)?;
let mut import_paths: HashSet<PathBuf> = HashSet::new();
let mut artifacts: HashMap<_, _> = artifact_output
.lines()
.filter_map(|line| {
let message = serde_json::from_str::<cargo_metadata::Message>(line).ok()?;
if let cargo_metadata::Message::CompilerArtifact(artifact) = message {
for filename in &artifact.filenames {
import_paths.insert(filename.parent().unwrap().into());
}
Some((artifact.package_id, artifact.filenames))
} else {
None
}
})
.collect();
// Check which crates are mentioned in the crate itself
let mut metadata = cargo_metadata::MetadataCommand::new().cargo_command();
metadata.arg("--manifest-path").arg(manifest_path);
config.dependency_builder.apply_env(&mut metadata);
set_locking(&mut metadata);
let output = metadata.output()?;
if !output.status.success() {
let stdout = String::from_utf8(output.stdout)?;
let stderr = String::from_utf8(output.stderr)?;
bail!("failed to run cargo-metadata:\nstderr:\n{stderr}\n\nstdout:{stdout}");
}
let output = output.stdout;
let output = String::from_utf8(output)?;
let cfg = cfgs(config)?;
for line in output.lines() {
if !line.starts_with('{') {
continue;
}
let metadata: cargo_metadata::Metadata = serde_json::from_str(line)?;
// Only take artifacts that are defined in the Cargo.toml
// First, find the root artifact
let root = metadata
.packages
.iter()
.find(|package| {
package.manifest_path.as_std_path().canonicalize().unwrap()
== manifest_path.canonicalize().unwrap()
})
.unwrap();
// Then go over all of its dependencies
let dependencies = root
.dependencies
.iter()
.filter(|dep| matches!(dep.kind, DependencyKind::Normal))
// Only consider dependencies that are enabled on the current target
.filter(|dep| match &dep.target {
Some(platform) => platform.matches(config.target.as_ref().unwrap(), &cfg),
None => true,
})
.map(|dep| {
let package = metadata
.packages
.iter()
.find(|&p| p.name == dep.name && dep.req.matches(&p.version))
.expect("dependency does not exist");
(
package,
dep.rename.clone().unwrap_or_else(|| package.name.clone()),
)
})
// Also expose the root crate
.chain(std::iter::once((root, root.name.clone())))
.filter_map(|(package, name)| {
// Get the id for the package matching the version requirement of the dep
let id = &package.id;
// Return the name chosen in `Cargo.toml` and the path to the corresponding artifact
match artifacts.remove(id) {
Some(artifacts) => Some((name.replace('-', "_"), artifacts)),
None => {
if name == root.name {
// If there are no artifacts, this is the root crate and it is being built as a binary/test
// instead of a library. We simply add no artifacts, meaning you can't depend on functions
// and types declared in the root crate.
None
} else {
panic!("no artifact found for `{name}`(`{id}`):`\n{artifact_output}")
}
}
}
})
.collect();
let import_paths = import_paths.into_iter().collect();
return Ok(Dependencies {
dependencies,
import_paths,
});
}
bail!("no json found in cargo-metadata output")
}
|