Auto merge of #14504 - eopb:push-xknxowwslrpt, r=epage

Include public/private dependency status in `cargo metadata`

fixes #14502

> [!TIP]
> This change can be reviewed commit-by-commit.
> Descriptions on individual commits are available to justify some decisions

`@rustbot` label Command-metadata Z-public-dependency
This commit is contained in:
bors 2024-09-09 16:22:26 +00:00
commit b958d79acd
10 changed files with 292 additions and 45 deletions

View File

@ -15,6 +15,9 @@ Deprecated, use `<cyan,bold>cargo metadata --no-deps</>` instead.\
pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
let ws = args.workspace(gctx)?; let ws = args.workspace(gctx)?;
gctx.shell().print_json(&ws.current()?.serialized())?; gctx.shell().print_json(
&ws.current()?
.serialized(gctx.cli_unstable(), ws.unstable_features()),
)?;
Ok(()) Ok(())
} }

View File

@ -9,7 +9,7 @@ use std::sync::Arc;
use tracing::trace; use tracing::trace;
use crate::core::compiler::{CompileKind, CompileTarget}; use crate::core::compiler::{CompileKind, CompileTarget};
use crate::core::{PackageId, SourceId, Summary}; use crate::core::{CliUnstable, Feature, Features, PackageId, SourceId, Summary};
use crate::util::errors::CargoResult; use crate::util::errors::CargoResult;
use crate::util::interning::InternedString; use crate::util::interning::InternedString;
use crate::util::OptVersionReq; use crate::util::OptVersionReq;
@ -52,50 +52,32 @@ struct Inner {
} }
#[derive(Serialize)] #[derive(Serialize)]
struct SerializedDependency<'a> { pub struct SerializedDependency {
name: &'a str, name: InternedString,
source: SourceId, source: SourceId,
req: String, req: String,
kind: DepKind, kind: DepKind,
rename: Option<&'a str>, rename: Option<InternedString>,
optional: bool, optional: bool,
uses_default_features: bool, uses_default_features: bool,
features: &'a [InternedString], features: Vec<InternedString>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
artifact: Option<&'a Artifact>, artifact: Option<Artifact>,
target: Option<&'a Platform>, target: Option<Platform>,
/// The registry URL this dependency is from. /// The registry URL this dependency is from.
/// If None, then it comes from the default registry (crates.io). /// If None, then it comes from the default registry (crates.io).
registry: Option<&'a str>, registry: Option<String>,
/// The file system path for a local path dependency. /// The file system path for a local path dependency.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
path: Option<PathBuf>, path: Option<PathBuf>,
}
impl ser::Serialize for Dependency { /// `public` flag is unset if `-Zpublic-dependency` is not enabled
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error> ///
where /// Once that feature is stabilized, `public` will not need to be `Option`
S: ser::Serializer, #[serde(skip_serializing_if = "Option::is_none")]
{ public: Option<bool>,
let registry_id = self.registry_id();
SerializedDependency {
name: &*self.package_name(),
source: self.source_id(),
req: self.version_req().to_string(),
kind: self.kind(),
optional: self.is_optional(),
uses_default_features: self.uses_default_features(),
features: self.features(),
target: self.platform(),
rename: self.explicit_name_in_toml().map(|s| s.as_str()),
registry: registry_id.as_ref().map(|sid| sid.url().as_str()),
path: self.source_id().local_path(),
artifact: self.artifact(),
}
.serialize(s)
}
} }
#[derive(PartialEq, Eq, Hash, Ord, PartialOrd, Clone, Debug, Copy)] #[derive(PartialEq, Eq, Hash, Ord, PartialOrd, Clone, Debug, Copy)]
@ -182,6 +164,34 @@ impl Dependency {
} }
} }
pub fn serialized(
&self,
unstable_flags: &CliUnstable,
features: &Features,
) -> SerializedDependency {
SerializedDependency {
name: self.package_name(),
source: self.source_id(),
req: self.version_req().to_string(),
kind: self.kind(),
optional: self.is_optional(),
uses_default_features: self.uses_default_features(),
features: self.features().to_vec(),
target: self.inner.platform.clone(),
rename: self.explicit_name_in_toml(),
registry: self.registry_id().as_ref().map(|sid| sid.url().to_string()),
path: self.source_id().local_path(),
artifact: self.inner.artifact.clone(),
public: if unstable_flags.public_dependency
|| features.is_enabled(Feature::public_dependency())
{
Some(self.inner.public)
} else {
None
},
}
}
pub fn version_req(&self) -> &OptVersionReq { pub fn version_req(&self) -> &OptVersionReq {
&self.inner.req &self.inner.req
} }

