]>
Commit | Line | Data |
---|---|---|
800d9eb8 | 1 | use std::collections::HashMap; |
55321111 | 2 | use std::ffi::CString; |
af45e088 GSF |
3 | use std::old_io::fs::PathExtensions; |
4 | use std::old_io::{fs, USER_RWX, File}; | |
3b21f7ac | 5 | use std::str; |
800d9eb8 | 6 | use std::sync::Mutex; |
d712fddb | 7 | |
800d9eb8 | 8 | use core::{Package, Target, PackageId, PackageSet}; |
9ed3a6ea | 9 | use util::{CargoResult, human, Human}; |
039c523c | 10 | use util::{internal, ChainError}; |
d712fddb PK |
11 | |
12 | use super::job::Work; | |
38d14a59 | 13 | use super::{fingerprint, process, Kind, Context, Platform}; |
3656bec8 | 14 | use super::CommandType; |
a0d499e0 | 15 | use util::Freshness; |
d712fddb | 16 | |
63915360 | 17 | /// Contains the parsed output of a custom build script. |
213afc02 | 18 | #[derive(Clone, Debug)] |
63915360 AC |
19 | pub struct BuildOutput { |
20 | /// Paths to pass to rustc with the `-L` flag | |
21 | pub library_paths: Vec<Path>, | |
22 | /// Names and link kinds of libraries, suitable for the `-l` flag | |
23 | pub library_links: Vec<String>, | |
24 | /// Metadata to pass to the immediate dependencies | |
25 | pub metadata: Vec<(String, String)>, | |
26 | } | |
27 | ||
50e00c97 AC |
28 | pub type BuildMap = HashMap<(PackageId, Kind), BuildOutput>; |
29 | ||
800d9eb8 | 30 | pub struct BuildState { |
50e00c97 | 31 | pub outputs: Mutex<BuildMap>, |
800d9eb8 AC |
32 | } |
33 | ||
d712fddb | 34 | /// Prepares a `Work` that executes the target as a custom build script. |
673c30de AC |
35 | /// |
36 | /// The `req` given is the requirement which this run of the build script will | |
37 | /// prepare work for. If the requirement is specified as both the target and the | |
38 | /// host platforms it is assumed that the two are equal and the build script is | |
39 | /// only run once (not twice). | |
38d14a59 | 40 | pub fn prepare(pkg: &Package, target: &Target, req: Platform, |
673c30de | 41 | cx: &mut Context) -> CargoResult<(Work, Work, Freshness)> { |
38d14a59 | 42 | let kind = match req { Platform::Plugin => Kind::Host, _ => Kind::Target, }; |
7a942be8 | 43 | let (script_output, build_output) = { |
38d14a59 | 44 | (cx.layout(pkg, Kind::Host).build(pkg), |
a49305a1 | 45 | cx.layout(pkg, kind).build_out(pkg)) |
a0d499e0 | 46 | }; |
d712fddb PK |
47 | |
48 | // Building the command to execute | |
49 | let to_exec = try!(cx.target_filenames(target))[0].clone(); | |
50 | let to_exec = script_output.join(to_exec); | |
51 | ||
a0d499e0 AC |
52 | // Start preparing the process to execute, starting out with some |
53 | // environment variables. | |
7a2facba | 54 | let profile = target.profile(); |
55321111 AC |
55 | let to_exec = CString::from_slice(to_exec.as_vec()); |
56 | let p = try!(super::process(CommandType::Host(to_exec), pkg, target, cx)); | |
57 | let mut p = p.env("OUT_DIR", Some(&build_output)) | |
7a2facba | 58 | .env("CARGO_MANIFEST_DIR", Some(pkg.manifest_path() |
55321111 AC |
59 | .dir_path() |
60 | .display().to_string())) | |
5d0cb3f2 | 61 | .env("NUM_JOBS", Some(cx.jobs().to_string())) |
55321111 AC |
62 | .env("TARGET", Some(match kind { |
63 | Kind::Host => cx.config.rustc_host(), | |
64 | Kind::Target => cx.target_triple(), | |
65 | })) | |
7a2facba AC |
66 | .env("DEBUG", Some(profile.debug().to_string())) |
67 | .env("OPT_LEVEL", Some(profile.opt_level().to_string())) | |
68 | .env("PROFILE", Some(profile.env())) | |
55321111 | 69 | .env("HOST", Some(cx.config.rustc_host())); |
d712fddb | 70 | |
a0d499e0 AC |
71 | // Be sure to pass along all enabled features for this package, this is the |
72 | // last piece of statically known information that we have. | |
7a2facba | 73 | match cx.resolve.features(pkg.package_id()) { |
d712fddb PK |
74 | Some(features) => { |
75 | for feat in features.iter() { | |
25e537aa | 76 | p = p.env(&format!("CARGO_FEATURE_{}", super::envify(feat)), |
63915360 | 77 | Some("1")); |
d712fddb PK |
78 | } |
79 | } | |
80 | None => {} | |
81 | } | |
82 | ||
a0d499e0 AC |
83 | // Gather the set of native dependencies that this package has along with |
84 | // some other variables to close over. | |
85 | // | |
86 | // This information will be used at build-time later on to figure out which | |
87 | // sorts of variables need to be discovered at that time. | |
63915360 | 88 | let lib_deps = { |
7a2facba AC |
89 | let non_build_target = pkg.targets().iter().find(|t| { |
90 | !t.profile().is_custom_build() | |
8960dd18 AC |
91 | }).unwrap(); |
92 | cx.dep_targets(pkg, non_build_target).iter().filter_map(|&(pkg, _)| { | |
7a2facba AC |
93 | pkg.manifest().links().map(|links| { |
94 | (links.to_string(), pkg.package_id().clone()) | |
800d9eb8 AC |
95 | }) |
96 | }).collect::<Vec<_>>() | |
d712fddb | 97 | }; |
a0d499e0 | 98 | let pkg_name = pkg.to_string(); |
800d9eb8 | 99 | let build_state = cx.build_state.clone(); |
7a2facba | 100 | let id = pkg.package_id().clone(); |
800d9eb8 | 101 | let all = (id.clone(), pkg_name.clone(), build_state.clone(), |
a5c868a9 | 102 | build_output.clone()); |
50e00c97 | 103 | let plugin_deps = super::crawl_build_deps(cx, pkg, target, Kind::Host); |
63915360 | 104 | |
38d14a59 AC |
105 | try!(fs::mkdir_recursive(&cx.layout(pkg, Kind::Target).build(pkg), USER_RWX)); |
106 | try!(fs::mkdir_recursive(&cx.layout(pkg, Kind::Host).build(pkg), USER_RWX)); | |
d712fddb | 107 | |
3656bec8 PK |
108 | let exec_engine = cx.exec_engine.clone(); |
109 | ||
a0d499e0 AC |
110 | // Prepare the unit of "dirty work" which will actually run the custom build |
111 | // command. | |
112 | // | |
113 | // Note that this has to do some extra work just before running the command | |
114 | // to determine extra environment variables and such. | |
50e00c97 | 115 | let work = Work::new(move |desc_tx| { |
a0d499e0 AC |
116 | // Make sure that OUT_DIR exists. |
117 | // | |
118 | // If we have an old build directory, then just move it into place, | |
119 | // otherwise create it! | |
7a942be8 AC |
120 | if !build_output.exists() { |
121 | try!(fs::mkdir(&build_output, USER_RWX).chain_error(|| { | |
122 | internal("failed to create script output directory for \ | |
123 | build command") | |
124 | })); | |
125 | } | |
d712fddb | 126 | |
a0d499e0 | 127 | // For all our native lib dependencies, pick up their metadata to pass |
50e00c97 AC |
128 | // along to this custom build command. We're also careful to augment our |
129 | // dynamic library search path in case the build script depended on any | |
130 | // native dynamic libraries. | |
63915360 AC |
131 | let mut p = p; |
132 | { | |
31534136 | 133 | let build_state = build_state.outputs.lock().unwrap(); |
800d9eb8 | 134 | for &(ref name, ref id) in lib_deps.iter() { |
ac4eddbb AC |
135 | let data = &build_state[(id.clone(), kind)].metadata; |
136 | for &(ref key, ref value) in data.iter() { | |
25e537aa AC |
137 | p = p.env(&format!("DEP_{}_{}", super::envify(name), |
138 | super::envify(key)), | |
139 | Some(value)); | |
d712fddb PK |
140 | } |
141 | } | |
e0b31b6b | 142 | p = try!(super::add_plugin_deps(p, &build_state, plugin_deps)); |
63915360 | 143 | } |
d712fddb | 144 | |
a0d499e0 | 145 | // And now finally, run the build command itself! |
ba280047 | 146 | desc_tx.send(p.to_string()).ok(); |
3656bec8 | 147 | let output = try!(exec_engine.exec_with_output(p).map_err(|mut e| { |
9ed3a6ea AC |
148 | e.desc = format!("failed to run custom build command for `{}`\n{}", |
149 | pkg_name, e.desc); | |
150 | Human(e) | |
d712fddb PK |
151 | })); |
152 | ||
a0d499e0 AC |
153 | // After the build command has finished running, we need to be sure to |
154 | // remember all of its output so we can later discover precisely what it | |
155 | // was, even if we don't run the build command again (due to freshness). | |
156 | // | |
157 | // This is also the location where we provide feedback into the build | |
158 | // state informing what variables were discovered via our script as | |
159 | // well. | |
25e537aa | 160 | let output = try!(str::from_utf8(&output.output).chain_error(|| { |
3b21f7ac AC |
161 | human("build script output was not valid utf-8") |
162 | })); | |
25e537aa | 163 | let parsed_output = try!(BuildOutput::parse(output, &pkg_name)); |
673c30de | 164 | build_state.insert(id, req, parsed_output); |
d712fddb | 165 | |
d29e3a0f | 166 | try!(File::create(&build_output.dir_path().join("output")) |
3b21f7ac | 167 | .write_str(output).map_err(|e| { |
a0d499e0 AC |
168 | human(format!("failed to write output of custom build command: {}", |
169 | e)) | |
170 | })); | |
d712fddb PK |
171 | |
172 | Ok(()) | |
50e00c97 | 173 | }); |
d712fddb | 174 | |
a0d499e0 AC |
175 | // Now that we've prepared our work-to-do, we need to prepare the fresh work |
176 | // itself to run when we actually end up just discarding what we calculated | |
177 | // above. | |
178 | // | |
179 | // Note that the freshness calculation here is the build_cmd freshness, not | |
180 | // target specific freshness. This is because we don't actually know what | |
181 | // the inputs are to this command! | |
3b21f7ac AC |
182 | // |
183 | // Also note that a fresh build command needs to | |
a0d499e0 | 184 | let (freshness, dirty, fresh) = |
0fdaf507 | 185 | try!(fingerprint::prepare_build_cmd(cx, pkg, kind, Some(target))); |
ba280047 | 186 | let dirty = Work::new(move |tx| { |
50e00c97 | 187 | try!(work.call((tx.clone()))); |
157d639a AC |
188 | dirty.call(tx) |
189 | }); | |
190 | let fresh = Work::new(move |tx| { | |
7a942be8 | 191 | let (id, pkg_name, build_state, build_output) = all; |
d29e3a0f | 192 | let new_loc = build_output.dir_path().join("output"); |
3b21f7ac AC |
193 | let mut f = try!(File::open(&new_loc).map_err(|e| { |
194 | human(format!("failed to read cached build command output: {}", e)) | |
195 | })); | |
196 | let contents = try!(f.read_to_string()); | |
25e537aa | 197 | let output = try!(BuildOutput::parse(&contents, &pkg_name)); |
673c30de | 198 | build_state.insert(id, req, output); |
3b21f7ac | 199 | |
157d639a AC |
200 | fresh.call(tx) |
201 | }); | |
a0d499e0 AC |
202 | |
203 | Ok((dirty, fresh, freshness)) | |
d712fddb PK |
204 | } |
205 | ||
800d9eb8 | 206 | impl BuildState { |
ac4eddbb | 207 | pub fn new(config: super::BuildConfig, |
800d9eb8 AC |
208 | packages: &PackageSet) -> BuildState { |
209 | let mut sources = HashMap::new(); | |
210 | for package in packages.iter() { | |
7a2facba | 211 | match package.manifest().links() { |
800d9eb8 AC |
212 | Some(links) => { |
213 | sources.insert(links.to_string(), | |
7a2facba | 214 | package.package_id().clone()); |
800d9eb8 AC |
215 | } |
216 | None => {} | |
217 | } | |
218 | } | |
219 | let mut outputs = HashMap::new(); | |
38d14a59 AC |
220 | let i1 = config.host.overrides.into_iter().map(|p| (p, Kind::Host)); |
221 | let i2 = config.target.overrides.into_iter().map(|p| (p, Kind::Target)); | |
0c08e0d7 AC |
222 | for ((name, output), kind) in i1.chain(i2) { |
223 | match sources.get(&name) { | |
224 | Some(id) => { outputs.insert((id.clone(), kind), output); } | |
d29e3a0f AC |
225 | |
226 | // If no package is using the library named `name`, then this is | |
227 | // just an override that we ignore. | |
0c08e0d7 AC |
228 | None => {} |
229 | } | |
800d9eb8 AC |
230 | } |
231 | BuildState { outputs: Mutex::new(outputs) } | |
232 | } | |
673c30de | 233 | |
38d14a59 | 234 | fn insert(&self, id: PackageId, req: Platform, |
673c30de | 235 | output: BuildOutput) { |
31534136 | 236 | let mut outputs = self.outputs.lock().unwrap(); |
673c30de | 237 | match req { |
38d14a59 AC |
238 | Platform::Target => { outputs.insert((id, Kind::Target), output); } |
239 | Platform::Plugin => { outputs.insert((id, Kind::Host), output); } | |
673c30de AC |
240 | |
241 | // If this build output was for both the host and target platforms, | |
242 | // we need to insert it at both places. | |
38d14a59 AC |
243 | Platform::PluginAndTarget => { |
244 | outputs.insert((id.clone(), Kind::Host), output.clone()); | |
245 | outputs.insert((id, Kind::Target), output); | |
673c30de AC |
246 | } |
247 | } | |
248 | } | |
800d9eb8 AC |
249 | } |
250 | ||
63915360 | 251 | impl BuildOutput { |
d712fddb PK |
252 | // Parses the output of a script. |
253 | // The `pkg_name` is used for error messages. | |
3b21f7ac | 254 | pub fn parse(input: &str, pkg_name: &str) -> CargoResult<BuildOutput> { |
d712fddb PK |
255 | let mut library_paths = Vec::new(); |
256 | let mut library_links = Vec::new(); | |
257 | let mut metadata = Vec::new(); | |
63915360 | 258 | let whence = format!("build script of `{}`", pkg_name); |
d712fddb PK |
259 | |
260 | for line in input.lines() { | |
157d639a | 261 | let mut iter = line.splitn(1, |&: c: char| c == ':'); |
d712fddb PK |
262 | if iter.next() != Some("cargo") { |
263 | // skip this line since it doesn't start with "cargo:" | |
264 | continue; | |
265 | } | |
266 | let data = match iter.next() { | |
267 | Some(val) => val, | |
268 | None => continue | |
269 | }; | |
270 | ||
271 | // getting the `key=value` part of the line | |
157d639a | 272 | let mut iter = data.splitn(1, |&: c: char| c == '='); |
d712fddb PK |
273 | let key = iter.next(); |
274 | let value = iter.next(); | |
275 | let (key, value) = match (key, value) { | |
a0d499e0 | 276 | (Some(a), Some(b)) => (a, b.trim_right()), |
d712fddb | 277 | // line started with `cargo:` but didn't match `key=value` |
63915360 AC |
278 | _ => return Err(human(format!("Wrong output in {}: `{}`", |
279 | whence, line))) | |
d712fddb PK |
280 | }; |
281 | ||
282 | if key == "rustc-flags" { | |
63915360 | 283 | let (libs, links) = try!( |
25e537aa | 284 | BuildOutput::parse_rustc_flags(value, &whence) |
63915360 AC |
285 | ); |
286 | library_links.extend(links.into_iter()); | |
287 | library_paths.extend(libs.into_iter()); | |
d712fddb PK |
288 | } else { |
289 | metadata.push((key.to_string(), value.to_string())) | |
290 | } | |
291 | } | |
292 | ||
63915360 | 293 | Ok(BuildOutput { |
d712fddb PK |
294 | library_paths: library_paths, |
295 | library_links: library_links, | |
296 | metadata: metadata, | |
297 | }) | |
298 | } | |
63915360 AC |
299 | |
300 | pub fn parse_rustc_flags(value: &str, whence: &str) | |
301 | -> CargoResult<(Vec<Path>, Vec<String>)> { | |
302 | // TODO: some arguments (like paths) may contain spaces | |
303 | let value = value.trim(); | |
304 | let mut flags_iter = value.words(); | |
305 | let (mut library_links, mut library_paths) = (Vec::new(), Vec::new()); | |
306 | loop { | |
307 | let flag = match flags_iter.next() { | |
308 | Some(f) => f, | |
309 | None => break | |
310 | }; | |
311 | if flag != "-l" && flag != "-L" { | |
312 | return Err(human(format!("Only `-l` and `-L` flags are allowed \ | |
313 | in {}: `{}`", | |
314 | whence, value))) | |
315 | } | |
316 | let value = match flags_iter.next() { | |
317 | Some(v) => v, | |
71b5a03f | 318 | None => return Err(human(format!("Flag in rustc-flags has no value \ |
63915360 AC |
319 | in {}: `{}`", |
320 | whence, value))) | |
321 | }; | |
322 | match flag { | |
323 | "-l" => library_links.push(value.to_string()), | |
324 | "-L" => library_paths.push(Path::new(value)), | |
325 | ||
326 | // was already checked above | |
327 | _ => return Err(human("only -l and -L flags are allowed")) | |
328 | }; | |
329 | } | |
330 | Ok((library_paths, library_links)) | |
331 | } | |
d712fddb | 332 | } |