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