View File

@ -1,4 +1,4 @@
pub use self::dependency::Dependency; pub use self::dependency::{Dependency, SerializedDependency};
pub use self::features::{CliUnstable, Edition, Feature, Features}; pub use self::features::{CliUnstable, Edition, Feature, Features};
pub use self::manifest::{EitherManifest, VirtualManifest}; pub use self::manifest::{EitherManifest, VirtualManifest};
pub use self::manifest::{Manifest, Target, TargetKind}; pub use self::manifest::{Manifest, Target, TargetKind};

View File

@ -22,7 +22,10 @@ use crate::core::compiler::{CompileKind, RustcTargetData};
use crate::core::dependency::DepKind; use crate::core::dependency::DepKind;
use crate::core::resolver::features::ForceAllTargets; use crate::core::resolver::features::ForceAllTargets;
use crate::core::resolver::{HasDevUnits, Resolve}; use crate::core::resolver::{HasDevUnits, Resolve};
use crate::core::{Dependency, Manifest, PackageId, PackageIdSpec, SourceId, Target}; use crate::core::{
CliUnstable, Dependency, Features, Manifest, PackageId, PackageIdSpec, SerializedDependency,
SourceId, Target,
};
use crate::core::{Summary, Workspace}; use crate::core::{Summary, Workspace};
use crate::sources::source::{MaybePackage, SourceMap}; use crate::sources::source::{MaybePackage, SourceMap};
use crate::util::cache_lock::{CacheLock, CacheLockMode}; use crate::util::cache_lock::{CacheLock, CacheLockMode};
@ -73,7 +76,7 @@ pub struct SerializedPackage {
license_file: Option<String>, license_file: Option<String>,
description: Option<String>, description: Option<String>,
source: SourceId, source: SourceId,
dependencies: Vec<Dependency>, dependencies: Vec<SerializedDependency>,
targets: Vec<Target>, targets: Vec<Target>,
features: BTreeMap<InternedString, Vec<InternedString>>, features: BTreeMap<InternedString, Vec<InternedString>>,
manifest_path: PathBuf, manifest_path: PathBuf,
@ -188,7 +191,11 @@ impl Package {
self.targets().iter().any(|t| t.is_example() || t.is_bin()) self.targets().iter().any(|t| t.is_example() || t.is_bin())
} }
pub fn serialized(&self) -> SerializedPackage { pub fn serialized(
&self,
unstable_flags: &CliUnstable,
cargo_features: &Features,
) -> SerializedPackage {
let summary = self.manifest().summary(); let summary = self.manifest().summary();
let package_id = summary.package_id(); let package_id = summary.package_id();
let manmeta = self.manifest().metadata(); let manmeta = self.manifest().metadata();
@ -203,7 +210,7 @@ impl Package {
.cloned() .cloned()
.collect(); .collect();
// Convert Vec<FeatureValue> to Vec<InternedString> // Convert Vec<FeatureValue> to Vec<InternedString>
let features = summary let crate_features = summary
.features() .features()
.iter() .iter()
.map(|(k, v)| { .map(|(k, v)| {
@ -224,9 +231,13 @@ impl Package {
license_file: manmeta.license_file.clone(), license_file: manmeta.license_file.clone(),
description: manmeta.description.clone(), description: manmeta.description.clone(),
source: summary.source_id(), source: summary.source_id(),
dependencies: summary.dependencies().to_vec(), dependencies: summary
.dependencies()
.iter()
.map(|dep| dep.serialized(unstable_flags, cargo_features))
.collect(),
targets, targets,
features, features: crate_features,
manifest_path: self.manifest_path().to_path_buf(), manifest_path: self.manifest_path().to_path_buf(),
metadata: self.manifest().custom_metadata().cloned(), metadata: self.manifest().custom_metadata().cloned(),
authors: manmeta.authors.clone(), authors: manmeta.authors.clone(),

View File

@ -33,7 +33,10 @@ pub fn output_metadata(ws: &Workspace<'_>, opt: &OutputMetadataOptions) -> Cargo
); );
} }
let (packages, resolve) = if opt.no_deps { let (packages, resolve) = if opt.no_deps {
let packages = ws.members().map(|pkg| pkg.serialized()).collect(); let packages = ws
.members()
.map(|pkg| pkg.serialized(ws.gctx().cli_unstable(), ws.unstable_features()))
.collect();
(packages, None) (packages, None)
} else { } else {
let (packages, resolve) = build_resolve_graph(ws, opt)?; let (packages, resolve) = build_resolve_graph(ws, opt)?;
@ -178,7 +181,7 @@ fn build_resolve_graph(
let actual_packages = package_map let actual_packages = package_map
.into_iter() .into_iter()
.filter_map(|(pkg_id, pkg)| node_map.get(&pkg_id).map(|_| pkg)) .filter_map(|(pkg_id, pkg)| node_map.get(&pkg_id).map(|_| pkg))
.map(|pkg| pkg.serialized()) .map(|pkg| pkg.serialized(ws.gctx().cli_unstable(), ws.unstable_features()))
.collect(); .collect();
let mr = MetadataResolve { let mr = MetadataResolve {

View File

@ -123,7 +123,12 @@ The JSON output has the following format:
If not specified or null, the dependency is from the default If not specified or null, the dependency is from the default
registry (crates.io). registry (crates.io).
*/ */
"registry": null "registry": null,
/* (unstable) Boolean flag of whether or not this is a pulbic
dependency. This field is only present when
`-Zpublic-dependency` is enabled.
*/
"public": false
} }
], ],
/* Array of Cargo targets. */ /* Array of Cargo targets. */

View File

@ -119,7 +119,12 @@ OUTPUT FORMAT
If not specified or null, the dependency is from the default If not specified or null, the dependency is from the default
registry (crates.io). registry (crates.io).
*/ */
"registry": null "registry": null,
/* (unstable) Boolean flag of whether or not this is a pulbic
dependency. This field is only present when
`-Zpublic-dependency` is enabled.
*/
"public": false
} }
], ],
/* Array of Cargo targets. */ /* Array of Cargo targets. */

View File

@ -123,7 +123,12 @@ The JSON output has the following format:
If not specified or null, the dependency is from the default If not specified or null, the dependency is from the default
registry (crates.io). registry (crates.io).
*/ */
"registry": null "registry": null,
/* (unstable) Boolean flag of whether or not this is a pulbic
dependency. This field is only present when
`-Zpublic-dependency` is enabled.
*/
"public": false
} }
], ],
/* Array of Cargo targets. */ /* Array of Cargo targets. */

