1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
use std::default::Default;
use std::fs;
use std::io::prelude::*;
use std::path::Path;

use core::{Package, PackageSet, Profiles, Profile};
use core::source::{Source, SourceMap};
use util::{CargoResult, human, ChainError, Config};
use ops::{self, Layout, Context, BuildConfig, Kind, Unit};

pub struct CleanOptions<'a> {
    pub spec: &'a [String],
    pub target: Option<&'a str>,
    pub config: &'a Config,
}

/// Cleans the project from build artifacts.
pub fn clean(manifest_path: &Path, opts: &CleanOptions) -> CargoResult<()> {
    let root = try!(Package::for_path(manifest_path, opts.config));
    let target_dir = opts.config.target_dir(&root);

    // If we have a spec, then we need to delete some packages, otherwise, just
    // remove the whole target directory and be done with it!
    if opts.spec.len() == 0 {
        return rm_rf(&target_dir);
    }

    // Load the lockfile (if one's available)
    let lockfile = root.root().join("Cargo.lock");
    let source_id = root.package_id().source_id();
    let resolve = match try!(ops::load_lockfile(&lockfile, source_id)) {
        Some(resolve) => resolve,
        None => return Err(human("A Cargo.lock must exist before cleaning"))
    };

    // Create a compilation context to have access to information like target
    // filenames and such
    let srcs = SourceMap::new();
    let pkgs = PackageSet::new(&[]);
    let profiles = Profiles::default();
    let cx = try!(Context::new(&resolve, &srcs, &pkgs, opts.config,
                               Layout::at(target_dir),
                               None, BuildConfig::default(),
                               &profiles));

    // resolve package specs and remove the corresponding packages
    for spec in opts.spec {
        let pkgid = try!(resolve.query(spec));

        // Translate the PackageId to a Package
        let pkg = {
            let mut source = pkgid.source_id().load(opts.config);
            try!(source.update());
            (try!(source.get(&[pkgid.clone()]))).into_iter().next().unwrap()
        };

        // And finally, clean everything out!
        for target in pkg.targets().iter() {
            // TODO: `cargo clean --release`
            let layout = Layout::new(opts.config, &root, opts.target, "debug");
            try!(rm_rf(&layout.fingerprint(&pkg)));
            let profiles = [Profile::default_dev(), Profile::default_test()];
            for profile in profiles.iter() {
                let unit = Unit {
                    pkg: &pkg,
                    target: target,
                    profile: profile,
                    kind: Kind::Target,
                };
                for filename in try!(cx.target_filenames(&unit)).iter() {
                    try!(rm_rf(&layout.dest().join(&filename)));
                    try!(rm_rf(&layout.deps().join(&filename)));
                }
            }
        }
    }

    Ok(())
}

fn rm_rf(path: &Path) -> CargoResult<()> {
    let m = fs::metadata(path);
    if m.as_ref().map(|s| s.is_dir()).unwrap_or(false) {
        try!(fs::remove_dir_all(path).chain_error(|| {
            human("could not remove build directory")
        }));
    } else if m.is_ok() {
        try!(fs::remove_file(path).chain_error(|| {
            human("failed to remove build artifact")
        }));
    }
    Ok(())
}