diff --git a/src/cargo/ops/vendor.rs b/src/cargo/ops/vendor.rs index a060b3cd6db..6cd27b28899 100644 --- a/src/cargo/ops/vendor.rs +++ b/src/cargo/ops/vendor.rs @@ -221,7 +221,7 @@ fn sync( let pathsource = PathSource::new(src, id.source_id(), gctx); let paths = pathsource.list_files(pkg)?; let mut map = BTreeMap::new(); - cp_sources(pkg, src, &paths, &dst, &mut map, &mut tmp_buf) + cp_sources(pkg, src, &paths, &dst, &mut map, &mut tmp_buf, gctx) .with_context(|| format!("failed to copy over vendored sources for: {}", id))?; // Finally, emit the metadata about this package @@ -317,6 +317,7 @@ fn cp_sources( dst: &Path, cksums: &mut BTreeMap, tmp_buf: &mut [u8], + gctx: &GlobalContext, ) -> CargoResult<()> { for p in paths { let relative = p.strip_prefix(&src).unwrap(); @@ -360,7 +361,12 @@ fn cp_sources( let cksum = if dst.file_name() == Some(OsStr::new("Cargo.toml")) && pkg.package_id().source_id().is_git() { - let contents = pkg.manifest().to_normalized_contents()?; + let packaged_files = paths + .iter() + .map(|p| p.strip_prefix(src).unwrap().to_owned()) + .collect::>(); + let vendored_pkg = prepare_for_vendor(pkg, &packaged_files, gctx)?; + let contents = vendored_pkg.manifest().to_normalized_contents()?; copy_and_checksum( &dst, &mut dst_opts, @@ -392,6 +398,119 @@ fn cp_sources( Ok(()) } +/// HACK: Perform the bare minimum of `prepare_for_publish` needed for #14348. +/// +/// There are parts of `prepare_for_publish` that could be directly useful (e.g. stripping +/// `[workspace]`) while other parts that require other filesystem operations (moving the README +/// file) and ideally we'd reuse `cargo package` code to take care of all of this for us. +fn prepare_for_vendor( + me: &Package, + packaged_files: &[PathBuf], + gctx: &GlobalContext, +) -> CargoResult { + let contents = me.manifest().contents(); + let document = me.manifest().document(); + let original_toml = prepare_toml_for_vendor( + me.manifest().normalized_toml().clone(), + packaged_files, + gctx, + )?; + let normalized_toml = original_toml.clone(); + let features = me.manifest().unstable_features().clone(); + let workspace_config = me.manifest().workspace_config().clone(); + let source_id = me.package_id().source_id(); + let mut warnings = Default::default(); + let mut errors = Default::default(); + let manifest = crate::util::toml::to_real_manifest( + contents.to_owned(), + document.clone(), + original_toml, + normalized_toml, + features, + workspace_config, + source_id, + me.manifest_path(), + gctx, + &mut warnings, + &mut errors, + )?; + let new_pkg = Package::new(manifest, me.manifest_path()); + Ok(new_pkg) +} + +fn prepare_toml_for_vendor( + mut me: cargo_util_schemas::manifest::TomlManifest, + packaged_files: &[PathBuf], + gctx: &GlobalContext, +) -> CargoResult { + let package = me + .package + .as_mut() + .expect("venedored manifests must have packages"); + if let Some(cargo_util_schemas::manifest::StringOrBool::String(path)) = &package.build { + let path = paths::normalize_path(Path::new(path)); + let included = packaged_files.contains(&path); + let build = if included { + let path = path + .into_os_string() + .into_string() + .map_err(|_err| anyhow::format_err!("non-UTF8 `package.build`"))?; + let path = crate::util::toml::normalize_path_string_sep(path); + cargo_util_schemas::manifest::StringOrBool::String(path) + } else { + gctx.shell().warn(format!( + "ignoring `package.build` as `{}` is not included in the published package", + path.display() + ))?; + cargo_util_schemas::manifest::StringOrBool::Bool(false) + }; + package.build = Some(build); + } + + let lib = if let Some(target) = &me.lib { + crate::util::toml::prepare_target_for_publish( + target, + Some(packaged_files), + "library", + gctx, + )? + } else { + None + }; + let bin = crate::util::toml::prepare_targets_for_publish( + me.bin.as_ref(), + Some(packaged_files), + "binary", + gctx, + )?; + let example = crate::util::toml::prepare_targets_for_publish( + me.example.as_ref(), + Some(packaged_files), + "example", + gctx, + )?; + let test = crate::util::toml::prepare_targets_for_publish( + me.test.as_ref(), + Some(packaged_files), + "test", + gctx, + )?; + let bench = crate::util::toml::prepare_targets_for_publish( + me.bench.as_ref(), + Some(packaged_files), + "benchmark", + gctx, + )?; + + me.lib = lib; + me.bin = bin; + me.example = example; + me.test = test; + me.bench = bench; + + Ok(me) +} + fn copy_and_checksum( dst_path: &Path, dst_opts: &mut OpenOptions, diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index 35318991789..172a4a736a5 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -1086,7 +1086,7 @@ fn deprecated_ws_default_features( } #[tracing::instrument(skip_all)] -fn to_real_manifest( +pub fn to_real_manifest( contents: String, document: toml_edit::ImDocument, original_toml: manifest::TomlManifest, @@ -2889,7 +2889,7 @@ fn prepare_toml_for_publish( } } -fn prepare_targets_for_publish( +pub fn prepare_targets_for_publish( targets: Option<&Vec>, packaged_files: Option<&[PathBuf]>, context: &str, @@ -2915,7 +2915,7 @@ fn prepare_targets_for_publish( } } -fn prepare_target_for_publish( +pub fn prepare_target_for_publish( target: &manifest::TomlTarget, packaged_files: Option<&[PathBuf]>, context: &str, @@ -2950,7 +2950,7 @@ fn normalize_path_sep(path: PathBuf, context: &str) -> CargoResult { Ok(path.into()) } -fn normalize_path_string_sep(path: String) -> String { +pub fn normalize_path_string_sep(path: String) -> String { if std::path::MAIN_SEPARATOR != '/' { path.replace(std::path::MAIN_SEPARATOR, "/") } else { diff --git a/tests/testsuite/vendor.rs b/tests/testsuite/vendor.rs index c103955dfe7..0f3a7f7b4ef 100644 --- a/tests/testsuite/vendor.rs +++ b/tests/testsuite/vendor.rs @@ -36,7 +36,7 @@ fn vendor_simple() { let lock = p.read_file("vendor/log/Cargo.toml"); assert!(lock.contains("version = \"0.3.5\"")); - add_vendor_config(&p); + add_crates_io_vendor_config(&p); p.cargo("check").run(); } @@ -151,7 +151,7 @@ directory = "deps/.vendor" assert!(lock.contains("version = \"0.3.5\"")); } -fn add_vendor_config(p: &Project) { +fn add_crates_io_vendor_config(p: &Project) { p.change_file( ".cargo/config.toml", r#" @@ -164,6 +164,23 @@ fn add_vendor_config(p: &Project) { ); } +fn add_git_vendor_config(p: &Project, git_project: &Project) { + p.change_file( + ".cargo/config.toml", + &format!( + r#" + [source."git+{url}"] + git = "{url}" + replace-with = 'vendor' + + [source.vendor] + directory = 'vendor' + "#, + url = git_project.url() + ), + ); +} + #[cargo_test] fn package_exclude() { let p = project() @@ -208,6 +225,554 @@ fn package_exclude() { assert!(!csum.contains(".dotdir/include")); } +#[cargo_test] +fn discovery_inferred_build_rs_included() { + let git_project = git::new("dep", |project| { + project + .file( + "Cargo.toml", + r#" + [package] + name = "dep" + version = "0.0.1" + edition = "2015" + license = "MIT" + description = "foo" + documentation = "docs.rs/foo" + authors = [] + include = ["src/lib.rs", "build.rs"] + "#, + ) + .file("src/lib.rs", "") + .file("build.rs", "fn main() {}") + }); + + let p = project() + .file( + "Cargo.toml", + &format!( + r#" + [package] + name = "foo" + version = "0.5.0" + edition = "2015" + + [dependencies.dep] + git = '{}' + "#, + git_project.url() + ), + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("vendor --respect-source-config").run(); + add_git_vendor_config(&p, &git_project); + + let lock = p.read_file("vendor/dep/Cargo.toml"); + assert_e2e().eq( + lock, + str![[r##" +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2015" +name = "dep" +version = "0.0.1" +authors = [] +build = "build.rs" +include = [ + "src/lib.rs", + "build.rs", +] +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "foo" +documentation = "docs.rs/foo" +readme = false +license = "MIT" + +[lib] +name = "dep" +path = "src/lib.rs" + +"##]], + ); + + p.cargo("check").run(); +} + +#[cargo_test] +fn discovery_inferred_build_rs_excluded() { + let git_project = git::new("dep", |project| { + project + .file( + "Cargo.toml", + r#" + [package] + name = "dep" + version = "0.0.1" + edition = "2015" + license = "MIT" + description = "foo" + documentation = "docs.rs/foo" + authors = [] + include = ["src/lib.rs"] + "#, + ) + .file("src/lib.rs", "") + .file("build.rs", "fn main() {}") + }); + + let p = project() + .file( + "Cargo.toml", + &format!( + r#" + [package] + name = "foo" + version = "0.5.0" + edition = "2015" + + [dependencies.dep] + git = '{}' + "#, + git_project.url() + ), + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("vendor --respect-source-config").run(); + add_git_vendor_config(&p, &git_project); + + let lock = p.read_file("vendor/dep/Cargo.toml"); + assert_e2e().eq( + lock, + str![[r##" +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2015" +name = "dep" +version = "0.0.1" +authors = [] +build = false +include = ["src/lib.rs"] +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "foo" +documentation = "docs.rs/foo" +readme = false +license = "MIT" + +[lib] +name = "dep" +path = "src/lib.rs" + +"##]], + ); + + p.cargo("check").run(); +} + +#[cargo_test] +fn discovery_inferred_lib_included() { + let git_project = git::new("dep", |project| { + project + .file( + "Cargo.toml", + r#" + [package] + name = "dep" + version = "0.0.1" + edition = "2015" + license = "MIT" + description = "foo" + documentation = "docs.rs/foo" + authors = [] + include = ["src/main.rs", "src/lib.rs"] + "#, + ) + .file("src/main.rs", "fn main() {}") + .file("src/lib.rs", "") + }); + + let p = project() + .file( + "Cargo.toml", + &format!( + r#" + [package] + name = "foo" + version = "0.5.0" + edition = "2015" + + [dependencies.dep] + git = '{}' + "#, + git_project.url() + ), + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("vendor --respect-source-config").run(); + add_git_vendor_config(&p, &git_project); + + let lock = p.read_file("vendor/dep/Cargo.toml"); + assert_e2e().eq( + lock, + str![[r##" +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2015" +name = "dep" +version = "0.0.1" +authors = [] +build = false +include = [ + "src/main.rs", + "src/lib.rs", +] +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "foo" +documentation = "docs.rs/foo" +readme = false +license = "MIT" + +[lib] +name = "dep" +path = "src/lib.rs" + +[[bin]] +name = "dep" +path = "src/main.rs" + +"##]], + ); + + p.cargo("check").run(); +} + +#[cargo_test] +fn discovery_inferred_lib_excluded() { + let git_project = git::new("dep", |project| { + project + .file( + "Cargo.toml", + r#" + [package] + name = "dep" + version = "0.0.1" + edition = "2015" + license = "MIT" + description = "foo" + documentation = "docs.rs/foo" + authors = [] + include = ["src/main.rs"] + "#, + ) + .file("src/main.rs", "fn main() {}") + .file("src/lib.rs", "") + }); + + let p = project() + .file( + "Cargo.toml", + &format!( + r#" + [package] + name = "foo" + version = "0.5.0" + edition = "2015" + + [dependencies.dep] + git = '{}' + "#, + git_project.url() + ), + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("vendor --respect-source-config").run(); + add_git_vendor_config(&p, &git_project); + + let lock = p.read_file("vendor/dep/Cargo.toml"); + assert_e2e().eq( + lock, + str![[r##" +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2015" +name = "dep" +version = "0.0.1" +authors = [] +build = false +include = ["src/main.rs"] +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "foo" +documentation = "docs.rs/foo" +readme = false +license = "MIT" + +[[bin]] +name = "dep" +path = "src/main.rs" + +"##]], + ); + + p.cargo("check").run(); +} + +#[cargo_test] +fn discovery_inferred_other_included() { + let git_project = git::new("dep", |project| { + project + .file( + "Cargo.toml", + r#" + [package] + name = "dep" + version = "0.0.1" + edition = "2015" + license = "MIT" + description = "foo" + documentation = "docs.rs/foo" + authors = [] + include = ["src/lib.rs", "src/bin/foo/main.rs", "examples/example_foo.rs", "tests/test_foo.rs", "benches/bench_foo.rs"] + "#, + ) + .file("src/lib.rs", "") + .file("src/bin/foo/main.rs", "fn main() {}") + .file("examples/example_foo.rs", "fn main() {}") + .file("tests/test_foo.rs", "fn main() {}") + .file("benches/bench_foo.rs", "fn main() {}") + }); + + let p = project() + .file( + "Cargo.toml", + &format!( + r#" + [package] + name = "foo" + version = "0.5.0" + edition = "2015" + + [dependencies.dep] + git = '{}' + "#, + git_project.url() + ), + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("vendor --respect-source-config").run(); + add_git_vendor_config(&p, &git_project); + + let lock = p.read_file("vendor/dep/Cargo.toml"); + assert_e2e().eq( + lock, + str![[r##" +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2015" +name = "dep" +version = "0.0.1" +authors = [] +build = false +include = [ + "src/lib.rs", + "src/bin/foo/main.rs", + "examples/example_foo.rs", + "tests/test_foo.rs", + "benches/bench_foo.rs", +] +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "foo" +documentation = "docs.rs/foo" +readme = false +license = "MIT" + +[lib] +name = "dep" +path = "src/lib.rs" + +[[bin]] +name = "foo" +path = "src/bin/foo/main.rs" + +[[example]] +name = "example_foo" +path = "examples/example_foo.rs" + +[[test]] +name = "test_foo" +path = "tests/test_foo.rs" + +[[bench]] +name = "bench_foo" +path = "benches/bench_foo.rs" + +"##]], + ); + + p.cargo("check").run(); +} + +#[cargo_test] +fn discovery_inferred_other_excluded() { + let git_project = git::new("dep", |project| { + project + .file( + "Cargo.toml", + r#" + [package] + name = "dep" + version = "0.0.1" + edition = "2015" + license = "MIT" + description = "foo" + documentation = "docs.rs/foo" + authors = [] + include = ["src/lib.rs"] + "#, + ) + .file("src/lib.rs", "") + .file("src/bin/foo/main.rs", "fn main() {}") + .file("examples/example_foo.rs", "fn main() {}") + .file("tests/test_foo.rs", "fn main() {}") + .file("benches/bench_foo.rs", "fn main() {}") + }); + + let p = project() + .file( + "Cargo.toml", + &format!( + r#" + [package] + name = "foo" + version = "0.5.0" + edition = "2015" + + [dependencies.dep] + git = '{}' + "#, + git_project.url() + ), + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("vendor --respect-source-config").run(); + add_git_vendor_config(&p, &git_project); + + let lock = p.read_file("vendor/dep/Cargo.toml"); + assert_e2e().eq( + lock, + str![[r##" +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2015" +name = "dep" +version = "0.0.1" +authors = [] +build = false +include = ["src/lib.rs"] +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "foo" +documentation = "docs.rs/foo" +readme = false +license = "MIT" + +[lib] +name = "dep" +path = "src/lib.rs" + +"##]], + ); + + p.cargo("check").run(); +} + #[cargo_test] fn two_versions() { let p = project() @@ -248,7 +813,7 @@ fn two_versions() { let lock = p.read_file("vendor/bitflags-0.7.0/Cargo.toml"); assert!(lock.contains("version = \"0.7.0\"")); - add_vendor_config(&p); + add_crates_io_vendor_config(&p); p.cargo("check").run(); } @@ -293,7 +858,7 @@ fn two_explicit_versions() { let lock = p.read_file("vendor/bitflags-0.7.0/Cargo.toml"); assert!(lock.contains("version = \"0.7.0\"")); - add_vendor_config(&p); + add_crates_io_vendor_config(&p); p.cargo("check").run(); } @@ -386,7 +951,7 @@ fn two_lockfiles() { let lock = p.read_file("vendor/bitflags-0.7.0/Cargo.toml"); assert!(lock.contains("version = \"0.7.0\"")); - add_vendor_config(&p); + add_crates_io_vendor_config(&p); p.cargo("check").cwd("foo").run(); p.cargo("check").cwd("bar").run(); } @@ -645,7 +1210,7 @@ fn vendoring_git_crates() { p.cargo("vendor --respect-source-config").run(); p.read_file("vendor/serde_derive/src/wut.rs"); - add_vendor_config(&p); + add_crates_io_vendor_config(&p); p.cargo("check").run(); } @@ -956,10 +1521,6 @@ fn git_deterministic() { # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. -bin = [] -test = [] -bench = [] - [package] edition = "2021" name = "git_dep" @@ -977,7 +1538,7 @@ license = "MIT" [lib] name = "git_dep" -path = [..] +path = "src/lib.rs" [[example]] name = "a"