]> git.proxmox.com Git - cargo.git/blame - src/cargo/ops/cargo_rustc/custom_build.rs
Remove leading `get_` prefix from accessors
[cargo.git] / src / cargo / ops / cargo_rustc / custom_build.rs
CommitLineData
800d9eb8 1use std::collections::HashMap;
55321111 2use std::ffi::CString;
af45e088
GSF
3use std::old_io::fs::PathExtensions;
4use std::old_io::{fs, USER_RWX, File};
3b21f7ac 5use std::str;
800d9eb8 6use std::sync::Mutex;
d712fddb 7
800d9eb8 8use core::{Package, Target, PackageId, PackageSet};
9ed3a6ea 9use util::{CargoResult, human, Human};
039c523c 10use util::{internal, ChainError};
d712fddb
PK
11
12use super::job::Work;
38d14a59 13use super::{fingerprint, process, Kind, Context, Platform};
3656bec8 14use super::CommandType;
a0d499e0 15use util::Freshness;
d712fddb 16
63915360 17/// Contains the parsed output of a custom build script.
213afc02 18#[derive(Clone, Debug)]
63915360
AC
19pub 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
28pub type BuildMap = HashMap<(PackageId, Kind), BuildOutput>;
29
800d9eb8 30pub 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 40pub 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 206impl 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 251impl 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}