]> git.proxmox.com Git - cargo.git/blame - src/cargo/ops/cargo_rustc/custom_build.rs
Add tests for other cargo cmds + -L propagation
[cargo.git] / src / cargo / ops / cargo_rustc / custom_build.rs
CommitLineData
a0d499e0 1use std::fmt;
63915360 2use std::io::fs::PathExtensions;
3b21f7ac
AC
3use std::io::{fs, USER_RWX, File};
4use std::str;
d712fddb
PK
5
6use core::{Package, Target};
7use util::{CargoResult, CargoError, human};
3b21f7ac 8use util::{internal, ChainError, Require};
d712fddb
PK
9
10use super::job::Work;
a0d499e0
AC
11use super::{fingerprint, process, KindHost, Context};
12use util::Freshness;
d712fddb 13
63915360
AC
14/// Contains the parsed output of a custom build script.
15#[deriving(Clone)]
16pub 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
26pub 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 185impl 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
269impl 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}