Skip to content
19 changes: 19 additions & 0 deletions src/bin/cargo/commands/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,19 @@ The package name will be exposed as feature of your crate.")
The package will be removed from your features.")
.conflicts_with("dev")
.overrides_with("optional"),
flag("public", "Mark the dependency as public")
.conflicts_with("dev")
.conflicts_with("build")
.long_help("Mark the dependency as public

The dependency can be referenced in your library's public API."),
flag("no-public", "Mark the dependency as private")
.conflicts_with("dev")
.conflicts_with("build")
.overrides_with("public")
.long_help("Mark the dependency as private

While you can use the crate in your implementation, it cannot be referenced in your public API."),
clap::Arg::new("rename")
.long("rename")
.action(ArgAction::Set)
Expand Down Expand Up @@ -235,6 +248,7 @@ fn parse_dependencies(config: &Config, matches: &ArgMatches) -> CargoResult<Vec<
};
let default_features = default_features(matches);
let optional = optional(matches);
let public = public(matches);

let mut crates = matches
.get_many::<String>("crates")
Expand Down Expand Up @@ -325,6 +339,7 @@ fn parse_dependencies(config: &Config, matches: &ArgMatches) -> CargoResult<Vec<
features,
default_features,
optional,
public,
registry: registry.clone(),
path: path.map(String::from),
git: git.map(String::from),
Expand Down Expand Up @@ -353,6 +368,10 @@ fn optional(matches: &ArgMatches) -> Option<bool> {
resolve_bool_arg(matches.flag("optional"), matches.flag("no-optional"))
}

fn public(matches: &ArgMatches) -> Option<bool> {
resolve_bool_arg(matches.flag("public"), matches.flag("no-public"))
}

fn resolve_bool_arg(yes: bool, no: bool) -> Option<bool> {
match (yes, no) {
(true, false) => Some(true),
Expand Down
13 changes: 13 additions & 0 deletions src/cargo/ops/cargo_add/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,9 @@ pub struct DepOp {
/// Whether dependency is optional
pub optional: Option<bool>,

/// Whether dependency is public
pub public: Option<bool>,

/// Registry for looking up dependency version
pub registry: Option<String>,

Expand Down Expand Up @@ -758,6 +761,13 @@ fn populate_dependency(mut dependency: Dependency, arg: &DepOp) -> Dependency {
dependency.optional = None;
}
}
if let Some(value) = arg.public {
if value {
dependency.public = Some(true);
} else {
dependency.public = None;
}
}
if let Some(value) = arg.default_features {
if value {
dependency.default_features = None;
Expand Down Expand Up @@ -945,6 +955,9 @@ fn print_action_msg(shell: &mut Shell, dep: &DependencyUI, section: &[String]) -
if dep.optional().unwrap_or(false) {
write!(message, " optional")?;
}
if dep.public().unwrap_or(false) {
write!(message, " public")?;
}
let section = if section.len() == 1 {
section[0].clone()
} else {
Expand Down
39 changes: 32 additions & 7 deletions src/cargo/util/toml_mut/dependency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ pub struct Dependency {
/// Whether the dependency is opted-in with a feature flag.
pub optional: Option<bool>,

/// Whether the dependency is marked as public.
pub public: Option<bool>,

/// List of features to add (or None to keep features unchanged).
pub features: Option<IndexSet<String>>,
/// Whether default features are enabled.
Expand All @@ -48,6 +51,7 @@ impl Dependency {
Self {
name: name.into(),
optional: None,
public: None,
features: None,
default_features: None,
inherited_features: None,
Expand Down Expand Up @@ -163,6 +167,11 @@ impl Dependency {
self.optional
}

/// Get whether the dep is public.
pub fn public(&self) -> Option<bool> {
self.public
}

/// Get the SourceID for this dependency.
pub fn source_id(&self, config: &Config) -> CargoResult<MaybeWorkspace<SourceId>> {
match &self.source.as_ref() {
Expand Down Expand Up @@ -325,16 +334,18 @@ impl Dependency {
};

let optional = table.get("optional").and_then(|v| v.as_bool());
let public = table.get("public").and_then(|v| v.as_bool());

let dep = Self {
name,
rename,
source: Some(source),
registry,
default_features,
features,
optional,
public,
features,
default_features,
inherited_features: None,
source: Some(source),
registry,
rename,
};
Ok(dep)
} else {
Expand Down Expand Up @@ -366,6 +377,7 @@ impl Dependency {
crate_root.display()
);
let table: toml_edit::Item = match (
self.public.unwrap_or(false),
self.optional.unwrap_or(false),
self.features.as_ref(),
self.default_features.unwrap_or(true),
Expand All @@ -375,21 +387,22 @@ impl Dependency {
) {
// Extra short when version flag only
(
false,
false,
None,
true,
Some(Source::Registry(RegistrySource { version: v })),
None,
None,
) => toml_edit::value(v),
(false, None, true, Some(Source::Workspace(WorkspaceSource {})), None, None) => {
(false, false, None, true, Some(Source::Workspace(WorkspaceSource {})), None, None) => {
let mut table = toml_edit::InlineTable::default();
table.set_dotted(true);
table.insert("workspace", true.into());
toml_edit::value(toml_edit::Value::InlineTable(table))
}
// Other cases are represented as an inline table
(_, _, _, _, _, _) => {
(_, _, _, _, _, _, _) => {
let mut table = toml_edit::InlineTable::default();

match &self.source {
Expand Down Expand Up @@ -442,6 +455,9 @@ impl Dependency {
if let Some(v) = self.optional {
table.insert("optional", v.into());
}
if let Some(v) = self.public {
table.insert("public", v.into());
}

toml_edit::value(toml_edit::Value::InlineTable(table))
}
Expand Down Expand Up @@ -579,6 +595,15 @@ impl Dependency {
table.remove("optional");
}
}
match self.public {
Some(v) => {
table.set_dotted(false);
overwrite_value(table, "public", v);
}
None => {
table.remove("public");
}
}
} else {
unreachable!("Invalid dependency type: {}", item.type_name());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[workspace]
members = ["primary", "dependency"]

[workspace.dependencies]
foo = { version = "0.0.0", path = "./dependency"}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[package]
name = "foo"
version = "0.0.0"
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
cargo-features = ["public-dependency"]
[package]
name = "bar"
version = "0.0.0"
26 changes: 26 additions & 0 deletions tests/testsuite/cargo_add/detect_workspace_inherit_public/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use cargo_test_support::compare::assert_ui;
use cargo_test_support::prelude::*;
use cargo_test_support::Project;

use cargo_test_support::curr_dir;

#[cargo_test]
fn case() {
cargo_test_support::registry::init();

let project = Project::from_template(curr_dir!().join("in"));
let project_root = project.root();
let cwd = &project_root;

snapbox::cmd::Command::cargo_ui()
.arg("add")
.args(["foo", "-p", "bar", "--public"])
.masquerade_as_nightly_cargo(&["public-dependency"])
.current_dir(cwd)
.assert()
.success()
.stdout_matches_path(curr_dir!().join("stdout.log"))
.stderr_matches_path(curr_dir!().join("stderr.log"));

assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[workspace]
members = ["primary", "dependency"]

[workspace.dependencies]
foo = { version = "0.0.0", path = "./dependency"}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[package]
name = "foo"
version = "0.0.0"
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
cargo-features = ["public-dependency"]
[package]
name = "bar"
version = "0.0.0"

[dependencies]
foo = { workspace = true, public = true }
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Adding foo (workspace) to public dependencies.
Empty file.
11 changes: 11 additions & 0 deletions tests/testsuite/cargo_add/help/stdout.log
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ Options:

The package will be removed from your features.

--public
Mark the dependency as public

The dependency can be referenced in your library's public API.

--no-public
Mark the dependency as private

While you can use the crate in your implementation, it cannot be referenced in your public
API.

--rename <NAME>
Rename the dependency

Expand Down
7 changes: 7 additions & 0 deletions tests/testsuite/cargo_add/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ mod deprecated_section;
mod detect_workspace_inherit;
mod detect_workspace_inherit_features;
mod detect_workspace_inherit_optional;
mod detect_workspace_inherit_public;
mod dev;
mod dev_build_conflict;
mod dev_prefer_existing_version;
Expand Down Expand Up @@ -65,6 +66,7 @@ mod namever;
mod no_args;
mod no_default_features;
mod no_optional;
mod no_public;
mod offline_empty_cache;
mod optional;
mod overwrite_default_features;
Expand All @@ -81,11 +83,15 @@ mod overwrite_no_default_features;
mod overwrite_no_default_features_with_default_features;
mod overwrite_no_optional;
mod overwrite_no_optional_with_optional;
mod overwrite_no_public;
mod overwrite_no_public_with_public;
mod overwrite_optional;
mod overwrite_optional_with_no_optional;
mod overwrite_path_noop;
mod overwrite_path_with_version;
mod overwrite_preserves_inline_table;
mod overwrite_public;
mod overwrite_public_with_no_public;
mod overwrite_rename_with_no_rename;
mod overwrite_rename_with_rename;
mod overwrite_rename_with_rename_noop;
Expand All @@ -103,6 +109,7 @@ mod preserve_dep_std_table;
mod preserve_features_table;
mod preserve_sorted;
mod preserve_unsorted;
mod public;
mod quiet;
mod registry;
mod rename;
Expand Down
6 changes: 6 additions & 0 deletions tests/testsuite/cargo_add/no_public/in/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
cargo-features = ["public-dependency"]
[workspace]

[package]
name = "cargo-list-test-fixture"
version = "0.0.0"
Empty file.
26 changes: 26 additions & 0 deletions tests/testsuite/cargo_add/no_public/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use cargo_test_support::compare::assert_ui;
use cargo_test_support::prelude::*;
use cargo_test_support::Project;

use cargo_test_support::curr_dir;

#[cargo_test]
fn case() {
cargo_test_support::registry::init();
cargo_test_support::registry::Package::new("my-package", "0.1.0").publish();
let project = Project::from_template(curr_dir!().join("in"));
let project_root = project.root();
let cwd = &project_root;

snapbox::cmd::Command::cargo_ui()
.arg("add")
.arg_line("my-package --no-public")
.current_dir(cwd)
.masquerade_as_nightly_cargo(&["public-dependency"])
.assert()
.success()
.stdout_matches_path(curr_dir!().join("stdout.log"))
.stderr_matches_path(curr_dir!().join("stderr.log"));

assert_ui().subset_matches(curr_dir!().join("out"), &project_root);
}
9 changes: 9 additions & 0 deletions tests/testsuite/cargo_add/no_public/out/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
cargo-features = ["public-dependency"]
[workspace]

[package]
name = "cargo-list-test-fixture"
version = "0.0.0"

[dependencies]
my-package = "0.1.0"
2 changes: 2 additions & 0 deletions tests/testsuite/cargo_add/no_public/stderr.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Updating `dummy-registry` index
Adding my-package v0.1.0 to dependencies.
Empty file.
9 changes: 9 additions & 0 deletions tests/testsuite/cargo_add/overwrite_no_public/in/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
cargo-features = ["public-dependency"]
[workspace]

[package]
name = "cargo-list-test-fixture"
version = "0.0.0"

[dependencies]
my-package = "0.1.0"
Empty file.
Loading