Skip to content

Commit b2cceaf

Browse files
committed
Expose implicit features in cargo metadata.
1 parent bcfdf9f commit b2cceaf

File tree

4 files changed

+135
-135
lines changed

4 files changed

+135
-135
lines changed

src/bin/cargo/commands/read_manifest.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@ Deprecated, use `cargo metadata --no-deps` instead.\
1515

1616
pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
1717
let ws = args.workspace(config)?;
18-
config.shell().print_json(&ws.current()?);
18+
config.shell().print_json(&ws.current()?.serialized(config));
1919
Ok(())
2020
}

src/cargo/core/package.rs

Lines changed: 85 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ use curl::multi::{EasyHandle, Multi};
1515
use lazycell::LazyCell;
1616
use log::{debug, warn};
1717
use semver::Version;
18-
use serde::ser;
1918
use serde::Serialize;
2019

2120
use crate::core::compiler::{CompileKind, RustcTargetData};
@@ -77,94 +76,31 @@ impl PartialOrd for Package {
7776

7877
/// A Package in a form where `Serialize` can be derived.
7978
#[derive(Serialize)]
80-
struct SerializedPackage<'a> {
81-
name: &'a str,
82-
version: &'a Version,
79+
pub struct SerializedPackage {
80+
name: InternedString,
81+
version: Version,
8382
id: PackageId,
84-
license: Option<&'a str>,
85-
license_file: Option<&'a str>,
86-
description: Option<&'a str>,
83+
license: Option<String>,
84+
license_file: Option<String>,
85+
description: Option<String>,
8786
source: SourceId,
88-
dependencies: &'a [Dependency],
89-
targets: Vec<&'a Target>,
90-
features: &'a BTreeMap<InternedString, Vec<InternedString>>,
91-
manifest_path: &'a Path,
92-
metadata: Option<&'a toml::Value>,
93-
publish: Option<&'a Vec<String>>,
94-
authors: &'a [String],
95-
categories: &'a [String],
96-
keywords: &'a [String],
97-
readme: Option<&'a str>,
98-
repository: Option<&'a str>,
99-
homepage: Option<&'a str>,
100-
documentation: Option<&'a str>,
101-
edition: &'a str,
102-
links: Option<&'a str>,
87+
dependencies: Vec<Dependency>,
88+
targets: Vec<Target>,
89+
features: BTreeMap<InternedString, Vec<InternedString>>,
90+
manifest_path: PathBuf,
91+
metadata: Option<toml::Value>,
92+
publish: Option<Vec<String>>,
93+
authors: Vec<String>,
94+
categories: Vec<String>,
95+
keywords: Vec<String>,
96+
readme: Option<String>,
97+
repository: Option<String>,
98+
homepage: Option<String>,
99+
documentation: Option<String>,
100+
edition: String,
101+
links: Option<String>,
103102
#[serde(skip_serializing_if = "Option::is_none")]
104-
metabuild: Option<&'a Vec<String>>,
105-
}
106-
107-
impl ser::Serialize for Package {
108-
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
109-
where
110-
S: ser::Serializer,
111-
{
112-
let summary = self.manifest().summary();
113-
let package_id = summary.package_id();
114-
let manmeta = self.manifest().metadata();
115-
let license = manmeta.license.as_deref();
116-
let license_file = manmeta.license_file.as_deref();
117-
let description = manmeta.description.as_deref();
118-
let authors = manmeta.authors.as_ref();
119-
let categories = manmeta.categories.as_ref();
120-
let keywords = manmeta.keywords.as_ref();
121-
let readme = manmeta.readme.as_deref();
122-
let repository = manmeta.repository.as_deref();
123-
let homepage = manmeta.homepage.as_ref().map(String::as_ref);
124-
let documentation = manmeta.documentation.as_ref().map(String::as_ref);
125-
// Filter out metabuild targets. They are an internal implementation
126-
// detail that is probably not relevant externally. There's also not a
127-
// real path to show in `src_path`, and this avoids changing the format.
128-
let targets: Vec<&Target> = self
129-
.manifest()
130-
.targets()
131-
.iter()
132-
.filter(|t| t.src_path().is_path())
133-
.collect();
134-
let empty_feats = BTreeMap::new();
135-
let features = self
136-
.manifest()
137-
.original()
138-
.features()
139-
.unwrap_or(&empty_feats);
140-
141-
SerializedPackage {
142-
name: &*package_id.name(),
143-
version: package_id.version(),
144-
id: package_id,
145-
license,
146-
license_file,
147-
description,
148-
source: summary.source_id(),
149-
dependencies: summary.dependencies(),
150-
targets,
151-
features,
152-
manifest_path: self.manifest_path(),
153-
metadata: self.manifest().custom_metadata(),
154-
authors,
155-
categories,
156-
keywords,
157-
readme,
158-
repository,
159-
homepage,
160-
documentation,
161-
edition: &self.manifest().edition().to_string(),
162-
links: self.manifest().links(),
163-
metabuild: self.manifest().metabuild(),
164-
publish: self.publish().as_ref(),
165-
}
166-
.serialize(s)
167-
}
103+
metabuild: Option<Vec<String>>,
168104
}
169105

170106
impl Package {
@@ -261,6 +197,69 @@ impl Package {
261197
pub fn include_lockfile(&self) -> bool {
262198
self.targets().iter().any(|t| t.is_example() || t.is_bin())
263199
}
200+
201+
pub fn serialized(&self, config: &Config) -> SerializedPackage {
202+
let summary = self.manifest().summary();
203+
let package_id = summary.package_id();
204+
let manmeta = self.manifest().metadata();
205+
// Filter out metabuild targets. They are an internal implementation
206+
// detail that is probably not relevant externally. There's also not a
207+
// real path to show in `src_path`, and this avoids changing the format.
208+
let targets: Vec<Target> = self
209+
.manifest()
210+
.targets()
211+
.iter()
212+
.filter(|t| t.src_path().is_path())
213+
.cloned()
214+
.collect();
215+
let features = if config.cli_unstable().namespaced_features {
216+
// Convert Vec<FeatureValue> to Vec<InternedString>
217+
summary
218+
.features()
219+
.iter()
220+
.map(|(k, v)| {
221+
(
222+
*k,
223+
v.iter()
224+
.map(|fv| InternedString::new(&fv.to_string()))
225+
.collect(),
226+
)
227+
})
228+
.collect()
229+
} else {
230+
self.manifest()
231+
.original()
232+
.features()
233+
.cloned()
234+
.unwrap_or_default()
235+
};
236+
237+
SerializedPackage {
238+
name: package_id.name(),
239+
version: package_id.version().clone(),
240+
id: package_id,
241+
license: manmeta.license.clone(),
242+
license_file: manmeta.license_file.clone(),
243+
description: manmeta.description.clone(),
244+
source: summary.source_id(),
245+
dependencies: summary.dependencies().to_vec(),
246+
targets,
247+
features,
248+
manifest_path: self.manifest_path().to_path_buf(),
249+
metadata: self.manifest().custom_metadata().cloned(),
250+
authors: manmeta.authors.clone(),
251+
categories: manmeta.categories.clone(),
252+
keywords: manmeta.keywords.clone(),
253+
readme: manmeta.readme.clone(),
254+
repository: manmeta.repository.clone(),
255+
homepage: manmeta.homepage.clone(),
256+
documentation: manmeta.documentation.clone(),
257+
edition: self.manifest().edition().to_string(),
258+
links: self.manifest().links().map(|s| s.to_owned()),
259+
metabuild: self.manifest().metabuild().cloned(),
260+
publish: self.publish().as_ref().cloned(),
261+
}
262+
}
264263
}
265264

266265
impl fmt::Display for Package {

src/cargo/ops/cargo_output_metadata.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::core::compiler::{CompileKind, RustcTargetData};
22
use crate::core::dependency::DepKind;
3+
use crate::core::package::SerializedPackage;
34
use crate::core::resolver::{HasDevUnits, Resolve, ResolveOpts};
45
use crate::core::{Dependency, Package, PackageId, Workspace};
56
use crate::ops::{self, Packages};
@@ -32,8 +33,9 @@ pub fn output_metadata(ws: &Workspace<'_>, opt: &OutputMetadataOptions) -> Cargo
3233
VERSION
3334
);
3435
}
36+
let config = ws.config();
3537
let (packages, resolve) = if opt.no_deps {
36-
let packages = ws.members().cloned().collect();
38+
let packages = ws.members().map(|pkg| pkg.serialized(config)).collect();
3739
(packages, None)
3840
} else {
3941
let (packages, resolve) = build_resolve_graph(ws, opt)?;
@@ -56,7 +58,7 @@ pub fn output_metadata(ws: &Workspace<'_>, opt: &OutputMetadataOptions) -> Cargo
5658
/// See cargo-metadata.adoc for detailed documentation of the format.
5759
#[derive(Serialize)]
5860
pub struct ExportInfo {
59-
packages: Vec<Package>,
61+
packages: Vec<SerializedPackage>,
6062
workspace_members: Vec<PackageId>,
6163
resolve: Option<MetadataResolve>,
6264
target_directory: PathBuf,
@@ -105,7 +107,7 @@ impl From<&Dependency> for DepKindInfo {
105107
fn build_resolve_graph(
106108
ws: &Workspace<'_>,
107109
metadata_opts: &OutputMetadataOptions,
108-
) -> CargoResult<(Vec<Package>, MetadataResolve)> {
110+
) -> CargoResult<(Vec<SerializedPackage>, MetadataResolve)> {
109111
// TODO: Without --filter-platform, features are being resolved for `host` only.
110112
// How should this work?
111113
let requested_kinds =
@@ -153,9 +155,11 @@ fn build_resolve_graph(
153155
);
154156
}
155157
// Get a Vec of Packages.
158+
let config = ws.config();
156159
let actual_packages = package_map
157160
.into_iter()
158161
.filter_map(|(pkg_id, pkg)| node_map.get(&pkg_id).map(|_| pkg))
162+
.map(|pkg| pkg.serialized(config))
159163
.collect();
160164

161165
let mr = MetadataResolve {

tests/testsuite/features_namespaced.rs

Lines changed: 42 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -708,8 +708,8 @@ explicit `crate:` feature values are not allowed in required-features
708708
}
709709

710710
#[cargo_test]
711-
fn json_not_exposed() {
712-
// Checks that crate: is not exposed in JSON.
711+
fn json_exposed() {
712+
// Checks that the implicit crate: values are exposed in JSON.
713713
Package::new("bar", "1.0.0").publish();
714714
let p = project()
715715
.file(
@@ -726,51 +726,48 @@ fn json_not_exposed() {
726726
.file("src/lib.rs", "")
727727
.build();
728728

729-
// Check that "features" is empty (doesn't include the implicit features).
730-
let json = r#"
731-
{
732-
"packages": [
733-
{
734-
"name": "foo",
735-
"version": "0.1.0",
736-
"id": "foo 0.1.0 [..]",
737-
"license": null,
738-
"license_file": null,
739-
"description": null,
740-
"homepage": null,
741-
"documentation": null,
742-
"source": null,
743-
"dependencies": "{...}",
744-
"targets": "{...}",
745-
"features": {},
746-
"manifest_path": "[..]foo/Cargo.toml",
747-
"metadata": null,
748-
"publish": null,
749-
"authors": [],
750-
"categories": [],
751-
"keywords": [],
752-
"readme": null,
753-
"repository": null,
754-
"edition": "2015",
755-
"links": null
756-
}
757-
],
758-
"workspace_members": "{...}",
759-
"resolve": null,
760-
"target_directory": "[..]foo/target",
761-
"version": 1,
762-
"workspace_root": "[..]foo",
763-
"metadata": null
764-
}
765-
"#;
766-
767-
p.cargo("metadata --no-deps").with_json(json).run();
768-
769-
// And namespaced-features shouldn't be any different.
770-
// NOTE: I don't actually know if this is ideal behavior.
771729
p.cargo("metadata -Z namespaced-features --no-deps")
772730
.masquerade_as_nightly_cargo()
773-
.with_json(json)
731+
.with_json(
732+
r#"
733+
{
734+
"packages": [
735+
{
736+
"name": "foo",
737+
"version": "0.1.0",
738+
"id": "foo 0.1.0 [..]",
739+
"license": null,
740+
"license_file": null,
741+
"description": null,
742+
"homepage": null,
743+
"documentation": null,
744+
"source": null,
745+
"dependencies": "{...}",
746+
"targets": "{...}",
747+
"features": {
748+
"bar": ["crate:bar"]
749+
},
750+
"manifest_path": "[..]foo/Cargo.toml",
751+
"metadata": null,
752+
"publish": null,
753+
"authors": [],
754+
"categories": [],
755+
"keywords": [],
756+
"readme": null,
757+
"repository": null,
758+
"edition": "2015",
759+
"links": null
760+
}
761+
],
762+
"workspace_members": "{...}",
763+
"resolve": null,
764+
"target_directory": "[..]foo/target",
765+
"version": 1,
766+
"workspace_root": "[..]foo",
767+
"metadata": null
768+
}
769+
"#,
770+
)
774771
.run();
775772
}
776773

0 commit comments

Comments
 (0)