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