Skip to content

Commit 13b74cb

Browse files
committed
Move custom-build-related code in its own module
1 parent 3fa3001 commit 13b74cb

File tree

2 files changed

+204
-192
lines changed

2 files changed

+204
-192
lines changed
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
use std::io::{fs, BufferedReader, BufReader, USER_RWX};
2+
use std::io::fs::{File, PathExtensions};
3+
4+
use core::{Package, Target};
5+
use util::{CargoResult, CargoError, human};
6+
use util::{internal, ChainError};
7+
8+
use super::job::Work;
9+
use super::{process, KindHost, Context};
10+
11+
/// Prepares a `Work` that executes the target as a custom build script.
12+
pub fn prepare_execute_custom_build(pkg: &Package, target: &Target,
13+
cx: &mut Context)
14+
-> CargoResult<Work> {
15+
let layout = cx.layout(pkg, KindHost);
16+
let script_output = layout.build(pkg);
17+
let build_output = layout.build_out(pkg);
18+
19+
// Building the command to execute
20+
let to_exec = try!(cx.target_filenames(target))[0].clone();
21+
let to_exec = script_output.join(to_exec);
22+
23+
// Filling environment variables
24+
let profile = target.get_profile();
25+
let mut p = process(to_exec, pkg, cx)
26+
.env("OUT_DIR", Some(&build_output))
27+
.env("CARGO_MANIFEST_DIR", Some(pkg.get_manifest_path()
28+
.display().to_string()))
29+
.env("NUM_JOBS", profile.get_codegen_units().map(|n| n.to_string()))
30+
.env("TARGET", Some(cx.target_triple()))
31+
.env("DEBUG", Some(profile.get_debug().to_string()))
32+
.env("OPT_LEVEL", Some(profile.get_opt_level().to_string()))
33+
.env("PROFILE", Some(profile.get_env()));
34+
35+
match cx.resolve.features(pkg.get_package_id()) {
36+
Some(features) => {
37+
for feat in features.iter() {
38+
let feat = feat.as_slice().chars()
39+
.map(|c| c.to_uppercase())
40+
.map(|c| if c == '-' {'_'} else {c})
41+
.collect::<String>();
42+
p = p.env(format!("CARGO_FEATURE_{}", feat).as_slice(), Some("1"));
43+
}
44+
}
45+
None => {}
46+
}
47+
48+
// building the list of all possible `build/$pkg/output` files
49+
// whether they exist or not will be checked during the work
50+
let command_output_files = {
51+
let layout = cx.layout(pkg, KindHost);
52+
cx.dep_targets(pkg).iter().map(|&(pkg, _)| {
53+
layout.build(pkg).join("output")
54+
}).collect::<Vec<_>>()
55+
};
56+
57+
// Building command
58+
let pkg = pkg.to_string();
59+
let work = proc(desc_tx: Sender<String>) {
60+
desc_tx.send_opt(build_output.display().to_string()).ok();
61+
62+
if !build_output.exists() {
63+
try!(fs::mkdir(&build_output, USER_RWX)
64+
.chain_error(|| {
65+
internal("failed to create build output directory for build command")
66+
}))
67+
}
68+
69+
// loading each possible custom build output file in order to get their metadata
70+
let _metadata = {
71+
let mut metadata = Vec::new();
72+
73+
for flags_file in command_output_files.into_iter() {
74+
match File::open(&flags_file) {
75+
Ok(flags) => {
76+
let flags = try!(CustomBuildCommandOutput::parse(
77+
BufferedReader::new(flags), pkg.as_slice()));
78+
metadata.extend(flags.metadata.into_iter());
79+
},
80+
Err(_) => () // the file doesn't exist, probably means that this pkg
81+
// doesn't have a build command
82+
}
83+
}
84+
85+
metadata
86+
};
87+
88+
// TODO: ENABLE THIS CODE WHEN `links` IS ADDED
89+
/*let mut p = p;
90+
for (key, value) in metadata.into_iter() {
91+
p = p.env(format!("DEP_{}_{}", PUT LINKS VALUES HERE, value), value);
92+
}*/
93+
94+
let output = try!(p.exec_with_output().map_err(|mut e| {
95+
e.msg = format!("Failed to run custom build command for `{}`\n{}",
96+
pkg, e.msg);
97+
e.mark_human()
98+
}));
99+
100+
// parsing the output of the custom build script to check that it's correct
101+
try!(CustomBuildCommandOutput::parse(BufReader::new(output.output.as_slice()),
102+
pkg.as_slice()));
103+
104+
// writing the output to the right directory
105+
try!(fs::File::create(&script_output.join("output")).write(output.output.as_slice())
106+
.map_err(|e| {
107+
human(format!("failed to write output of custom build command: {}", e))
108+
}));
109+
110+
Ok(())
111+
};
112+
113+
Ok(work)
114+
}
115+
116+
/// Contains the parsed output of a custom build script.
117+
pub struct CustomBuildCommandOutput {
118+
/// Paths to pass to rustc with the `-L` flag
119+
pub library_paths: Vec<Path>,
120+
/// Names and link kinds of libraries, suitable for the `-l` flag
121+
pub library_links: Vec<String>,
122+
/// Metadata to pass to the immediate dependencies
123+
pub metadata: Vec<(String, String)>,
124+
}
125+
126+
impl CustomBuildCommandOutput {
127+
// Parses the output of a script.
128+
// The `pkg_name` is used for error messages.
129+
pub fn parse<B: Buffer>(mut input: B, pkg_name: &str) -> CargoResult<CustomBuildCommandOutput> {
130+
let mut library_paths = Vec::new();
131+
let mut library_links = Vec::new();
132+
let mut metadata = Vec::new();
133+
134+
for line in input.lines() {
135+
// unwrapping the IoResult
136+
let line = try!(line.map_err(|e| human(format!("Error while reading\
137+
custom build output: {}", e))));
138+
139+
let mut iter = line.as_slice().splitn(1, |c: char| c == ':');
140+
if iter.next() != Some("cargo") {
141+
// skip this line since it doesn't start with "cargo:"
142+
continue;
143+
}
144+
let data = match iter.next() {
145+
Some(val) => val,
146+
None => continue
147+
};
148+
149+
// getting the `key=value` part of the line
150+
let mut iter = data.splitn(1, |c: char| c == '=');
151+
let key = iter.next();
152+
let value = iter.next();
153+
let (key, value) = match (key, value) {
154+
(Some(a), Some(b)) => (a, b),
155+
// line started with `cargo:` but didn't match `key=value`
156+
_ => return Err(human(format!("Wrong output for the custom\
157+
build script of `{}`:\n`{}`", pkg_name, line)))
158+
};
159+
160+
if key == "rustc-flags" {
161+
// TODO: some arguments (like paths) may contain spaces
162+
let mut flags_iter = value.words();
163+
loop {
164+
let flag = match flags_iter.next() {
165+
Some(f) => f,
166+
None => break
167+
};
168+
if flag != "-l" && flag != "-L" {
169+
return Err(human(format!("Only `-l` and `-L` flags are allowed \
170+
in build script of `{}`:\n`{}`",
171+
pkg_name, value)))
172+
}
173+
let value = match flags_iter.next() {
174+
Some(v) => v,
175+
None => return Err(human(format!("Flag in rustc-flags has no value\
176+
in build script of `{}`:\n`{}`",
177+
pkg_name, value)))
178+
};
179+
match flag {
180+
"-l" => library_links.push(value.to_string()),
181+
"-L" => library_paths.push(Path::new(value)),
182+
183+
// was already checked above
184+
_ => return Err(human("only -l and -L flags are allowed"))
185+
};
186+
}
187+
} else {
188+
metadata.push((key.to_string(), value.to_string()))
189+
}
190+
}
191+
192+
Ok(CustomBuildCommandOutput {
193+
library_paths: library_paths,
194+
library_links: library_links,
195+
metadata: metadata,
196+
})
197+
}
198+
}

0 commit comments

Comments
 (0)