Skip to main content

cargo/ops/
cargo_clean.rs

1use crate::core::InternedString;
2use std::collections::HashMap;
3use std::fs;
4use std::path::Path;
5
6use crate::core::compiler::unit_dependencies;
7use crate::core::compiler::{BuildConfig, BuildContext, CompileKind, CompileMode, Context};
8use crate::core::compiler::{RustcTargetData, UnitInterner};
9use crate::core::profiles::{Profiles, UnitFor};
10use crate::core::resolver::features::HasDevUnits;
11use crate::core::resolver::ResolveOpts;
12use crate::core::{PackageIdSpec, Workspace};
13use crate::ops;
14use crate::ops::resolve::WorkspaceResolve;
15use crate::util::errors::{CargoResult, CargoResultExt};
16use crate::util::paths;
17use crate::util::Config;
18
19pub struct CleanOptions<'a> {
20    pub config: &'a Config,
21    /// A list of packages to clean. If empty, everything is cleaned.
22    pub spec: Vec<String>,
23    /// The target arch triple to clean, or None for the host arch
24    pub target: Option<String>,
25    /// Whether to clean the release directory
26    pub profile_specified: bool,
27    /// Whether to clean the directory of a certain build profile
28    pub requested_profile: InternedString,
29    /// Whether to just clean the doc directory
30    pub doc: bool,
31}
32
33/// Cleans the package's build artifacts.
34pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> {
35    let mut target_dir = ws.target_dir();
36    let config = ws.config();
37
38    // If the doc option is set, we just want to delete the doc directory.
39    if opts.doc {
40        target_dir = target_dir.join("doc");
41        return rm_rf(&target_dir.into_path_unlocked(), config);
42    }
43
44    let profiles = Profiles::new(ws.profiles(), config, opts.requested_profile, ws.features())?;
45
46    if opts.profile_specified {
47        // After parsing profiles we know the dir-name of the profile, if a profile
48        // was passed from the command line. If so, delete only the directory of
49        // that profile.
50        let dir_name = profiles.get_dir_name();
51        target_dir = target_dir.join(dir_name);
52    }
53
54    // If we have a spec, then we need to delete some packages, otherwise, just
55    // remove the whole target directory and be done with it!
56    //
57    // Note that we don't bother grabbing a lock here as we're just going to
58    // blow it all away anyway.
59    if opts.spec.is_empty() {
60        return rm_rf(&target_dir.into_path_unlocked(), config);
61    }
62    let mut build_config = BuildConfig::new(config, Some(1), &opts.target, CompileMode::Build)?;
63    build_config.requested_profile = opts.requested_profile;
64    let target_data = RustcTargetData::new(ws, build_config.requested_kind)?;
65    let resolve_opts = ResolveOpts::everything();
66    let specs = opts
67        .spec
68        .iter()
69        .map(|spec| PackageIdSpec::parse(spec))
70        .collect::<CargoResult<Vec<_>>>()?;
71    let ws_resolve = ops::resolve_ws_with_opts(
72        ws,
73        &target_data,
74        build_config.requested_kind,
75        &resolve_opts,
76        &specs,
77        HasDevUnits::Yes,
78    )?;
79    let WorkspaceResolve {
80        pkg_set,
81        targeted_resolve: resolve,
82        resolved_features: features,
83        ..
84    } = ws_resolve;
85
86    let interner = UnitInterner::new();
87    let bcx = BuildContext::new(
88        ws,
89        &pkg_set,
90        opts.config,
91        &build_config,
92        profiles,
93        &interner,
94        HashMap::new(),
95        target_data,
96    )?;
97    let mut units = Vec::new();
98
99    for spec in opts.spec.iter() {
100        // Translate the spec to a Package
101        let pkgid = resolve.query(spec)?;
102        let pkg = pkg_set.get_one(pkgid)?;
103
104        // Generate all relevant `Unit` targets for this package
105        for target in pkg.targets() {
106            for kind in [CompileKind::Host, build_config.requested_kind].iter() {
107                for mode in CompileMode::all_modes() {
108                    for unit_for in UnitFor::all_values() {
109                        let profile = if mode.is_run_custom_build() {
110                            bcx.profiles
111                                .get_profile_run_custom_build(&bcx.profiles.get_profile(
112                                    pkg.package_id(),
113                                    ws.is_member(pkg),
114                                    *unit_for,
115                                    CompileMode::Build,
116                                ))
117                        } else {
118                            bcx.profiles.get_profile(
119                                pkg.package_id(),
120                                ws.is_member(pkg),
121                                *unit_for,
122                                *mode,
123                            )
124                        };
125                        // Use unverified here since this is being more
126                        // exhaustive than what is actually needed.
127                        let features_for = unit_for.map_to_features_for();
128                        let features =
129                            features.activated_features_unverified(pkg.package_id(), features_for);
130                        units.push(bcx.units.intern(
131                            pkg, target, profile, *kind, *mode, features, /*is_std*/ false,
132                        ));
133                    }
134                }
135            }
136        }
137    }
138
139    let unit_dependencies =
140        unit_dependencies::build_unit_dependencies(&bcx, &resolve, &features, None, &units, &[])?;
141    let mut cx = Context::new(config, &bcx, unit_dependencies, build_config.requested_kind)?;
142    cx.prepare_units(None, &units)?;
143
144    for unit in units.iter() {
145        if unit.mode.is_doc() || unit.mode.is_doc_test() {
146            // Cleaning individual rustdoc crates is currently not supported.
147            // For example, the search index would need to be rebuilt to fully
148            // remove it (otherwise you're left with lots of broken links).
149            // Doc tests produce no output.
150            continue;
151        }
152        rm_rf(&cx.files().fingerprint_dir(unit), config)?;
153        if unit.target.is_custom_build() {
154            if unit.mode.is_run_custom_build() {
155                rm_rf(&cx.files().build_script_out_dir(unit), config)?;
156            } else {
157                rm_rf(&cx.files().build_script_dir(unit), config)?;
158            }
159            continue;
160        }
161
162        for output in cx.outputs(unit)?.iter() {
163            rm_rf(&output.path, config)?;
164            if let Some(ref dst) = output.hardlink {
165                rm_rf(dst, config)?;
166            }
167        }
168    }
169
170    Ok(())
171}
172
173fn rm_rf(path: &Path, config: &Config) -> CargoResult<()> {
174    let m = fs::metadata(path);
175    if m.as_ref().map(|s| s.is_dir()).unwrap_or(false) {
176        config
177            .shell()
178            .verbose(|shell| shell.status("Removing", path.display()))?;
179        paths::remove_dir_all(path)
180            .chain_err(|| anyhow::format_err!("could not remove build directory"))?;
181    } else if m.is_ok() {
182        config
183            .shell()
184            .verbose(|shell| shell.status("Removing", path.display()))?;
185        paths::remove_file(path)
186            .chain_err(|| anyhow::format_err!("failed to remove build artifact"))?;
187    }
188    Ok(())
189}