1extern crate badge;
2extern crate cargo;
3extern crate fs_extra;
4#[macro_use]
5extern crate serde_json;
6
7use badge::{Badge, BadgeOptions};
8use cargo::core::Workspace;
9use cargo::ops::CompileOptions;
10use cargo::util::{config::Config, errors::ProcessError, process, CargoTestError, Test};
11use cargo::CargoResult;
12use std::env;
13use std::ffi::OsString;
14use std::fs;
15use std::io::Write;
16use std::path::{Path, PathBuf};
17use std::process::{self, Command};
18
19pub struct CoverageOptions<'a> {
20 pub compile_opts: CompileOptions<'a>,
21 pub merge_dir: &'a Path,
22 pub no_fail_fast: bool,
23 pub kcov_path: &'a Path,
24 pub merge_args: Vec<OsString>, pub exclude_pattern: Option<String>
26}
27
28pub fn run_coverage(ws: &Workspace, options: &CoverageOptions, test_args: &[String]) -> CargoResult<Option<CargoTestError>> {
29 let mut rustflags: std::ffi::OsString = "-C link-dead-code".into();
36 if options.compile_opts.build_config.release {
37 rustflags.push(" -C debuginfo=2");
40 }
41 if let Some(existing) = std::env::var_os("RUSTFLAGS") {
42 rustflags.push(" ");
43 rustflags.push(existing);
44 }
45 std::env::set_var("RUSTFLAGS", rustflags);
46
47
48 let mut compilation = try!(cargo::ops::compile(ws, &options.compile_opts));
49 compilation.tests.sort_by(|a, b| {
50 (a.0.package_id(), &a.1).cmp(&(b.0.package_id(), &b.1))
51 });
52
53 let config = options.compile_opts.config;
54 let cwd = options.compile_opts.config.cwd();
55
56 let mut errors = vec![];
57
58 let v : Vec<std::ffi::OsString> = test_args.iter().cloned().map::<std::ffi::OsString, _>(|val| val.into()).collect();
59
60 for &(ref pkg, ref kind, ref test, ref exe) in &compilation.tests {
63 let to_display = match cargo::util::without_prefix(exe, &cwd) {
64 Some(path) => path,
65 None => &**exe
66 };
67
68 let mut cmd = try!(compilation.target_process(options.kcov_path, pkg));
70 let target = ws.target_dir().join("kcov-".to_string() + to_display.file_name().unwrap().to_str().unwrap()).into_path_unlocked();
73 let default_include_path = format!("--include-path={}", ws.root().display());
74
75 let mut args = vec![
76 OsString::from("--verify"),
77 OsString::from(default_include_path),
78 OsString::from(target)];
79
80 if let Some(ref exclude) = options.exclude_pattern {
82 let exclude_option = OsString::from(format!("--exclude-pattern={}", exclude));
83 args.push(exclude_option);
84 }
85
86 args.push(OsString::from(exe));
87
88 args.extend(v.clone());
89 cmd.args(&args);
90 try!(config.shell().concise(|shell| {
91 shell.status("Running", to_display.display().to_string())
92 }));
93 try!(config.shell().verbose(|shell| {
94 shell.status("Running", cmd.to_string())
95 }));
96
97 let result = cmd.exec();
98
99 match result {
100 Err(e) => {
101 match e.downcast::<ProcessError>() {
102 Ok(e) => {
103 errors.push(e);
104 if !options.no_fail_fast {
105 return Ok(Some(CargoTestError::new(Test::UnitTest {
106 kind: kind.clone(),
107 name: test.clone(),
108 pkg_name: pkg.name().to_string(),
109 }, errors)))
110 }
111 }
112 Err(e) => {
113 return Err(e)
115 }
116 }
117 }
118 Ok(()) => {}
119 }
120 }
121
122 let mut mergeargs : Vec<OsString> = vec!["--merge".to_string().into(), options.merge_dir.as_os_str().to_os_string()];
124 mergeargs.extend(options.merge_args.iter().cloned());
125 mergeargs.extend(compilation.tests.iter().map(|&(_, _, _, ref exe)|
126 ws.target_dir().join("kcov-".to_string() + exe.file_name().unwrap().to_str().unwrap()).into_path_unlocked().into()
127 ));
128 let mut cmd = process(options.kcov_path.as_os_str().to_os_string());
129 cmd.args(&mergeargs);
130 try!(config.shell().concise(|shell| {
131 shell.status("Merging coverage", options.merge_dir.display().to_string())
132 }));
133 try!(config.shell().verbose(|shell| {
134 shell.status("Merging coverage", cmd.to_string())
135 }));
136 try!(cmd.exec());
137 if errors.is_empty() {
138 Ok(None)
139 } else {
140 Ok(Some(CargoTestError::new(Test::Multiple, errors)))
141 }
142}
143
144fn require_success(status: process::ExitStatus) {
145 if !status.success() {
146 process::exit(status.code().unwrap())
147 }
148}
149
150pub fn build_kcov<P: AsRef<Path>>(kcov_dir: P) -> PathBuf {
151 if let Some(paths) = std::env::var_os("PATH") {
153 for path in std::env::split_paths(&paths) {
154 if path.join("kcov").exists() {
155 return path.join("kcov");
156 }
157 }
158 }
159
160 let kcov_dir: &Path = kcov_dir.as_ref();
161 let kcov_master_dir = kcov_dir.join("kcov-master");
162 let kcov_build_dir = kcov_master_dir.join("build");
163 let kcov_built_path = kcov_build_dir.join("src/kcov");
164
165 if kcov_built_path.exists() {
167 return kcov_built_path;
168 }
169
170 println!("Downloading kcov");
172 require_success(
173 Command::new("wget")
174 .current_dir(kcov_dir)
175 .arg("https://github.com/SimonKagstrom/kcov/archive/master.zip")
176 .status()
177 .unwrap()
178 );
179
180 println!("Extracting kcov");
182 require_success(
183 Command::new("unzip")
184 .current_dir(kcov_dir)
185 .arg("master.zip")
186 .status()
187 .unwrap()
188 );
189
190 fs::create_dir(&kcov_build_dir).expect(&format!("Failed to created dir {:?} for kcov", kcov_build_dir));
192 println!("CMaking kcov");
193 require_success(
194 Command::new("cmake")
195 .current_dir(&kcov_build_dir)
196 .arg("..")
197 .status()
198 .unwrap()
199 );
200 println!("Making kcov");
201 require_success(
202 Command::new("make")
203 .current_dir(&kcov_build_dir)
204 .status()
205 .unwrap()
206 );
207
208 assert!(kcov_build_dir.exists());
209 kcov_built_path
210}
211
212pub fn doc_upload(message: &str, origin: &str, gh_pages: &str, doc_path: &str, local_doc_path: &Path, clobber_index: bool) -> Result<(), (String, i32)> {
213 let doc_upload = Path::new("target/doc-upload");
214
215 if !doc_upload.exists() {
216 let status = Command::new("git")
219 .arg("clone")
220 .arg("--verbose")
221 .args(&["--branch", gh_pages])
222 .args(&["--depth", "1"])
223 .arg(origin)
224 .arg(doc_upload)
225 .status()
226 .unwrap();
227 if !status.success() {
228 require_success(
231 Command::new("git")
232 .arg("init")
233 .arg(doc_upload)
234 .status()
235 .unwrap()
236 );
237 require_success(
238 Command::new("git")
239 .current_dir(doc_upload)
240 .arg("checkout")
241 .args(&["-b", gh_pages])
242 .status()
243 .unwrap()
244 );
245 }
246 }
247
248 let doc_upload_branch = doc_upload.join(doc_path);
249 fs::create_dir(&doc_upload_branch).ok(); let doc_upload_branch = doc_upload_branch.canonicalize().unwrap();
253
254 if !doc_upload_branch.starts_with(env::current_dir().unwrap().join(doc_upload)) {
255 return Err(("Path passed in `--path` is outside the intended `target/doc-upload` folder".to_string(), 1));
256 }
257
258 for entry in doc_upload_branch.read_dir().unwrap() {
259 let dir = entry.unwrap();
260 if dir.file_name() != OsString::from("index.html")
265 || clobber_index
266 || local_doc_path.join("index.html").exists()
267 {
268 let path = dir.path();
269 println!("rm -r {}", path.to_string_lossy());
270 fs::remove_dir_all(&path).ok();
271 fs::remove_file(path).ok();
272 }
273 }
274
275 let mut badge_status = "no builds".to_string();
277 let mut badge_color = "#e05d44".to_string();
278
279 let config = Config::default().expect("failed to create cargo Config");
281 let mut version = Err(());
282
283 let mut manifest = env::current_dir().unwrap();
284 manifest.push("Cargo.toml");
285
286 match Workspace::new(&manifest, &config) {
287 Ok(workspace) => match workspace.current() {
288 Ok(package) => version = Ok(format!("{}", package.manifest().version())),
289 Err(error) => println!("couldn't get package: {}", error),
290 },
291 Err(error) => println!("couldn't generate workspace: {}", error),
292 }
293
294 if let Ok(version) = &version {
296 badge_status = version.clone();
297 }
298
299 let doc = local_doc_path;
300 println!("cp {} {}", doc.to_string_lossy(), doc_upload_branch.to_string_lossy());
301 let mut last_progress = 0;
302
303 let mut result = Ok(());
304
305 if let Ok(doc) = doc.read_dir() {
306 fs_extra::copy_items_with_progress(
307 &doc.map(|entry| entry.unwrap().path()).collect(),
308 &doc_upload_branch,
309 &fs_extra::dir::CopyOptions::new(),
310 |info| {
311 if info.copied_bytes >> 20 > last_progress {
314 last_progress = info.copied_bytes >> 20;
315 println!("{}/{} MiB", info.copied_bytes >> 20, info.total_bytes >> 20);
316 }
317 fs_extra::dir::TransitProcessResult::ContinueOrAbort
318 }
319 ).unwrap();
320
321 if version.is_ok() {
324 badge_color = "#4d76ae".to_string();
325 }
326 }
327 else {
328 println!("No documentation found to upload.");
329 result = Err(("No documentation generated".to_string(), 1));
330 }
331
332 let json = json!({
334 "schemaVersion": 1,
335 "label": "docs",
336 "message": badge_status,
337 "color": badge_color
338 });
339
340 let mut file = fs::File::create(doc_upload_branch.join("badge.json")).unwrap();
341 file.write_all(json.to_string().as_bytes()).unwrap();
342
343 let badge_options = BadgeOptions {
345 subject: "docs".to_string(),
346 status: badge_status.to_string(),
347 color: badge_color.to_string(),
348 };
349
350 let mut file = fs::File::create(doc_upload_branch.join("badge.svg")).unwrap();
351 file.write_all(Badge::new(badge_options).unwrap().to_svg().as_bytes()).unwrap();
352
353 require_success(
356 Command::new("git")
357 .current_dir(doc_upload)
358 .arg("add")
359 .arg("--verbose")
360 .arg("--all")
361 .status()
362 .unwrap()
363 );
364
365 if Command::new("git")
367 .current_dir(doc_upload)
368 .arg("commit")
369 .arg("--verbose")
370 .args(&["-m", message])
371 .status().is_err()
372 {
373 println!("No changes to the documentation.");
374 } else {
375 require_success(
377 Command::new("git")
378 .current_dir(doc_upload)
379 .arg("push")
380 .arg(origin)
381 .arg(gh_pages)
382 .status()
383 .unwrap(),
384 );
385 }
386 result
387}