diff --git a/crates/cargo-test-support/src/publish.rs b/crates/cargo-test-support/src/publish.rs index b538615b7dd..11ba31dc5d2 100644 --- a/crates/cargo-test-support/src/publish.rs +++ b/crates/cargo-test-support/src/publish.rs @@ -229,6 +229,7 @@ pub(crate) fn create_index_line( links: Option, rust_version: Option<&str>, v: Option, + proc_macro: Option, ) -> String { // This emulates what crates.io does to retain backwards compatibility. let (features, features2) = split_index_features(features.clone()); @@ -240,6 +241,7 @@ pub(crate) fn create_index_line( "features": features, "yanked": yanked, "links": links, + "proc_macro": proc_macro, }); if let Some(f2) = &features2 { json["features2"] = serde_json::json!(f2); diff --git a/crates/cargo-test-support/src/registry.rs b/crates/cargo-test-support/src/registry.rs index e4f83fbb769..e534dd0d3de 100644 --- a/crates/cargo-test-support/src/registry.rs +++ b/crates/cargo-test-support/src/registry.rs @@ -1243,6 +1243,7 @@ fn save_new_crate( new_crate.links, new_crate.rust_version.as_deref(), None, + None, ); write_to_index(registry_path, &new_crate.name, line, false); @@ -1527,6 +1528,12 @@ impl Package { } else { serde_json::json!(self.name) }; + // simulate alternative registries not having proc_macro in the index + let proc_macro = if self.alternative { + None + } else { + Some(self.proc_macro) + }; create_index_line( name, &self.vers, @@ -1537,6 +1544,7 @@ impl Package { self.links.clone(), self.rust_version.as_deref(), self.v, + proc_macro, ) }; diff --git a/crates/cargo-util-schemas/index.schema.json b/crates/cargo-util-schemas/index.schema.json index 6435e3ab6e5..c62cf615c81 100644 --- a/crates/cargo-util-schemas/index.schema.json +++ b/crates/cargo-util-schemas/index.schema.json @@ -76,6 +76,12 @@ ], "format": "uint32", "minimum": 0 + }, + "proc_macro": { + "type": [ + "boolean", + "null" + ] } }, "required": [ diff --git a/crates/cargo-util-schemas/src/index.rs b/crates/cargo-util-schemas/src/index.rs index 1e5fb0938da..8c3bd8e62ff 100644 --- a/crates/cargo-util-schemas/src/index.rs +++ b/crates/cargo-util-schemas/src/index.rs @@ -69,6 +69,7 @@ pub struct IndexPackage<'a> { /// workaround is to downgrade any packages that are incompatible with the /// `--precise` flag of `cargo update`. pub v: Option, + pub proc_macro: Option, } /// A dependency as encoded in the [`IndexPackage`] index JSON. diff --git a/crates/crates-io/lib.rs b/crates/crates-io/lib.rs index b1dbd59c485..2bc65ecdd68 100644 --- a/crates/crates-io/lib.rs +++ b/crates/crates-io/lib.rs @@ -63,6 +63,7 @@ pub struct NewCrate { pub badges: BTreeMap>, pub links: Option, pub rust_version: Option, + pub proc_macro: Option, } #[derive(Serialize, Deserialize)] diff --git a/crates/resolver-tests/src/helpers.rs b/crates/resolver-tests/src/helpers.rs index 2706c4d4732..908eb5d0cbd 100644 --- a/crates/resolver-tests/src/helpers.rs +++ b/crates/resolver-tests/src/helpers.rs @@ -119,7 +119,7 @@ pub fn pkg_dep(name: T, dep: Vec) -> Summary { } else { None }; - Summary::new(name.to_pkgid(), dep, &BTreeMap::new(), link, None).unwrap() + Summary::new(name.to_pkgid(), dep, &BTreeMap::new(), link, None, None).unwrap() } pub fn pkg_dep_with( @@ -137,11 +137,19 @@ pub fn pkg_dep_with( .into_iter() .map(|&(name, values)| (name.into(), values.into_iter().map(|&v| v.into()).collect())) .collect(); - Summary::new(name.to_pkgid(), dep, &features, link, None).unwrap() + Summary::new(name.to_pkgid(), dep, &features, link, None, None).unwrap() } pub fn pkg_dep_link(name: T, link: &str, dep: Vec) -> Summary { - Summary::new(name.to_pkgid(), dep, &BTreeMap::new(), Some(link), None).unwrap() + Summary::new( + name.to_pkgid(), + dep, + &BTreeMap::new(), + Some(link), + None, + None, + ) + .unwrap() } pub fn pkg_id(name: &str) -> PackageId { @@ -177,6 +185,7 @@ pub fn pkg_loc(name: &str, loc: &str) -> Summary { &BTreeMap::new(), link, None, + None, ) .unwrap() } @@ -185,7 +194,15 @@ pub fn remove_dep(sum: &Summary, ind: usize) -> Summary { let mut deps = sum.dependencies().to_vec(); deps.remove(ind); // note: more things will need to be copied over in the future, but it works for now. - Summary::new(sum.package_id(), deps, &BTreeMap::new(), sum.links(), None).unwrap() + Summary::new( + sum.package_id(), + deps, + &BTreeMap::new(), + sum.links(), + None, + None, + ) + .unwrap() } pub fn dep(name: &str) -> Dependency { diff --git a/crates/resolver-tests/src/lib.rs b/crates/resolver-tests/src/lib.rs index d332e3962fd..4bda6d3d38e 100644 --- a/crates/resolver-tests/src/lib.rs +++ b/crates/resolver-tests/src/lib.rs @@ -190,8 +190,15 @@ pub fn resolve_with_global_context_raw( used: HashSet::new(), }; - let root_summary = - Summary::new(root_pkg_id, deps, &BTreeMap::new(), None::<&String>, None).unwrap(); + let root_summary = Summary::new( + root_pkg_id, + deps, + &BTreeMap::new(), + None::<&String>, + None, + None, + ) + .unwrap(); let opts = ResolveOpts::everything(); diff --git a/src/cargo/core/resolver/features.rs b/src/cargo/core/resolver/features.rs index 204c5032da8..a015efb30f2 100644 --- a/src/cargo/core/resolver/features.rs +++ b/src/cargo/core/resolver/features.rs @@ -862,6 +862,11 @@ impl<'a, 'gctx> FeatureResolver<'a, 'gctx> { self.resolve .deps(pkg_id) .map(|(dep_id, deps)| { + let proc_macro = self + .resolve + .summary(dep_id) + .proc_macro() + .unwrap_or_else(|| self.has_proc_macro_lib(dep_id)); let deps = deps .iter() .filter(|dep| { @@ -907,10 +912,9 @@ impl<'a, 'gctx> FeatureResolver<'a, 'gctx> { // for various targets which are either specified in the manifest // or on the cargo command-line. let lib_fk = if fk == FeaturesFor::default() { - (self.track_for_host - && (dep.is_build() || self.has_proc_macro_lib(dep_id))) - .then(|| FeaturesFor::HostDep) - .unwrap_or_default() + (self.track_for_host && (dep.is_build() || proc_macro)) + .then(|| FeaturesFor::HostDep) + .unwrap_or_default() } else { fk }; diff --git a/src/cargo/core/resolver/version_prefs.rs b/src/cargo/core/resolver/version_prefs.rs index 61f17404936..f2ebb3ef028 100644 --- a/src/cargo/core/resolver/version_prefs.rs +++ b/src/cargo/core/resolver/version_prefs.rs @@ -143,6 +143,7 @@ mod test { &features, None::<&String>, msrv.map(|m| m.parse().unwrap()), + None, ) .unwrap() } diff --git a/src/cargo/core/summary.rs b/src/cargo/core/summary.rs index 92cc8ce8ad9..0999247a20a 100644 --- a/src/cargo/core/summary.rs +++ b/src/cargo/core/summary.rs @@ -29,6 +29,7 @@ struct Inner { checksum: Option, links: Option, rust_version: Option, + proc_macro: Option, } /// Indicates the dependency inferred from the `dep` syntax that should exist, @@ -68,6 +69,7 @@ impl Summary { features: &BTreeMap>, links: Option>, rust_version: Option, + proc_macro: Option, ) -> CargoResult { // ****CAUTION**** If you change anything here that may raise a new // error, be sure to coordinate that change with either the index @@ -90,6 +92,7 @@ impl Summary { checksum: None, links: links.map(|l| l.into()), rust_version, + proc_macro, }), }) } @@ -112,6 +115,9 @@ impl Summary { pub fn features(&self) -> &FeatureMap { &self.inner.features } + pub fn proc_macro(&self) -> &Option { + &self.inner.proc_macro + } pub fn checksum(&self) -> Option<&str> { self.inner.checksum.as_deref() diff --git a/src/cargo/ops/cargo_package/mod.rs b/src/cargo/ops/cargo_package/mod.rs index 73bb0f7570c..eea40ba68e9 100644 --- a/src/cargo/ops/cargo_package/mod.rs +++ b/src/cargo/ops/cargo_package/mod.rs @@ -1212,6 +1212,7 @@ impl<'a> TmpRegistry<'a> { yanked: None, links: new_crate.links.map(|x| x.into()), rust_version: None, + proc_macro: Some(package.proc_macro()), v: Some(2), })?; diff --git a/src/cargo/ops/registry/publish.rs b/src/cargo/ops/registry/publish.rs index 16c0348be65..93a98beb8d9 100644 --- a/src/cargo/ops/registry/publish.rs +++ b/src/cargo/ops/registry/publish.rs @@ -634,6 +634,13 @@ pub(crate) fn prepare_transmit( None => BTreeMap::new(), }; + let proc_macro = manifest + .normalized_toml() + .lib + .as_ref() + .and_then(|lib| lib.proc_macro()) + .unwrap_or_default(); + Ok(NewCrate { name: publish_pkg.name().to_string(), vers: publish_pkg.version().to_string(), @@ -653,6 +660,7 @@ pub(crate) fn prepare_transmit( badges: badges.clone(), links: links.clone(), rust_version, + proc_macro: Some(proc_macro), }) } diff --git a/src/cargo/sources/registry/index/mod.rs b/src/cargo/sources/registry/index/mod.rs index f2cd5ae39a5..ec15ad3c46c 100644 --- a/src/cargo/sources/registry/index/mod.rs +++ b/src/cargo/sources/registry/index/mod.rs @@ -215,7 +215,14 @@ fn index_package_to_summary(pkg: &IndexPackage<'_>, source_id: SourceId) -> Carg .map(|(name, values)| (name.into(), values.into_iter().map(|v| v.into()).collect())) .collect::>(); let links: Option = pkg.links.as_ref().map(|l| l.as_ref().into()); - let mut summary = Summary::new(pkgid, deps, &features, links, pkg.rust_version.clone())?; + let mut summary = Summary::new( + pkgid, + deps, + &features, + links, + pkg.rust_version.clone(), + pkg.proc_macro, + )?; summary.set_checksum(pkg.cksum.clone()); Ok(summary) } @@ -674,6 +681,7 @@ impl IndexSummary { cksum: Default::default(), yanked: Default::default(), links: Default::default(), + proc_macro: Default::default(), }; let summary = index_package_to_summary(&index, source_id)?; (index, summary, false) diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index facd5873362..cfd5a7311f4 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -1725,6 +1725,10 @@ pub fn to_real_manifest( .unwrap_or_else(|| semver::Version::new(0, 0, 0)), source_id, ); + let proc_macro = normalized_toml + .lib + .as_ref() + .and_then(|lib| lib.proc_macro()); let summary = { let summary = Summary::new( pkgid, @@ -1743,6 +1747,7 @@ pub fn to_real_manifest( .collect(), normalized_package.links.as_deref(), rust_version.clone(), + proc_macro, ); // edition2024 stops exposing implicit features, which will strip weak optional dependencies from `dependencies`, // need to check whether `dep_name` is stripped as unused dependency diff --git a/tests/testsuite/alt_registry.rs b/tests/testsuite/alt_registry.rs index 689da0f8845..6348e734715 100644 --- a/tests/testsuite/alt_registry.rs +++ b/tests/testsuite/alt_registry.rs @@ -396,6 +396,7 @@ fn publish_with_registry_dependency() { "license_file": null, "links": null, "name": "foo", + "proc_macro": false, "readme": null, "readme_file": null, "repository": null, @@ -550,6 +551,7 @@ fn publish_to_alt_registry() { "license_file": null, "links": null, "name": "foo", + "proc_macro": false, "readme": null, "readme_file": null, "repository": null, @@ -563,6 +565,79 @@ fn publish_to_alt_registry() { ); } +#[cargo_test] +fn publish_proc_macro_to_alt_registry() { + let _reg = RegistryBuilder::new() + .http_api() + .http_index() + .alternative() + .build(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + edition = "2015" + authors = [] + license = "MIT" + description = "foo" + + [lib] + proc-macro = true + "#, + ) + .file("src/lib.rs", "fn t() -> i32 { 1 }") + .build(); + + p.cargo("publish --no-verify --registry alternative") + .with_stderr_data(str![[r#" +[UPDATING] `alternative` index +[WARNING] manifest has no documentation, homepage or repository + | + = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info +[PACKAGING] foo v0.0.1 ([ROOT]/foo) +[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) +[UPLOADING] foo v0.0.1 ([ROOT]/foo) +[UPLOADED] foo v0.0.1 to registry `alternative` +[NOTE] waiting for foo v0.0.1 to be available at registry `alternative` +[HELP] you may press ctrl-c to skip waiting; the crate should be available shortly +[PUBLISHED] foo v0.0.1 at registry `alternative` + +"#]]) + .run(); + + validate_alt_upload( + r#" + { + "authors": [], + "badges": {}, + "categories": [], + "deps": [], + "description": "foo", + "documentation": null, + "features": {}, + "homepage": null, + "keywords": [], + "license": "MIT", + "license_file": null, + "links": null, + "name": "foo", + "proc_macro": true, + "readme": null, + "readme_file": null, + "repository": null, + "rust_version": null, + "vers": "0.0.1" + } + "#, + "foo-0.0.1.crate", + &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/lib.rs"], + ); +} + #[cargo_test] fn publish_with_crates_io_dep() { // crates.io registry. @@ -644,6 +719,7 @@ fn publish_with_crates_io_dep() { "license_file": null, "links": null, "name": "foo", + "proc_macro": false, "readme": null, "readme_file": null, "repository": null, @@ -1865,6 +1941,70 @@ fn publish_with_transitive_dep() { p2.cargo("publish").run(); } +#[cargo_test] +fn proc_macro_dependency() { + registry::alt_init(); + + Package::new("noop", "0.0.1") + .file( + "Cargo.toml", + r#" + [package] + name = "noop" + version = "0.0.1" + edition = "2021" + + [lib] + proc-macro = true + "#, + ) + .proc_macro(true) + .alternative(true) + .file( + "src/lib.rs", + r#" + extern crate proc_macro; + use proc_macro::TokenStream; + + #[proc_macro_derive(Noop)] + pub fn noop(_input: TokenStream) -> TokenStream { + "".parse().unwrap() + } + "#, + ) + .publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + edition = "2021" + + [dependencies.noop] + version = "0.0.1" + registry = "alternative" + "#, + ) + .file( + "src/main.rs", + r#" + #[macro_use] + extern crate noop; + + #[derive(Noop)] + struct X; + + fn main() {} + "#, + ) + .build(); + + p.cargo("build").with_status(0).run(); +} + #[cargo_test] fn warn_for_unused_fields() { let _ = RegistryBuilder::new() diff --git a/tests/testsuite/artifact_dep.rs b/tests/testsuite/artifact_dep.rs index e75ff9722a1..cbd6f22e912 100644 --- a/tests/testsuite/artifact_dep.rs +++ b/tests/testsuite/artifact_dep.rs @@ -2310,6 +2310,7 @@ fn publish_artifact_dep() { "license_file": null, "links": null, "name": "foo", + "proc_macro": false, "readme": null, "readme_file": null, "repository": "foo", diff --git a/tests/testsuite/features_namespaced.rs b/tests/testsuite/features_namespaced.rs index c79f5fdc14a..20f897515ec 100644 --- a/tests/testsuite/features_namespaced.rs +++ b/tests/testsuite/features_namespaced.rs @@ -974,6 +974,7 @@ fn publish_no_implicit() { "license_file": null, "links": null, "name": "foo", + "proc_macro": false, "readme": null, "readme_file": null, "repository": null, @@ -1111,6 +1112,7 @@ fn publish() { "license_file": null, "links": null, "name": "foo", + "proc_macro": false, "readme": null, "readme_file": null, "repository": null, diff --git a/tests/testsuite/inheritable_workspace_fields.rs b/tests/testsuite/inheritable_workspace_fields.rs index 8fcf8625023..5b069db5b3e 100644 --- a/tests/testsuite/inheritable_workspace_fields.rs +++ b/tests/testsuite/inheritable_workspace_fields.rs @@ -190,6 +190,7 @@ fn inherit_own_workspace_fields() { "license_file": null, "links": null, "name": "foo", + "proc_macro": false, "readme": null, "readme_file": null, "repository": "https://github.com/example/example", @@ -386,6 +387,7 @@ fn inherit_own_dependencies() { "license_file": null, "links": null, "name": "bar", + "proc_macro": false, "readme": null, "readme_file": null, "repository": null, @@ -536,6 +538,7 @@ fn inherit_own_detailed_dependencies() { "license_file": null, "links": null, "name": "bar", + "proc_macro": false, "readme": null, "readme_file": null, "repository": null, @@ -787,6 +790,7 @@ See https://doc.rust-lang.org/cargo/reference/manifest.html#the-license-and-lice "license_file": "LICENSE", "links": null, "name": "bar", + "proc_macro": false, "readme": "README.md", "readme_file": "README.md", "repository": "https://github.com/example/example", @@ -990,6 +994,7 @@ fn inherit_dependencies() { "license_file": null, "links": null, "name": "bar", + "proc_macro": false, "readme": null, "readme_file": null, "repository": null, diff --git a/tests/testsuite/publish.rs b/tests/testsuite/publish.rs index 43e43cbab13..8bcdb75bc81 100644 --- a/tests/testsuite/publish.rs +++ b/tests/testsuite/publish.rs @@ -24,6 +24,7 @@ const CLEAN_FOO_JSON: &str = r#" "license_file": null, "links": null, "name": "foo", + "proc_macro": false, "readme": null, "readme_file": null, "repository": "foo", @@ -49,6 +50,7 @@ fn validate_upload_foo() { "license_file": null, "links": null, "name": "foo", + "proc_macro": false, "readme": null, "readme_file": null, "repository": null, @@ -78,6 +80,7 @@ fn validate_upload_li() { "license_file": null, "links": null, "name": "li", + "proc_macro": false, "readme": null, "readme_file": null, "repository": null, @@ -131,6 +134,76 @@ fn simple() { validate_upload_foo(); } +#[cargo_test] +fn proc_macro() { + let registry = RegistryBuilder::new().http_api().http_index().build(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + edition = "2015" + authors = [] + license = "MIT" + description = "foo" + + [lib] + proc-macro = true + "#, + ) + .file("src/lib.rs", "fn t() -> i32 { 1 }") + .build(); + + p.cargo("publish --no-verify") + .replace_crates_io(registry.index_url()) + .with_stderr_data(str![[r#" +[UPDATING] crates.io index +[WARNING] manifest has no documentation, homepage or repository + | + = [NOTE] see https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info +[PACKAGING] foo v0.0.1 ([ROOT]/foo) +[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) +[UPLOADING] foo v0.0.1 ([ROOT]/foo) +[UPLOADED] foo v0.0.1 to registry `crates-io` +[NOTE] waiting for foo v0.0.1 to be available at registry `crates-io` +[HELP] you may press ctrl-c to skip waiting; the crate should be available shortly +[PUBLISHED] foo v0.0.1 at registry `crates-io` + +"#]]) + .run(); + + publish::validate_upload( + r#" + { + "authors": [], + "badges": {}, + "categories": [], + "deps": [], + "description": "foo", + "documentation": null, + "features": {}, + "homepage": null, + "keywords": [], + "license": "MIT", + "license_file": null, + "links": null, + "name": "foo", + "proc_macro": true, + "readme": null, + "readme_file": null, + "repository": null, + "rust_version": null, + "vers": "0.0.1" + } + "#, + "foo-0.0.1.crate", + &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/lib.rs"], + ); +} + #[cargo_test] fn duplicate_version() { let registry_dupl = RegistryBuilder::new().http_api().http_index().build(); @@ -1391,6 +1464,7 @@ error[E0425]: cannot find function `newfunc` in crate `bar` "license_file": null, "links": null, "name": "foo", + "proc_macro": false, "readme": null, "readme_file": null, "repository": null, @@ -1600,6 +1674,7 @@ fn publish_git_with_version() { "license_file": null, "links": null, "name": "foo", + "proc_macro": false, "readme": null, "readme_file": null, "repository": null, @@ -1994,6 +2069,7 @@ fn publish_dev_dep_stripping() { "license_file": null, "links": null, "name": "foo", + "proc_macro": false, "readme": null, "readme_file": null, "repository": "foo", diff --git a/tests/testsuite/registry.rs b/tests/testsuite/registry.rs index fe9a14755de..5fe137c77d2 100644 --- a/tests/testsuite/registry.rs +++ b/tests/testsuite/registry.rs @@ -4653,3 +4653,63 @@ required by package `foo v0.0.1 ([ROOT]/foo)` "#]]) .run(); } + +#[cargo_test] +fn proc_macro_dependency() { + Package::new("noop", "0.0.1") + .file( + "Cargo.toml", + r#" + [package] + name = "noop" + version = "0.0.1" + edition = "2021" + + [lib] + proc-macro = true + "#, + ) + .proc_macro(true) + .file( + "src/lib.rs", + r#" + extern crate proc_macro; + use proc_macro::TokenStream; + + #[proc_macro_derive(Noop)] + pub fn noop(_input: TokenStream) -> TokenStream { + "".parse().unwrap() + } + "#, + ) + .publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + edition = "2021" + + [dependencies] + noop = "0.0.1" + "#, + ) + .file( + "src/main.rs", + r#" + #[macro_use] + extern crate noop; + + #[derive(Noop)] + struct X; + + fn main() {} + "#, + ) + .build(); + + p.cargo("build").with_status(0).run(); +} diff --git a/tests/testsuite/sbom.rs b/tests/testsuite/sbom.rs index 8c272a767d3..854fefb15da 100644 --- a/tests/testsuite/sbom.rs +++ b/tests/testsuite/sbom.rs @@ -561,6 +561,7 @@ fn proc_macro() { proc-macro = true "#, ) + .proc_macro(true) .file( "src/lib.rs", r#" diff --git a/tests/testsuite/weak_dep_features.rs b/tests/testsuite/weak_dep_features.rs index 5194ab4206f..578a0bf7098 100644 --- a/tests/testsuite/weak_dep_features.rs +++ b/tests/testsuite/weak_dep_features.rs @@ -612,6 +612,7 @@ fn publish() { "license_file": null, "links": null, "name": "foo", + "proc_macro": false, "readme": null, "readme_file": null, "repository": null,