View File

@ -125,7 +125,12 @@ The JSON output has the following format:
If not specified or null, the dependency is from the default If not specified or null, the dependency is from the default
registry (crates.io). registry (crates.io).
*/ */
"registry": null "registry": null,
/* (unstable) Boolean flag of whether or not this is a pulbic
dependency. This field is only present when
`\-Zpublic\-dependency` is enabled.
*/
"public": false
} }
], ],
/* Array of Cargo targets. */ /* Array of Cargo targets. */

View File

@ -627,6 +627,206 @@ fn cargo_metadata_with_deps_and_version() {
.run(); .run();
} }
/// The `public` field should not show up in `cargo metadata` output if `-Zpublic-dependency`
/// is not enabled
#[cargo_test]
fn cargo_metadata_public_private_dependencies_disabled() {
let p = project()
.file("src/foo.rs", "")
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.5.0"
authors = []
license = "MIT"
description = "foo"
[[bin]]
name = "foo"
[dependencies]
bar = { version = "*", public = false }
foobar = { version = "*", public = true }
baz = "*"
"#,
)
.build();
Package::new("bar", "0.0.1").publish();
Package::new("foobar", "0.0.2").publish();
Package::new("baz", "0.0.3").publish();
p.cargo("metadata -q --format-version 1")
.with_stdout_data(
str![[r#"
{
"metadata": null,
"packages": [
{
"name": "bar",
"...": "{...}"
},
{
"name": "baz",
"...": "{...}"
},
{
"name": "foo",
"dependencies": [
{
"features": [],
"kind": null,
"name": "bar",
"optional": false,
"registry": null,
"rename": null,
"req": "*",
"source": "registry+https://github.com/rust-lang/crates.io-index",
"target": null,
"uses_default_features": true
},
{
"features": [],
"kind": null,
"name": "baz",
"optional": false,
"registry": null,
"rename": null,
"req": "*",
"source": "registry+https://github.com/rust-lang/crates.io-index",
"target": null,
"uses_default_features": true
},
{
"features": [],
"kind": null,
"name": "foobar",
"optional": false,
"registry": null,
"rename": null,
"req": "*",
"source": "registry+https://github.com/rust-lang/crates.io-index",
"target": null,
"uses_default_features": true
}
],
"...": "{...}"
},
{
"name": "foobar",
"...": "{...}"
}
],
"...": "{...}"
}
"#]]
.is_json(),
)
.run();
}
#[cargo_test]
fn cargo_metadata_public_private_dependencies_enabled() {
let p = project()
.file("src/foo.rs", "")
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.5.0"
authors = []
license = "MIT"
description = "foo"
[[bin]]
name = "foo"
[dependencies]
bar = { version = "*", public = false }
foobar = { version = "*", public = true }
baz = "*"
"#,
)
.build();
Package::new("bar", "0.0.1").publish();
Package::new("foobar", "0.0.2").publish();
Package::new("baz", "0.0.3").publish();
p.cargo("metadata -q --format-version 1 -Zpublic-dependency")
.masquerade_as_nightly_cargo(&["public-dependency"])
.with_stdout_data(
str![[r#"
{
"metadata": null,
"packages": [
{
"name": "bar",
"...": "{...}"
},
{
"name": "baz",
"...": "{...}"
},
{
"name": "foo",
"dependencies": [
{
"features": [],
"kind": null,
"name": "bar",
"optional": false,
"public": false,
"registry": null,
"rename": null,
"req": "*",
"source": "registry+https://github.com/rust-lang/crates.io-index",
"target": null,
"uses_default_features": true
},
{
"features": [],
"kind": null,
"name": "baz",
"optional": false,
"public": false,
"registry": null,
"rename": null,
"req": "*",
"source": "registry+https://github.com/rust-lang/crates.io-index",
"target": null,
"uses_default_features": true
},
{
"features": [],
"kind": null,
"name": "foobar",
"optional": false,
"public": true,
"registry": null,
"rename": null,
"req": "*",
"source": "registry+https://github.com/rust-lang/crates.io-index",
"target": null,
"uses_default_features": true
}
],
"...": "{...}"
},
{
"name": "foobar",
"...": "{...}"
}
],
"...": "{...}"
}
"#]]
.is_json(),
)
.run();
}
#[cargo_test] #[cargo_test]
fn example() { fn example() {
let p = project() let p = project()