From a4a3302d46f5754037b04fded9518fb693ac7d0c Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 27 Feb 2018 07:56:04 -0800 Subject: [PATCH] Package lock files in published crates Previously we had logic to explicitly skip lock files but there's actually a good case to read these from crates.io (#2263) so let's do so! Closes #2263 --- src/cargo/core/features.rs | 3 + src/cargo/core/manifest.rs | 4 + src/cargo/ops/cargo_package.rs | 26 ++++++ src/cargo/util/toml/mod.rs | 11 +++ tests/testsuite/install.rs | 30 +++++++ tests/testsuite/package.rs | 160 ++++++++++++++++++++++++++++++++- 6 files changed, 233 insertions(+), 1 deletion(-) diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index 20d60d17da9..7583ac40f65 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -162,6 +162,9 @@ features! { // Renaming a package in the manifest via the `package` key [unstable] rename_dependency: bool, + + // Whether a lock file is published with this crate + [unstable] publish_lockfile: bool, } } diff --git a/src/cargo/core/manifest.rs b/src/cargo/core/manifest.rs index 0ebe5f533cd..557380d6c50 100644 --- a/src/cargo/core/manifest.rs +++ b/src/cargo/core/manifest.rs @@ -31,6 +31,7 @@ pub struct Manifest { metadata: ManifestMetadata, profiles: Profiles, publish: Option>, + publish_lockfile: bool, replace: Vec<(PackageIdSpec, Dependency)>, patch: HashMap>, workspace: WorkspaceConfig, @@ -267,6 +268,7 @@ impl Manifest { metadata: ManifestMetadata, profiles: Profiles, publish: Option>, + publish_lockfile: bool, replace: Vec<(PackageIdSpec, Dependency)>, patch: HashMap>, workspace: WorkspaceConfig, @@ -291,6 +293,7 @@ impl Manifest { epoch, original, im_a_teapot, + publish_lockfile, } } @@ -306,6 +309,7 @@ impl Manifest { pub fn warnings(&self) -> &[DelayedWarning] { &self.warnings } pub fn profiles(&self) -> &Profiles { &self.profiles } pub fn publish(&self) -> &Option> { &self.publish } + pub fn publish_lockfile(&self) -> bool { self.publish_lockfile } pub fn replace(&self) -> &[(PackageIdSpec, Dependency)] { &self.replace } pub fn original(&self) -> &TomlManifest { &self.original } pub fn patch(&self) -> &HashMap> { &self.patch } diff --git a/src/cargo/ops/cargo_package.rs b/src/cargo/ops/cargo_package.rs index c53bc477b9a..0ad54e6a704 100644 --- a/src/cargo/ops/cargo_package.rs +++ b/src/cargo/ops/cargo_package.rs @@ -12,6 +12,7 @@ use tar::{Archive, Builder, Header, EntryType}; use core::{Package, Workspace, Source, SourceId}; use sources::PathSource; use util::{self, internal, Config, FileLock}; +use util::paths; use util::errors::{CargoResult, CargoResultExt}; use ops::{self, DefaultExecutor}; @@ -28,6 +29,7 @@ pub struct PackageOpts<'cfg> { pub fn package(ws: &Workspace, opts: &PackageOpts) -> CargoResult> { + ops::resolve_ws(ws)?; let pkg = ws.current()?; let config = ws.config(); @@ -47,6 +49,9 @@ pub fn package(ws: &Workspace, let mut list: Vec<_> = src.list_files(pkg)?.iter().map(|file| { util::without_prefix(file, root).unwrap().to_path_buf() }).collect(); + if include_lockfile(&pkg) { + list.push("Cargo.lock".into()); + } list.sort(); for file in list.iter() { println!("{}", file.display()); @@ -91,6 +96,11 @@ pub fn package(ws: &Workspace, Ok(Some(dst)) } +fn include_lockfile(pkg: &Package) -> bool { + pkg.manifest().publish_lockfile() && + pkg.targets().iter().any(|t| t.is_example() || t.is_bin()) +} + // check that the package has some piece of metadata that a human can // use to tell what the package is about. fn check_metadata(pkg: &Package, config: &Config) -> CargoResult<()> { @@ -265,6 +275,22 @@ fn tar(ws: &Workspace, })?; } } + + if include_lockfile(pkg) { + let toml = paths::read(&ws.root().join("Cargo.lock"))?; + let path = format!("{}-{}{}Cargo.lock", pkg.name(), pkg.version(), + path::MAIN_SEPARATOR); + let mut header = Header::new_ustar(); + header.set_path(&path)?; + header.set_entry_type(EntryType::file()); + header.set_mode(0o644); + header.set_size(toml.len() as u64); + header.set_cksum(); + ar.append(&header, toml.as_bytes()).chain_err(|| { + internal("could not archive source file `Cargo.lock`") + })?; + } + let encoder = ar.into_inner()?; encoder.finish()?; Ok(()) diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index 3e1878853a7..a377f77e326 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -426,6 +426,8 @@ pub struct TomlProject { exclude: Option>, include: Option>, publish: Option, + #[serde(rename = "publish-lockfile")] + publish_lockfile: Option, workspace: Option, #[serde(rename = "im-a-teapot")] im_a_teapot: Option, @@ -719,6 +721,14 @@ impl TomlManifest { None | Some(VecStringOrBool::Bool(true)) => None, }; + let publish_lockfile = match project.publish_lockfile { + Some(b) => { + features.require(Feature::publish_lockfile())?; + b + } + None => false, + }; + let epoch = if let Some(ref epoch) = project.rust { features.require(Feature::epoch()).chain_err(|| { "epoches are unstable" @@ -739,6 +749,7 @@ impl TomlManifest { metadata, profiles, publish, + publish_lockfile, replace, patch, workspace_config, diff --git a/tests/testsuite/install.rs b/tests/testsuite/install.rs index 89adafc33f6..0103017501d 100644 --- a/tests/testsuite/install.rs +++ b/tests/testsuite/install.rs @@ -1044,3 +1044,33 @@ dependencies = [ assert_that(cargo_process("install").arg("foo"), execs().with_status(0)); } + +#[test] +fn lock_file_path_deps_ok() { + Package::new("bar", "0.1.0").publish(); + + Package::new("foo", "0.1.0") + .dep("bar", "0.1") + .file("src/lib.rs", "") + .file("src/main.rs", " + extern crate foo; + extern crate bar; + fn main() {} + ") + .file("Cargo.lock", r#" +[[package]] +name = "bar" +version = "0.1.0" + +[[package]] +name = "foo" +version = "0.1.0" +dependencies = [ + "bar 0.1.0", +] +"#) + .publish(); + + assert_that(cargo_process("install").arg("foo"), + execs().with_status(0)); +} diff --git a/tests/testsuite/package.rs b/tests/testsuite/package.rs index 425e7301911..f22e1a3ffad 100644 --- a/tests/testsuite/package.rs +++ b/tests/testsuite/package.rs @@ -702,8 +702,9 @@ to proceed despite this, pass the `--allow-dirty` flag #[test] fn generated_manifest() { Package::new("abc", "1.0.0").publish(); - Package::new("def", "1.0.0").publish(); + Package::new("def", "1.0.0").alternative(true).publish(); Package::new("ghi", "1.0.0").publish(); + let p = project("foo") .file("Cargo.toml", r#" cargo-features = ["alternative-registries"] @@ -995,3 +996,160 @@ Caused by: consider adding `cargo-features = [\"epoch\"]` to the manifest "))); } + +#[test] +fn package_lockfile() { + let p = project("foo") + .file("Cargo.toml", r#" + cargo-features = ["publish-lockfile"] + + [project] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + publish-lockfile = true + "#) + .file("src/main.rs", "fn main() {}") + .build(); + + assert_that(p.cargo("package").masquerade_as_nightly_cargo(), + execs().with_status(0).with_stderr(&format!("\ +[WARNING] manifest has no documentation[..] +See [..] +[PACKAGING] foo v0.0.1 ({dir}) +[VERIFYING] foo v0.0.1 ({dir}) +[COMPILING] foo v0.0.1 ({dir}[..]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + dir = p.url()))); + assert_that(&p.root().join("target/package/foo-0.0.1.crate"), existing_file()); + assert_that(p.cargo("package").arg("-l").masquerade_as_nightly_cargo(), + execs().with_status(0).with_stdout("\ +Cargo.lock +Cargo.toml +src[/]main.rs +")); + assert_that(p.cargo("package").masquerade_as_nightly_cargo(), + execs().with_status(0).with_stdout("")); + + let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap(); + let mut rdr = GzDecoder::new(f); + let mut contents = Vec::new(); + rdr.read_to_end(&mut contents).unwrap(); + let mut ar = Archive::new(&contents[..]); + for f in ar.entries().unwrap() { + let f = f.unwrap(); + let fname = f.header().path_bytes(); + let fname = &*fname; + assert!(fname == b"foo-0.0.1/Cargo.toml" || + fname == b"foo-0.0.1/Cargo.toml.orig" || + fname == b"foo-0.0.1/Cargo.lock" || + fname == b"foo-0.0.1/src/main.rs", + "unexpected filename: {:?}", f.header().path()) + } +} + +#[test] +fn package_lockfile_git_repo() { + let p = project("foo").build(); + + // Create a Git repository containing a minimal Rust project. + let _ = git::repo(&paths::root().join("foo")) + .file("Cargo.toml", r#" + cargo-features = ["publish-lockfile"] + + [project] + name = "foo" + version = "0.0.1" + license = "MIT" + description = "foo" + documentation = "foo" + homepage = "foo" + repository = "foo" + publish-lockfile = true + "#) + .file("src/main.rs", "fn main() {}") + .build(); + assert_that(p.cargo("package").arg("-l").masquerade_as_nightly_cargo(), + execs().with_status(0).with_stdout("\ +Cargo.lock +Cargo.toml +src/main.rs +")); +} + +#[test] +fn no_lock_file_with_library() { + let p = project("foo") + .file("Cargo.toml", r#" + cargo-features = ["publish-lockfile"] + + [project] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + publish-lockfile = true + "#) + .file("src/lib.rs", "") + .build(); + + assert_that(p.cargo("package").masquerade_as_nightly_cargo(), + execs().with_status(0)); + + let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap(); + let mut rdr = GzDecoder::new(f); + let mut contents = Vec::new(); + rdr.read_to_end(&mut contents).unwrap(); + let mut ar = Archive::new(&contents[..]); + for f in ar.entries().unwrap() { + let f = f.unwrap(); + let fname = f.header().path().unwrap(); + assert!(!fname.ends_with("Cargo.lock")); + } +} + +#[test] +fn lock_file_and_workspace() { + let p = project("foo") + .file("Cargo.toml", r#" + [workspace] + members = ["foo"] + "#) + .file("foo/Cargo.toml", r#" + cargo-features = ["publish-lockfile"] + + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + publish-lockfile = true + "#) + .file("foo/src/main.rs", "fn main() {}") + .build(); + + assert_that(p.cargo("package") + .cwd(p.root().join("foo")) + .masquerade_as_nightly_cargo(), + execs().with_status(0)); + + let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap(); + let mut rdr = GzDecoder::new(f); + let mut contents = Vec::new(); + rdr.read_to_end(&mut contents).unwrap(); + let mut ar = Archive::new(&contents[..]); + assert!( + ar.entries().unwrap() + .into_iter() + .any(|f|{ + let f = f.unwrap(); + let fname = f.header().path().unwrap(); + fname.ends_with("Cargo.lock") + }) + ); +}