From 57db8bdef5e84f88045711d7e5b483f4feb1da4c Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 10 May 2017 22:09:44 -0700 Subject: [PATCH] Rewrite Cargo.toml when packaging crates This commit is an implementation of rewriting TOML manifests when we publish them to the registry. The rationale for doing this is to provide a guarantee that downloaded tarballs from crates.io can be built with `cargo build` (literally). This in turn eases a number of other possible consumers of crates from crates.io * Vendored sources can now be more easily modified/checked as cargo build should work and they're standalone crates that suffice for `path` dependencies * Tools like cargobomb/crater no longer need to edit the manifest and can instead perform regression testing on the literal tarballs they download * Other systems such as packaging Rust code may be able to take advantage of this, but this is a less clear benefit. Overall I'm hesitatnt about this, unfortunately. This is a silent translation happening on *publish*, a rare operation, that's difficult to inspect before it flies up to crates.io. I wrote a script to run this transformation over all crates.io crates and found a surprisingly large number of discrepancies. The transformation basically just downloaded all crates at all versions, regenerated the manifest, and then tested if the two manifests were (in memory) the same. Unfortunately historical Cargo had a critical bug which I think made this exercise not too useful. Cargo used to *not* recreate tarballs if one already existed, which I believe led to situations such as: 1. `cargo publish` 2. Cargo generates an error about a dependency. This could be that there's a `version` not present in a `path` dependency, there could be a `git` dependency, etc. 3. Errors are fixed. 4. `cargo publish` 5. Publish is successful In step 4 above historical Cargo *would not recreate the tarball*. This means that the contents of the index (what was published) aren't guaranteed to match with the tarball's `Cargo.toml`. When building from crates.io this is ok as the index is the source of truth for dependency information, but it means that *any* transformation to rewrite Cargo.toml is impossible to verify against all crates on crates.io (due to historical bugs like these). I strove to read as many errors as possible regardless, attempting to suss out bugs in the implementation here. To further guard against surprises I've updated the verification step of packaging to work "normally" in these sense that it's not rewriting dependencies itself or changing summaries. I'm hoping that this serves as a good last-ditch effort that what we're about to publish will indeed build as expected when uploaded to crates.io Overall I'm probably 70% confident in this change. I think it's necessary to make progress, but I think there are going to be very painful bugs that arise from this feature. I'm open to ideas to help weed out these bugs ahead of time! I've done what I can but I fear it may not be entirely enough. Closes #4027 --- src/cargo/core/manifest.rs | 10 +- src/cargo/core/package.rs | 23 +++- src/cargo/core/workspace.rs | 4 +- src/cargo/ops/cargo_package.rs | 72 +++++----- src/cargo/ops/cargo_rustc/context.rs | 2 +- src/cargo/util/toml.rs | 195 +++++++++++++++++++++------ tests/package.rs | 128 +++++++++++++++++- tests/publish.rs | 1 + 8 files changed, 351 insertions(+), 84 deletions(-) diff --git a/src/cargo/core/manifest.rs b/src/cargo/core/manifest.rs index e0b8d36016d..14087106492 100644 --- a/src/cargo/core/manifest.rs +++ b/src/cargo/core/manifest.rs @@ -1,12 +1,14 @@ use std::collections::HashMap; use std::fmt; use std::path::{PathBuf, Path}; +use std::rc::Rc; use semver::Version; use serde::ser; use core::{Dependency, PackageId, Summary, SourceId, PackageIdSpec}; use core::WorkspaceConfig; +use util::toml::TomlManifest; pub enum EitherManifest { Real(Manifest), @@ -14,7 +16,7 @@ pub enum EitherManifest { } /// Contains all the information about a package, as loaded from a Cargo.toml. -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct Manifest { summary: Summary, targets: Vec, @@ -27,6 +29,7 @@ pub struct Manifest { publish: bool, replace: Vec<(PackageIdSpec, Dependency)>, workspace: WorkspaceConfig, + original: Rc, } #[derive(Clone, Debug)] @@ -222,7 +225,8 @@ impl Manifest { profiles: Profiles, publish: bool, replace: Vec<(PackageIdSpec, Dependency)>, - workspace: WorkspaceConfig) -> Manifest { + workspace: WorkspaceConfig, + original: Rc) -> Manifest { Manifest { summary: summary, targets: targets, @@ -235,6 +239,7 @@ impl Manifest { publish: publish, replace: replace, workspace: workspace, + original: original, } } @@ -251,6 +256,7 @@ impl Manifest { pub fn profiles(&self) -> &Profiles { &self.profiles } pub fn publish(&self) -> bool { self.publish } pub fn replace(&self) -> &[(PackageIdSpec, Dependency)] { &self.replace } + pub fn original(&self) -> &TomlManifest { &self.original } pub fn links(&self) -> Option<&str> { self.links.as_ref().map(|s| &s[..]) } diff --git a/src/cargo/core/package.rs b/src/cargo/core/package.rs index 898745460f9..943f570d335 100644 --- a/src/cargo/core/package.rs +++ b/src/cargo/core/package.rs @@ -6,6 +6,7 @@ use std::path::{Path, PathBuf}; use semver::Version; use serde::ser; +use toml; use core::{Dependency, Manifest, PackageId, SourceId, Target}; use core::{Summary, SourceMap}; @@ -16,7 +17,7 @@ use util::{CargoResult, Config, LazyCell, ChainError, internal, human, lev_dista /// /// A package is a `Cargo.toml` file plus all the files that are part of it. // TODO: Is manifest_path a relic? -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct Package { // The package's manifest manifest: Manifest, @@ -117,6 +118,26 @@ impl Package { manifest_path: self.manifest_path, } } + + pub fn to_registry_toml(&self) -> String { + let manifest = self.manifest().original().prepare_for_publish(); + let toml = toml::to_string(&manifest).unwrap(); + format!("\ + # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO\n\ + #\n\ + # When uploading crates to the registry Cargo will automatically\n\ + # \"normalize\" Cargo.toml files for maximal compatibility\n\ + # with all versions of Cargo and also rewrite `path` dependencies\n\ + # to registry (e.g. crates.io) dependencies\n\ + #\n\ + # If you believe there's an error in this file please file an\n\ + # issue against the rust-lang/cargo repository. If you're\n\ + # editing this file be aware that the upstream Cargo.toml\n\ + # will likely look very different (and much more reasonable)\n\ + \n\ + {}\ + ", toml) + } } impl fmt::Display for Package { diff --git a/src/cargo/core/workspace.rs b/src/cargo/core/workspace.rs index 5015448a569..0607e828bcd 100644 --- a/src/cargo/core/workspace.rs +++ b/src/cargo/core/workspace.rs @@ -124,7 +124,9 @@ impl<'cfg> Workspace<'cfg> { /// /// This is currently only used in niche situations like `cargo install` or /// `cargo package`. - pub fn ephemeral(package: Package, config: &'cfg Config, target_dir: Option, + pub fn ephemeral(package: Package, + config: &'cfg Config, + target_dir: Option, require_optional_deps: bool) -> CargoResult> { let mut ws = Workspace { config: config, diff --git a/src/cargo/ops/cargo_package.rs b/src/cargo/ops/cargo_package.rs index 107a4f2a554..de788bab5b6 100644 --- a/src/cargo/ops/cargo_package.rs +++ b/src/cargo/ops/cargo_package.rs @@ -7,9 +7,9 @@ use std::sync::Arc; use flate2::read::GzDecoder; use flate2::{GzBuilder, Compression}; use git2; -use tar::{Archive, Builder, Header}; +use tar::{Archive, Builder, Header, EntryType}; -use core::{SourceId, Package, PackageId, Workspace, Source}; +use core::{Package, Workspace, Source, SourceId}; use sources::PathSource; use util::{self, CargoResult, human, internal, ChainError, Config, FileLock}; use ops::{self, DefaultExecutor}; @@ -202,9 +202,6 @@ fn tar(ws: &Workspace, human(format!("non-utf8 path in source directory: {}", relative.display())) })?; - let mut file = File::open(file).chain_error(|| { - human(format!("failed to open for archiving: `{}`", file.display())) - })?; config.shell().verbose(|shell| { shell.status("Archiving", &relative) })?; @@ -230,18 +227,41 @@ fn tar(ws: &Workspace, // unpack the selectors 0.4.0 crate on crates.io. Either that or take a // look at rust-lang/cargo#2326 let mut header = Header::new_ustar(); - let metadata = file.metadata().chain_error(|| { - human(format!("could not learn metadata for: `{}`", relative)) - })?; header.set_path(&path).chain_error(|| { human(format!("failed to add to archive: `{}`", relative)) })?; + let mut file = File::open(file).chain_error(|| { + human(format!("failed to open for archiving: `{}`", file.display())) + })?; + let metadata = file.metadata().chain_error(|| { + human(format!("could not learn metadata for: `{}`", relative)) + })?; header.set_metadata(&metadata); - header.set_cksum(); - ar.append(&header, &mut file).chain_error(|| { - internal(format!("could not archive source file `{}`", relative)) - })?; + if relative == "Cargo.toml" { + let orig = Path::new(&path).with_file_name("Cargo.toml.orig"); + header.set_path(&orig)?; + header.set_cksum(); + ar.append(&header, &mut file).chain_error(|| { + internal(format!("could not archive source file `{}`", relative)) + })?; + + let mut header = Header::new_ustar(); + let toml = pkg.to_registry_toml(); + 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_error(|| { + internal(format!("could not archive source file `{}`", relative)) + })?; + } else { + header.set_cksum(); + ar.append(&header, &mut file).chain_error(|| { + internal(format!("could not archive source file `{}`", relative)) + })?; + } } let encoder = ar.into_inner()?; encoder.finish()?; @@ -262,30 +282,14 @@ fn run_verify(ws: &Workspace, tar: &File, opts: &PackageOpts) -> CargoResult<()> } let mut archive = Archive::new(f); archive.unpack(dst.parent().unwrap())?; - let manifest_path = dst.join("Cargo.toml"); - // When packages are uploaded to a registry, all path dependencies are - // implicitly converted to registry dependencies, so we rewrite those - // dependencies here. - // - // We also make sure to point all paths at `dst` instead of the previous - // location that the package was originally read from. In locking the - // `SourceId` we're telling it that the corresponding `PathSource` will be - // considered updated and we won't actually read any packages. - let cratesio = SourceId::crates_io(config)?; - let precise = Some("locked".to_string()); - let new_src = SourceId::for_path(&dst)?.with_precise(precise); - let new_pkgid = PackageId::new(pkg.name(), pkg.version(), &new_src)?; - let new_summary = pkg.summary().clone().map_dependencies(|d| { - if !d.source_id().is_path() { return d } - d.clone_inner().set_source_id(cratesio.clone()).into_dependency() - }); - let mut new_manifest = pkg.manifest().clone(); - new_manifest.set_summary(new_summary.override_id(new_pkgid)); - let new_pkg = Package::new(new_manifest, &manifest_path); - - // Now that we've rewritten all our path dependencies, compile it! + // Manufacture an ephemeral workspace to ensure that even if the top-level + // package has a workspace we can still build our new crate. + let id = SourceId::for_path(&dst)?; + let mut src = PathSource::new(&dst, &id, ws.config()); + let new_pkg = src.root_package()?; let ws = Workspace::ephemeral(new_pkg, config, None, true)?; + ops::compile_ws(&ws, None, &ops::CompileOptions { config: config, jobs: opts.jobs, diff --git a/src/cargo/ops/cargo_rustc/context.rs b/src/cargo/ops/cargo_rustc/context.rs index e17a0823b6c..34d9c8cc9b6 100644 --- a/src/cargo/ops/cargo_rustc/context.rs +++ b/src/cargo/ops/cargo_rustc/context.rs @@ -20,7 +20,7 @@ use super::layout::Layout; use super::links::Links; use super::{Kind, Compilation, BuildConfig}; -#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)] +#[derive(Clone, Copy, Eq, PartialEq, Hash)] pub struct Unit<'a> { pub pkg: &'a Package, pub target: &'a Target, diff --git a/src/cargo/util/toml.rs b/src/cargo/util/toml.rs index 771f48ddfda..254d0328017 100644 --- a/src/cargo/util/toml.rs +++ b/src/cargo/util/toml.rs @@ -1,12 +1,13 @@ use std::collections::{HashMap, HashSet, BTreeSet}; -use std::default::Default; use std::fmt; use std::fs; use std::path::{Path, PathBuf}; +use std::rc::Rc; use std::str; use toml; use semver::{self, VersionReq}; +use serde::ser; use serde::de::{self, Deserialize}; use serde_ignored; @@ -112,7 +113,11 @@ pub fn to_manifest(contents: &str, } })?; - return match manifest.to_real_manifest(source_id, &layout, config) { + let manifest = Rc::new(manifest); + return match TomlManifest::to_real_manifest(&manifest, + source_id, + &layout, + config) { Ok((mut manifest, paths)) => { for key in unused { manifest.add_warning(format!("unused manifest key: {}", key)); @@ -125,7 +130,10 @@ pub fn to_manifest(contents: &str, Ok((EitherManifest::Real(manifest), paths)) } Err(e) => { - match manifest.to_virtual_manifest(source_id, &layout, config) { + match TomlManifest::to_virtual_manifest(&manifest, + source_id, + &layout, + config) { Ok((m, paths)) => Ok((EitherManifest::Virtual(m), paths)), Err(..) => Err(e), } @@ -192,6 +200,8 @@ type TomlExampleTarget = TomlTarget; type TomlTestTarget = TomlTarget; type TomlBenchTarget = TomlTarget; +#[derive(Serialize)] +#[serde(untagged)] pub enum TomlDependency { Simple(String), Detailed(DetailedTomlDependency) @@ -229,7 +239,7 @@ impl<'de> de::Deserialize<'de> for TomlDependency { } } -#[derive(Deserialize, Clone, Default)] +#[derive(Deserialize, Serialize, Clone, Default)] pub struct DetailedTomlDependency { version: Option, path: Option, @@ -245,7 +255,7 @@ pub struct DetailedTomlDependency { default_features2: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, Serialize)] pub struct TomlManifest { package: Option>, project: Option>, @@ -271,7 +281,7 @@ pub struct TomlManifest { badges: Option>>, } -#[derive(Deserialize, Clone, Default)] +#[derive(Deserialize, Serialize, Clone, Default)] pub struct TomlProfiles { test: Option, doc: Option, @@ -318,7 +328,19 @@ impl<'de> de::Deserialize<'de> for TomlOptLevel { } } -#[derive(Clone)] +impl ser::Serialize for TomlOptLevel { + fn serialize(&self, serializer: S) -> Result + where S: ser::Serializer, + { + match self.0.parse::() { + Ok(n) => n.serialize(serializer), + Err(_) => self.0.serialize(serializer), + } + } +} + +#[derive(Clone, Serialize)] +#[serde(untagged)] pub enum U32OrBool { U32(u32), Bool(bool), @@ -360,7 +382,7 @@ impl<'de> de::Deserialize<'de> for U32OrBool { } } -#[derive(Deserialize, Clone, Default)] +#[derive(Deserialize, Serialize, Clone, Default)] pub struct TomlProfile { #[serde(rename = "opt-level")] opt_level: Option, @@ -376,7 +398,8 @@ pub struct TomlProfile { overflow_checks: Option, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize)] +#[serde(untagged)] pub enum StringOrBool { String(String), Bool(bool), @@ -412,7 +435,7 @@ impl<'de> de::Deserialize<'de> for StringOrBool { } } -#[derive(Deserialize)] +#[derive(Deserialize, Serialize, Clone)] pub struct TomlProject { name: String, version: TomlVersion, @@ -437,12 +460,13 @@ pub struct TomlProject { repository: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, Serialize)] pub struct TomlWorkspace { members: Option>, exclude: Option>, } +#[derive(Clone)] pub struct TomlVersion { version: semver::Version, } @@ -474,6 +498,15 @@ impl<'de> de::Deserialize<'de> for TomlVersion { } } +impl ser::Serialize for TomlVersion { + fn serialize(&self, s: S) -> Result + where S: ser::Serializer, + { + self.version.to_string().serialize(s) + } +} + + impl TomlProject { pub fn to_package_id(&self, source_id: &SourceId) -> CargoResult { PackageId::new(&self.name, self.version.version.clone(), @@ -564,7 +597,72 @@ fn inferred_bench_targets(layout: &Layout) -> Vec { } impl TomlManifest { - fn to_real_manifest(&self, + pub fn prepare_for_publish(&self) -> TomlManifest { + let mut package = self.package.as_ref() + .or(self.project.as_ref()) + .unwrap() + .clone(); + package.workspace = None; + return TomlManifest { + package: Some(package), + project: None, + profile: self.profile.clone(), + lib: self.lib.clone(), + bin: self.bin.clone(), + example: self.example.clone(), + test: self.test.clone(), + bench: self.bench.clone(), + dependencies: map_deps(self.dependencies.as_ref()), + dev_dependencies: map_deps(self.dev_dependencies.as_ref() + .or(self.dev_dependencies2.as_ref())), + dev_dependencies2: None, + build_dependencies: map_deps(self.build_dependencies.as_ref() + .or(self.build_dependencies2.as_ref())), + build_dependencies2: None, + features: self.features.clone(), + target: self.target.as_ref().map(|target_map| { + target_map.iter().map(|(k, v)| { + (k.clone(), TomlPlatform { + dependencies: map_deps(v.dependencies.as_ref()), + dev_dependencies: map_deps(v.dev_dependencies.as_ref() + .or(v.dev_dependencies2.as_ref())), + dev_dependencies2: None, + build_dependencies: map_deps(v.build_dependencies.as_ref() + .or(v.build_dependencies2.as_ref())), + build_dependencies2: None, + }) + }).collect() + }), + replace: None, + workspace: None, + badges: self.badges.clone(), + }; + + fn map_deps(deps: Option<&HashMap>) + -> Option> + { + let deps = match deps { + Some(deps) => deps, + None => return None + }; + Some(deps.iter().map(|(k, v)| (k.clone(), map_dependency(v))).collect()) + } + + fn map_dependency(dep: &TomlDependency) -> TomlDependency { + match *dep { + TomlDependency::Detailed(ref d) => { + let mut d = d.clone(); + d.path.take(); // path dependencies become crates.io deps + TomlDependency::Detailed(d) + } + TomlDependency::Simple(ref s) => { + TomlDependency::Simple(s.clone()) + } + } + } + } + + fn to_real_manifest(me: &Rc, source_id: &SourceId, layout: &Layout, config: &Config) @@ -572,7 +670,7 @@ impl TomlManifest { let mut nested_paths = vec![]; let mut warnings = vec![]; - let project = self.project.as_ref().or_else(|| self.package.as_ref()); + let project = me.project.as_ref().or_else(|| me.package.as_ref()); let project = project.chain_error(|| { human("no `package` or `project` section found.") })?; @@ -587,7 +685,7 @@ impl TomlManifest { // If we have a lib with a path, we're done // If we have a lib with no path, use the inferred lib or_else package name - let lib = match self.lib { + let lib = match me.lib { Some(ref lib) => { lib.validate_library_name()?; lib.validate_crate_type()?; @@ -604,7 +702,7 @@ impl TomlManifest { None => inferred_lib_target(&project.name, layout), }; - let bins = match self.bin { + let bins = match me.bin { Some(ref bins) => { for target in bins { target.validate_binary_name()?; @@ -621,7 +719,7 @@ impl TomlManifest { } } - let examples = match self.example { + let examples = match me.example { Some(ref examples) => { for target in examples { target.validate_example_name()?; @@ -631,7 +729,7 @@ impl TomlManifest { None => inferred_example_targets(layout) }; - let tests = match self.test { + let tests = match me.test { Some(ref tests) => { for target in tests { target.validate_test_name()?; @@ -641,7 +739,7 @@ impl TomlManifest { None => inferred_test_targets(layout) }; - let benches = match self.bench { + let benches = match me.bench { Some(ref benches) => { for target in benches { target.validate_bench_name()?; @@ -672,7 +770,7 @@ impl TomlManifest { } // processing the custom build script - let new_build = self.maybe_custom_build(&project.build, &layout.root); + let new_build = me.maybe_custom_build(&project.build, &layout.root); // Get targets let targets = normalize(&layout.root, @@ -727,16 +825,16 @@ impl TomlManifest { } // Collect the deps - process_dependencies(&mut cx, self.dependencies.as_ref(), + process_dependencies(&mut cx, me.dependencies.as_ref(), None)?; - let dev_deps = self.dev_dependencies.as_ref() - .or(self.dev_dependencies2.as_ref()); + let dev_deps = me.dev_dependencies.as_ref() + .or(me.dev_dependencies2.as_ref()); process_dependencies(&mut cx, dev_deps, Some(Kind::Development))?; - let build_deps = self.build_dependencies.as_ref() - .or(self.build_dependencies2.as_ref()); + let build_deps = me.build_dependencies.as_ref() + .or(me.build_dependencies2.as_ref()); process_dependencies(&mut cx, build_deps, Some(Kind::Build))?; - for (name, platform) in self.target.iter().flat_map(|t| t) { + for (name, platform) in me.target.iter().flat_map(|t| t) { cx.platform = Some(name.parse()?); process_dependencies(&mut cx, platform.dependencies.as_ref(), None)?; @@ -748,7 +846,7 @@ impl TomlManifest { process_dependencies(&mut cx, dev_deps, Some(Kind::Development))?; } - replace = self.replace(&mut cx)?; + replace = me.replace(&mut cx)?; } { @@ -767,7 +865,7 @@ impl TomlManifest { let exclude = project.exclude.clone().unwrap_or(Vec::new()); let include = project.include.clone().unwrap_or(Vec::new()); - let summary = Summary::new(pkgid, deps, self.features.clone() + let summary = Summary::new(pkgid, deps, me.features.clone() .unwrap_or_else(HashMap::new))?; let metadata = ManifestMetadata { description: project.description.clone(), @@ -780,10 +878,10 @@ impl TomlManifest { repository: project.repository.clone(), keywords: project.keywords.clone().unwrap_or(Vec::new()), categories: project.categories.clone().unwrap_or(Vec::new()), - badges: self.badges.clone().unwrap_or_else(HashMap::new), + badges: me.badges.clone().unwrap_or_else(HashMap::new), }; - let workspace_config = match (self.workspace.as_ref(), + let workspace_config = match (me.workspace.as_ref(), project.workspace.as_ref()) { (Some(config), None) => { WorkspaceConfig::Root { @@ -799,7 +897,7 @@ impl TomlManifest { `[workspace]`, only one can be specified") } }; - let profiles = build_profiles(&self.profile); + let profiles = build_profiles(&me.profile); let publish = project.publish.unwrap_or(true); let mut manifest = Manifest::new(summary, targets, @@ -810,7 +908,8 @@ impl TomlManifest { profiles, publish, replace, - workspace_config); + workspace_config, + me.clone()); if project.license_file.is_some() && project.license.is_some() { manifest.add_warning("only one of `license` or \ `license-file` is necessary".to_string()); @@ -822,37 +921,37 @@ impl TomlManifest { Ok((manifest, nested_paths)) } - fn to_virtual_manifest(&self, + fn to_virtual_manifest(me: &Rc, source_id: &SourceId, layout: &Layout, config: &Config) -> CargoResult<(VirtualManifest, Vec)> { - if self.project.is_some() { + if me.project.is_some() { bail!("virtual manifests do not define [project]"); } - if self.package.is_some() { + if me.package.is_some() { bail!("virtual manifests do not define [package]"); } - if self.lib.is_some() { + if me.lib.is_some() { bail!("virtual manifests do not specifiy [lib]"); } - if self.bin.is_some() { + if me.bin.is_some() { bail!("virtual manifests do not specifiy [[bin]]"); } - if self.example.is_some() { + if me.example.is_some() { bail!("virtual manifests do not specifiy [[example]]"); } - if self.test.is_some() { + if me.test.is_some() { bail!("virtual manifests do not specifiy [[test]]"); } - if self.bench.is_some() { + if me.bench.is_some() { bail!("virtual manifests do not specifiy [[bench]]"); } let mut nested_paths = Vec::new(); let mut warnings = Vec::new(); let mut deps = Vec::new(); - let replace = self.replace(&mut Context { + let replace = me.replace(&mut Context { pkgid: None, deps: &mut deps, source_id: source_id, @@ -862,8 +961,8 @@ impl TomlManifest { platform: None, layout: layout, })?; - let profiles = build_profiles(&self.profile); - let workspace_config = match self.workspace { + let profiles = build_profiles(&me.profile); + let workspace_config = match me.workspace { Some(ref config) => { WorkspaceConfig::Root { members: config.members.clone(), @@ -1070,7 +1169,7 @@ impl TomlDependency { } } -#[derive(Default, Deserialize, Debug, Clone)] +#[derive(Default, Serialize, Deserialize, Debug, Clone)] struct TomlTarget { name: Option, @@ -1107,8 +1206,16 @@ impl<'de> de::Deserialize<'de> for PathValue { } } +impl ser::Serialize for PathValue { + fn serialize(&self, serializer: S) -> Result + where S: ser::Serializer, + { + self.0.serialize(serializer) + } +} + /// Corresponds to a `target` entry, but `TomlTarget` is already used. -#[derive(Deserialize)] +#[derive(Serialize, Deserialize)] struct TomlPlatform { dependencies: Option>, #[serde(rename = "build-dependencies")] diff --git a/tests/package.rs b/tests/package.rs index cbcf1468385..540c66c57d0 100644 --- a/tests/package.rs +++ b/tests/package.rs @@ -13,7 +13,7 @@ use std::path::{Path, PathBuf}; use cargotest::{cargo_process, process}; use cargotest::support::{project, execs, paths, git, path2url, cargo_exe}; use flate2::read::GzDecoder; -use hamcrest::{assert_that, existing_file, contains}; +use hamcrest::{assert_that, existing_file, contains, equal_to}; use tar::Archive; #[test] @@ -62,6 +62,7 @@ src[/]main.rs 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/src/main.rs", "unexpected filename: {:?}", f.header().path()) } @@ -423,6 +424,7 @@ src[..]main.rs let fname = f.header().path_bytes(); let fname = &*fname; assert!(fname == b"nested-0.0.1/Cargo.toml" || + fname == b"nested-0.0.1/Cargo.toml.orig" || fname == b"nested-0.0.1/src/main.rs", "unexpected filename: {:?}", f.header().path()) } @@ -588,3 +590,127 @@ Cargo.toml to proceed despite this, pass the `--allow-dirty` flag ")); } + +#[test] +fn generated_manifest() { + let p = project("foo") + .file("Cargo.toml", r#" + [project] + name = "foo" + version = "0.0.1" + authors = [] + exclude = ["*.txt"] + license = "MIT" + description = "foo" + + [workspace] + + [dependencies] + bar = { path = "bar", version = "0.1" } + "#) + .file("src/main.rs", "") + .file("bar/Cargo.toml", r#" + [package] + name = "bar" + version = "0.1.0" + authors = [] + "#) + .file("bar/src/lib.rs", ""); + + assert_that(p.cargo_process("package").arg("--no-verify"), + 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).unwrap(); + let mut contents = Vec::new(); + rdr.read_to_end(&mut contents).unwrap(); + let mut ar = Archive::new(&contents[..]); + let mut entry = ar.entries().unwrap() + .map(|f| f.unwrap()) + .find(|e| e.path().unwrap().ends_with("Cargo.toml")) + .unwrap(); + let mut contents = String::new(); + entry.read_to_string(&mut contents).unwrap(); + assert_that(&contents[..], equal_to( +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 believe there's an error in this file please file an +# issue against the rust-lang/cargo repository. If you're +# editing this file be aware that the upstream Cargo.toml +# will likely look very different (and much more reasonable) + +[package] +name = "foo" +version = "0.0.1" +authors = [] +exclude = ["*.txt"] +description = "foo" +license = "MIT" +[dependencies.bar] +version = "0.1" +"#)); +} + +#[test] +fn ignore_workspace_specifier() { + let p = project("foo") + .file("Cargo.toml", r#" + [project] + name = "foo" + version = "0.0.1" + authors = [] + + [workspace] + + [dependencies] + bar = { path = "bar", version = "0.1" } + "#) + .file("src/main.rs", "") + .file("bar/Cargo.toml", r#" + [package] + name = "bar" + version = "0.1.0" + authors = [] + workspace = ".." + "#) + .file("bar/src/lib.rs", ""); + p.build(); + + assert_that(p.cargo("package").arg("--no-verify").cwd(p.root().join("bar")), + execs().with_status(0)); + + let f = File::open(&p.root().join("target/package/bar-0.1.0.crate")).unwrap(); + let mut rdr = GzDecoder::new(f).unwrap(); + let mut contents = Vec::new(); + rdr.read_to_end(&mut contents).unwrap(); + let mut ar = Archive::new(&contents[..]); + let mut entry = ar.entries().unwrap() + .map(|f| f.unwrap()) + .find(|e| e.path().unwrap().ends_with("Cargo.toml")) + .unwrap(); + let mut contents = String::new(); + entry.read_to_string(&mut contents).unwrap(); + assert_that(&contents[..], equal_to( +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 believe there's an error in this file please file an +# issue against the rust-lang/cargo repository. If you're +# editing this file be aware that the upstream Cargo.toml +# will likely look very different (and much more reasonable) + +[package] +name = "bar" +version = "0.1.0" +authors = [] +"#)); +} diff --git a/tests/publish.rs b/tests/publish.rs index 1e4d71400aa..968281f6219 100644 --- a/tests/publish.rs +++ b/tests/publish.rs @@ -88,6 +88,7 @@ See [..] let fname = file.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/src/main.rs", "unexpected filename: {:?}", file.header().path()); }