Package lock files in published crates
authorAlex Crichton <alex@alexcrichton.com>
Tue, 27 Feb 2018 15:56:04 +0000 (07:56 -0800)
committerAlex Crichton <alex@alexcrichton.com>
Wed, 28 Feb 2018 21:57:16 +0000 (13:57 -0800)
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
src/cargo/core/manifest.rs
src/cargo/ops/cargo_package.rs
src/cargo/util/toml/mod.rs
tests/testsuite/install.rs
tests/testsuite/package.rs

index 20d60d17da9d2176066b11d28bf8b05804d12321..7583ac40f65a8813d3a7464d2ede2c6d44b6022d 100644 (file)
@@ -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,
     }
 }
 
index 0ebe5f533cd93cb81c50898f9852b9602c4d29f0..557380d6c50e173346b76f8320602031b9fc6480 100644 (file)
@@ -31,6 +31,7 @@ pub struct Manifest {
     metadata: ManifestMetadata,
     profiles: Profiles,
     publish: Option<Vec<String>>,
+    publish_lockfile: bool,
     replace: Vec<(PackageIdSpec, Dependency)>,
     patch: HashMap<Url, Vec<Dependency>>,
     workspace: WorkspaceConfig,
@@ -267,6 +268,7 @@ impl Manifest {
                metadata: ManifestMetadata,
                profiles: Profiles,
                publish: Option<Vec<String>>,
+               publish_lockfile: bool,
                replace: Vec<(PackageIdSpec, Dependency)>,
                patch: HashMap<Url, Vec<Dependency>>,
                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<Vec<String>> { &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<Url, Vec<Dependency>> { &self.patch }
index c53bc477b9aeb69f3829df115ec85070df20ed2c..0ad54e6a704ce69e670eae9092d7b96ff4410952 100644 (file)
@@ -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<Option<FileLock>> {
+    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(())
index 3e1878853a7a1c0512f687da4d49fce5b45ce6c1..a377f77e3265349903055c6be45ad908ad0991c5 100644 (file)
@@ -426,6 +426,8 @@ pub struct TomlProject {
     exclude: Option<Vec<String>>,
     include: Option<Vec<String>>,
     publish: Option<VecStringOrBool>,
+    #[serde(rename = "publish-lockfile")]
+    publish_lockfile: Option<bool>,
     workspace: Option<String>,
     #[serde(rename = "im-a-teapot")]
     im_a_teapot: Option<bool>,
@@ -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,
index 89adafc33f6e06c8bec988b74b022a9b621750bc..0103017501de03c9ea1cbf640891d80dd05a609f 100644 (file)
@@ -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));
+}
index 425e7301911e48d832db807f1a168e86277190e0..f22e1a3ffadb295a095e52c9e6f167989b48a495 100644 (file)
@@ -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")
+            })
+    );
+}