]> git.proxmox.com Git - rustc.git/blob - src/tools/cargo/src/bin/cargo/commands/add.rs
New upstream version 1.70.0+dfsg2
[rustc.git] / src / tools / cargo / src / bin / cargo / commands / add.rs
1 use cargo::sources::CRATES_IO_REGISTRY;
2 use cargo::util::print_available_packages;
3 use indexmap::IndexMap;
4 use indexmap::IndexSet;
5
6 use cargo::core::dependency::DepKind;
7 use cargo::core::FeatureValue;
8 use cargo::ops::cargo_add::add;
9 use cargo::ops::cargo_add::AddOptions;
10 use cargo::ops::cargo_add::DepOp;
11 use cargo::ops::resolve_ws;
12 use cargo::util::command_prelude::*;
13 use cargo::util::interning::InternedString;
14 use cargo::util::toml_mut::manifest::DepTable;
15 use cargo::CargoResult;
16
17 pub fn cli() -> Command {
18 clap::Command::new("add")
19 .about("Add dependencies to a Cargo.toml manifest file")
20 .override_usage(
21 "\
22 cargo add [OPTIONS] <DEP>[@<VERSION>] ...
23 cargo add [OPTIONS] --path <PATH> ...
24 cargo add [OPTIONS] --git <URL> ..."
25 )
26 .after_help("Run `cargo help add` for more detailed information.\n")
27 .group(clap::ArgGroup::new("selected").multiple(true).required(true))
28 .args([
29 clap::Arg::new("crates")
30 .value_name("DEP_ID")
31 .num_args(0..)
32 .help("Reference to a package to add as a dependency")
33 .long_help(
34 "Reference to a package to add as a dependency
35
36 You can reference a package by:
37 - `<name>`, like `cargo add serde` (latest version will be used)
38 - `<name>@<version-req>`, like `cargo add serde@1` or `cargo add serde@=1.0.38`"
39 )
40 .group("selected"),
41 flag("no-default-features",
42 "Disable the default features"),
43 flag("default-features",
44 "Re-enable the default features")
45 .overrides_with("no-default-features"),
46 clap::Arg::new("features")
47 .short('F')
48 .long("features")
49 .value_name("FEATURES")
50 .action(ArgAction::Append)
51 .help("Space or comma separated list of features to activate"),
52 flag("optional",
53 "Mark the dependency as optional")
54 .long_help("Mark the dependency as optional
55
56 The package name will be exposed as feature of your crate.")
57 .conflicts_with("dev"),
58 flag("no-optional",
59 "Mark the dependency as required")
60 .long_help("Mark the dependency as required
61
62 The package will be removed from your features.")
63 .conflicts_with("dev")
64 .overrides_with("optional"),
65 clap::Arg::new("rename")
66 .long("rename")
67 .action(ArgAction::Set)
68 .value_name("NAME")
69 .help("Rename the dependency")
70 .long_help("Rename the dependency
71
72 Example uses:
73 - Depending on multiple versions of a crate
74 - Depend on crates with the same name from different registries"),
75 ])
76 .arg_manifest_path()
77 .arg_package("Package to modify")
78 .arg_quiet()
79 .arg_dry_run("Don't actually write the manifest")
80 .next_help_heading("Source")
81 .args([
82 clap::Arg::new("path")
83 .long("path")
84 .action(ArgAction::Set)
85 .value_name("PATH")
86 .help("Filesystem path to local crate to add")
87 .group("selected")
88 .conflicts_with("git"),
89 clap::Arg::new("git")
90 .long("git")
91 .action(ArgAction::Set)
92 .value_name("URI")
93 .help("Git repository location")
94 .long_help("Git repository location
95
96 Without any other information, cargo will use latest commit on the main branch.")
97 .group("selected"),
98 clap::Arg::new("branch")
99 .long("branch")
100 .action(ArgAction::Set)
101 .value_name("BRANCH")
102 .help("Git branch to download the crate from")
103 .requires("git")
104 .group("git-ref"),
105 clap::Arg::new("tag")
106 .long("tag")
107 .action(ArgAction::Set)
108 .value_name("TAG")
109 .help("Git tag to download the crate from")
110 .requires("git")
111 .group("git-ref"),
112 clap::Arg::new("rev")
113 .long("rev")
114 .action(ArgAction::Set)
115 .value_name("REV")
116 .help("Git reference to download the crate from")
117 .long_help("Git reference to download the crate from
118
119 This is the catch all, handling hashes to named references in remote repositories.")
120 .requires("git")
121 .group("git-ref"),
122 clap::Arg::new("registry")
123 .long("registry")
124 .action(ArgAction::Set)
125 .value_name("NAME")
126 .help("Package registry for this dependency"),
127 ])
128 .next_help_heading("Section")
129 .args([
130 flag("dev",
131 "Add as development dependency")
132 .long_help("Add as development dependency
133
134 Dev-dependencies are not used when compiling a package for building, but are used for compiling tests, examples, and benchmarks.
135
136 These dependencies are not propagated to other packages which depend on this package.")
137 .group("section"),
138 flag("build",
139 "Add as build dependency")
140 .long_help("Add as build dependency
141
142 Build-dependencies are the only dependencies available for use by build scripts (`build.rs` files).")
143 .group("section"),
144 clap::Arg::new("target")
145 .long("target")
146 .action(ArgAction::Set)
147 .value_name("TARGET")
148 .value_parser(clap::builder::NonEmptyStringValueParser::new())
149 .help("Add as dependency to the given target platform")
150 ])
151 }
152
153 pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
154 let dry_run = args.dry_run();
155 let section = parse_section(args);
156
157 let ws = args.workspace(config)?;
158
159 if args.is_present_with_zero_values("package") {
160 print_available_packages(&ws)?;
161 }
162
163 let packages = args.packages_from_flags()?;
164 let packages = packages.get_packages(&ws)?;
165 let spec = match packages.len() {
166 0 => {
167 return Err(CliError::new(
168 anyhow::format_err!(
169 "no packages selected to modify. Please specify one with `-p <PKGID>`"
170 ),
171 101,
172 ));
173 }
174 1 => packages[0],
175 _ => {
176 let names = packages.iter().map(|p| p.name()).collect::<Vec<_>>();
177 return Err(CliError::new(
178 anyhow::format_err!(
179 "`cargo add` could not determine which package to modify. \
180 Use the `--package` option to specify a package. \n\
181 available packages: {}",
182 names.join(", ")
183 ),
184 101,
185 ));
186 }
187 };
188
189 let dependencies = parse_dependencies(config, args)?;
190
191 let options = AddOptions {
192 config,
193 spec,
194 dependencies,
195 section,
196 dry_run,
197 };
198 add(&ws, &options)?;
199
200 if !dry_run {
201 // Reload the workspace since we've changed dependencies
202 let ws = args.workspace(config)?;
203 resolve_ws(&ws)?;
204 }
205
206 Ok(())
207 }
208
209 fn parse_dependencies(config: &Config, matches: &ArgMatches) -> CargoResult<Vec<DepOp>> {
210 let path = matches.get_one::<String>("path");
211 let git = matches.get_one::<String>("git");
212 let branch = matches.get_one::<String>("branch");
213 let rev = matches.get_one::<String>("rev");
214 let tag = matches.get_one::<String>("tag");
215 let rename = matches.get_one::<String>("rename");
216 let registry = match matches.registry(config)? {
217 Some(reg) if reg == CRATES_IO_REGISTRY => None,
218 reg => reg,
219 };
220 let default_features = default_features(matches);
221 let optional = optional(matches);
222
223 let mut crates = matches
224 .get_many::<String>("crates")
225 .into_iter()
226 .flatten()
227 .map(|c| (Some(c.clone()), None))
228 .collect::<IndexMap<_, _>>();
229 let mut infer_crate_name = false;
230 if crates.is_empty() {
231 if path.is_some() || git.is_some() {
232 crates.insert(None, None);
233 infer_crate_name = true;
234 } else {
235 unreachable!("clap should ensure we have some source selected");
236 }
237 }
238 for feature in matches
239 .get_many::<String>("features")
240 .into_iter()
241 .flatten()
242 .map(String::as_str)
243 .flat_map(parse_feature)
244 {
245 let parsed_value = FeatureValue::new(InternedString::new(feature));
246 match parsed_value {
247 FeatureValue::Feature(_) => {
248 if 1 < crates.len() {
249 let candidates = crates
250 .keys()
251 .map(|c| {
252 format!(
253 "`{}/{}`",
254 c.as_deref().expect("only none when there is 1"),
255 feature
256 )
257 })
258 .collect::<Vec<_>>();
259 anyhow::bail!("feature `{feature}` must be qualified by the dependency it's being activated for, like {}", candidates.join(", "));
260 }
261 crates
262 .first_mut()
263 .expect("always at least one crate")
264 .1
265 .get_or_insert_with(IndexSet::new)
266 .insert(feature.to_owned());
267 }
268 FeatureValue::Dep { .. } => {
269 anyhow::bail!("feature `{feature}` is not allowed to use explicit `dep:` syntax",)
270 }
271 FeatureValue::DepFeature {
272 dep_name,
273 dep_feature,
274 ..
275 } => {
276 if infer_crate_name {
277 anyhow::bail!("`{feature}` is unsupported when inferring the crate name, use `{dep_feature}`");
278 }
279 if dep_feature.contains('/') {
280 anyhow::bail!("multiple slashes in feature `{feature}` is not allowed");
281 }
282 crates.get_mut(&Some(dep_name.as_str().to_owned())).ok_or_else(|| {
283 anyhow::format_err!("feature `{dep_feature}` activated for crate `{dep_name}` but the crate wasn't specified")
284 })?
285 .get_or_insert_with(IndexSet::new)
286 .insert(dep_feature.as_str().to_owned());
287 }
288 }
289 }
290
291 let mut deps: Vec<DepOp> = Vec::new();
292 for (crate_spec, features) in crates {
293 let dep = DepOp {
294 crate_spec,
295 rename: rename.map(String::from),
296 features,
297 default_features,
298 optional,
299 registry: registry.clone(),
300 path: path.map(String::from),
301 git: git.map(String::from),
302 branch: branch.map(String::from),
303 rev: rev.map(String::from),
304 tag: tag.map(String::from),
305 };
306 deps.push(dep);
307 }
308
309 if deps.len() > 1 && rename.is_some() {
310 anyhow::bail!("cannot specify multiple crates with `--rename`");
311 }
312
313 Ok(deps)
314 }
315
316 fn default_features(matches: &ArgMatches) -> Option<bool> {
317 resolve_bool_arg(
318 matches.flag("default-features"),
319 matches.flag("no-default-features"),
320 )
321 }
322
323 fn optional(matches: &ArgMatches) -> Option<bool> {
324 resolve_bool_arg(matches.flag("optional"), matches.flag("no-optional"))
325 }
326
327 fn resolve_bool_arg(yes: bool, no: bool) -> Option<bool> {
328 match (yes, no) {
329 (true, false) => Some(true),
330 (false, true) => Some(false),
331 (false, false) => None,
332 (_, _) => unreachable!("clap should make this impossible"),
333 }
334 }
335
336 fn parse_section(matches: &ArgMatches) -> DepTable {
337 let kind = if matches.flag("dev") {
338 DepKind::Development
339 } else if matches.flag("build") {
340 DepKind::Build
341 } else {
342 DepKind::Normal
343 };
344
345 let mut table = DepTable::new().set_kind(kind);
346
347 if let Some(target) = matches.get_one::<String>("target") {
348 assert!(!target.is_empty(), "Target specification may not be empty");
349 table = table.set_target(target);
350 }
351
352 table
353 }
354
355 /// Split feature flag list
356 fn parse_feature(feature: &str) -> impl Iterator<Item = &str> {
357 // Not re-using `CliFeatures` because it uses a BTreeSet and loses user's ordering
358 feature
359 .split_whitespace()
360 .flat_map(|s| s.split(','))
361 .filter(|s| !s.is_empty())
362 }