Add support for publish to take a registry that should be used.
authorChris Swindle <christopher.swindle@metaswitch.com>
Tue, 3 Oct 2017 08:36:46 +0000 (09:36 +0100)
committerChris Swindle <christopher.swindle@metaswitch.com>
Tue, 3 Oct 2017 08:36:46 +0000 (09:36 +0100)
src/cargo/core/manifest.rs
src/cargo/core/package.rs
src/cargo/ops/registry.rs
src/cargo/util/toml/mod.rs
tests/publish.rs

index d8c3710ed43074d370fd57ad8ae688789ee31f41..7b52ec5798837c27470299151cd2bb6849f5d025 100644 (file)
@@ -29,7 +29,7 @@ pub struct Manifest {
     include: Vec<String>,
     metadata: ManifestMetadata,
     profiles: Profiles,
-    publish: bool,
+    publish: Option<Vec<String>>,
     replace: Vec<(PackageIdSpec, Dependency)>,
     patch: HashMap<Url, Vec<Dependency>>,
     workspace: WorkspaceConfig,
@@ -239,7 +239,7 @@ impl Manifest {
                links: Option<String>,
                metadata: ManifestMetadata,
                profiles: Profiles,
-               publish: bool,
+               publish: Option<Vec<String>>,
                replace: Vec<(PackageIdSpec, Dependency)>,
                patch: HashMap<Url, Vec<Dependency>>,
                workspace: WorkspaceConfig,
@@ -276,7 +276,7 @@ impl Manifest {
     pub fn version(&self) -> &Version { self.package_id().version() }
     pub fn warnings(&self) -> &[DelayedWarning] { &self.warnings }
     pub fn profiles(&self) -> &Profiles { &self.profiles }
-    pub fn publish(&self) -> bool { self.publish }
+    pub fn publish(&self) -> &Option<Vec<String>> { &self.publish }
     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 dc54497ec114fed84fc532273a5a107817df088a..9fc9e91adb25f02875fb599898cd85993d1f0139 100644 (file)
@@ -94,7 +94,7 @@ impl Package {
     pub fn targets(&self) -> &[Target] { self.manifest.targets() }
     pub fn version(&self) -> &Version { self.package_id().version() }
     pub fn authors(&self) -> &Vec<String> { &self.manifest.metadata().authors }
-    pub fn publish(&self) -> bool { self.manifest.publish() }
+    pub fn publish(&self) -> &Option<Vec<String>> { self.manifest.publish() }
 
     pub fn has_custom_build(&self) -> bool {
         self.targets().iter().any(|t| t.is_custom_build())
index 42ff2f872076205f06e18edbc5d054265d20145b..3499a3e5a7e54462662bd69d021c60598786c3a8 100644 (file)
@@ -41,10 +41,14 @@ pub struct PublishOpts<'cfg> {
 pub fn publish(ws: &Workspace, opts: &PublishOpts) -> CargoResult<()> {
     let pkg = ws.current()?;
 
-    if !pkg.publish() {
-        bail!("some crates cannot be published.\n\
-               `{}` is marked as unpublishable", pkg.name());
+    if let &Some(ref allowed_registries) = pkg.publish() {
+        let index = opts.index.clone();
+        if index.is_none() || !allowed_registries.contains(&index.unwrap()) {
+            bail!("some crates cannot be published.\n\
+                   `{}` is marked as unpublishable", pkg.name());
+        }
     }
+
     if !pkg.manifest().patch().is_empty() {
         bail!("published crates cannot contain [patch] sections");
     }
index c2220ac2d6e7254c219739276b9d474769d8cb3f..d8aaada23bab55e1ccae088ea17e59f3c6ecc413 100644 (file)
@@ -380,6 +380,44 @@ impl<'de> de::Deserialize<'de> for StringOrBool {
     }
 }
 
+#[derive(Clone, Debug, Serialize)]
+#[serde(untagged)]
+pub enum VecStringOrBool {
+    VecString(Vec<String>),
+    Bool(bool),
+}
+
+impl<'de> de::Deserialize<'de> for VecStringOrBool {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+        where D: de::Deserializer<'de>
+    {
+        struct Visitor;
+
+        impl<'de> de::Visitor<'de> for Visitor {
+            type Value = VecStringOrBool;
+
+            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+                formatter.write_str("a boolean or vector of strings")
+            }
+
+            fn visit_seq<V>(self, v: V) -> Result<Self::Value, V::Error>
+                where V: de::SeqAccess<'de>
+            {
+                let seq = de::value::SeqAccessDeserializer::new(v);
+                Vec::deserialize(seq).map(VecStringOrBool::VecString)
+            }
+
+            fn visit_bool<E>(self, b: bool) -> Result<Self::Value, E>
+                where E: de::Error,
+            {
+                Ok(VecStringOrBool::Bool(b))
+            }
+        }
+
+        deserializer.deserialize_any(Visitor)
+    }
+}
+
 #[derive(Deserialize, Serialize, Clone, Debug)]
 pub struct TomlProject {
     name: String,
@@ -389,7 +427,7 @@ pub struct TomlProject {
     links: Option<String>,
     exclude: Option<Vec<String>>,
     include: Option<Vec<String>>,
-    publish: Option<bool>,
+    publish: Option<VecStringOrBool>,
     workspace: Option<String>,
     #[serde(rename = "im-a-teapot")]
     im_a_teapot: Option<bool>,
@@ -648,7 +686,11 @@ impl TomlManifest {
             }
         };
         let profiles = build_profiles(&me.profile);
-        let publish = project.publish.unwrap_or(true);
+        let publish = match project.publish {
+            Some(VecStringOrBool::VecString(ref vecstring)) => Some(vecstring.clone()),
+            Some(VecStringOrBool::Bool(false)) => Some(vec![]),
+            _ => None,
+        };
         let empty = Vec::new();
         let cargo_features = me.cargo_features.as_ref().unwrap_or(&empty);
         let features = Features::new(cargo_features, &mut warnings)?;
index ea4986195173cf8d7952018df7a401236cee12ff..d38dcaed1fbfc0d60b05bc4b3a0a7fe97aa91aff 100644 (file)
@@ -494,3 +494,80 @@ See [..]
     // Ensure the API request wasn't actually made
     assert!(!publish::upload_path().join("api/v1/crates/new").exists());
 }
+
+#[test]
+fn index_not_in_publish_list() {
+    publish::setup();
+
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            [project]
+            name = "foo"
+            version = "0.0.1"
+            authors = []
+            license = "MIT"
+            description = "foo"
+            publish = [
+                "test"
+            ]
+        "#)
+        .file("src/main.rs", "fn main() {}");
+
+    assert_that(p.cargo_process("publish")
+                 .arg("--index").arg(publish::registry().to_string()),
+                execs().with_status(101).with_stderr("\
+[ERROR] some crates cannot be published.
+`foo` is marked as unpublishable
+"));
+}
+
+#[test]
+fn publish_empty_list() {
+    publish::setup();
+
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            [project]
+            name = "foo"
+            version = "0.0.1"
+            authors = []
+            license = "MIT"
+            description = "foo"
+            publish = []
+        "#)
+        .file("src/main.rs", "fn main() {}");
+
+    assert_that(p.cargo_process("publish")
+                 .arg("--index").arg(publish::registry().to_string()),
+                execs().with_status(101).with_stderr("\
+[ERROR] some crates cannot be published.
+`foo` is marked as unpublishable
+"));
+}
+
+#[test]
+fn publish_allowed_index() {
+    publish::setup();
+
+    let p = project("foo");
+    p.build();
+
+    repo(&paths::root().join("foo"))
+        .file("Cargo.toml", &format!(r#"
+            [project]
+            name = "foo"
+            version = "0.0.1"
+            authors = []
+            license = "MIT"
+            description = "foo"
+            documentation = "foo"
+            homepage = "foo"
+            publish = ["{}"]
+        "#, publish::registry().to_string()))
+        .file("src/main.rs", "fn main() {}")
+        .build();
+
+    assert_that(p.cargo("publish")
+                 .arg("--index").arg(publish::registry().to_string()),
+                execs().with_status(0));
+}