]> git.proxmox.com Git - cargo.git/blame - src/cargo/ops/cargo_rustc/custom_build.rs
auto merge of #901 : alexcrichton/cargo/update-faq, r=brson
[cargo.git] / src / cargo / ops / cargo_rustc / custom_build.rs
CommitLineData
800d9eb8 1use std::collections::HashMap;
a0d499e0 2use std::fmt;
63915360 3use std::io::fs::PathExtensions;
3b21f7ac
AC
4use std::io::{fs, USER_RWX, File};
5use std::str;
800d9eb8 6use std::sync::Mutex;
d712fddb 7
800d9eb8 8use core::{Package, Target, PackageId, PackageSet};
d712fddb 9use util::{CargoResult, CargoError, human};
3b21f7ac 10use util::{internal, ChainError, Require};
d712fddb
PK
11
12use super::job::Work;
ac4eddbb 13use super::{fingerprint, process, KindTarget, KindHost, Kind, Context};
673c30de
AC
14use super::{PlatformPlugin, PlatformPluginAndTarget, PlatformTarget};
15use super::PlatformRequirement;
a0d499e0 16use util::Freshness;
d712fddb 17
63915360
AC
18/// Contains the parsed output of a custom build script.
19#[deriving(Clone)]
20pub 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 29pub 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).
39pub 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 197impl 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 242impl 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
326impl 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}