Skip to content

Include unpublishable path dependencies of the root package for publishing #4735

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/cargo/core/package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,15 +135,16 @@ impl Package {
}

pub fn to_registry_toml(&self) -> CargoResult<String> {
let manifest = self.manifest().original().prepare_for_publish();
let root = self.root();
let manifest = self.manifest().original().prepare_for_publish(root);
let toml = toml::to_string(&manifest)?;
Ok(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\
# with all versions of Cargo and also change publishable `path`\n\
# dependencies 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\
Expand Down
19 changes: 13 additions & 6 deletions src/cargo/ops/cargo_package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::sync::Arc;
use flate2::read::GzDecoder;
use flate2::{GzBuilder, Compression};
use git2;
use same_file::is_same_file;
use tar::{Archive, Builder, Header, EntryType};

use core::{Package, Workspace, Source, SourceId};
Expand Down Expand Up @@ -203,8 +204,9 @@ fn tar(ws: &Workspace,
let pkg = ws.current()?;
let config = ws.config();
let root = pkg.root();
for file in src.list_files(pkg)?.iter() {
let relative = util::without_prefix(file, root).unwrap();
let manifest_path = pkg.manifest_path();
for filepath in src.list_files(pkg)?.iter() {
let relative = util::without_prefix(filepath, root).unwrap();
check_filename(relative)?;
let relative = relative.to_str().ok_or_else(|| {
format!("non-utf8 path in source directory: {}",
Expand Down Expand Up @@ -238,15 +240,15 @@ fn tar(ws: &Workspace,
header.set_path(&path).chain_err(|| {
format!("failed to add to archive: `{}`", relative)
})?;
let mut file = File::open(file).chain_err(|| {
format!("failed to open for archiving: `{}`", file.display())
let mut file = File::open(filepath).chain_err(|| {
format!("failed to open for archiving: `{}`", filepath.display())
})?;
let metadata = file.metadata().chain_err(|| {
format!("could not learn metadata for: `{}`", relative)
})?;
header.set_metadata(&metadata);

if relative == "Cargo.toml" {
if filepath.ends_with("Cargo.toml") {
let orig = Path::new(&path).with_file_name("Cargo.toml.orig");
header.set_path(&orig)?;
header.set_cksum();
Expand All @@ -255,7 +257,12 @@ fn tar(ws: &Workspace,
})?;

let mut header = Header::new_ustar();
let toml = pkg.to_registry_toml()?;
let toml = if is_same_file(manifest_path, filepath)? {
pkg.to_registry_toml()?
} else {
let pkg = Package::for_path(&filepath, config).unwrap();
pkg.to_registry_toml()?
};
header.set_path(&path)?;
header.set_entry_type(EntryType::file());
header.set_mode(0o644);
Expand Down
50 changes: 38 additions & 12 deletions src/cargo/sources/path.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::collections::HashMap;
use std::fmt::{self, Debug, Formatter};
use std::fs;
use std::path::{Path, PathBuf};
Expand All @@ -7,11 +8,13 @@ use git2;
use glob::Pattern;
use ignore::Match;
use ignore::gitignore::GitignoreBuilder;
use same_file::is_same_file;

use core::{Package, PackageId, Summary, SourceId, Source, Dependency, Registry};
use ops;
use util::{self, CargoError, CargoResult, internal};
use util::Config;
use util::toml::is_remote_source;

pub struct PathSource<'cfg> {
source_id: SourceId,
Expand Down Expand Up @@ -188,6 +191,25 @@ impl<'cfg> PathSource<'cfg> {
}
};

// filter for publish-able packages

let mut remote_prefixes = HashMap::<PathBuf, bool>::new();
let mut local_should_package = |path: &Path| -> bool {
for (ref prefix, is_remote) in remote_prefixes.iter() {
if path.starts_with(prefix) {
return !is_remote;
}
}
if let Some(dirpath) = path.parent() {
if !is_same_file(dirpath, root).unwrap_or(false) {
let is_remote = is_remote_source(&dirpath.join("Cargo.toml"));
remote_prefixes.insert(dirpath.to_path_buf(), is_remote);
return !is_remote;
}
}
true
};

// matching to paths

let mut filter = |path: &Path| -> CargoResult<bool> {
Expand Down Expand Up @@ -238,7 +260,7 @@ impl<'cfg> PathSource<'cfg> {
}

// Update to ignore_should_package for Stage 2
Ok(glob_should_package)
Ok(glob_should_package && local_should_package(path))
};

// attempt git-prepopulate only if no `include` (rust-lang/cargo#4135)
Expand Down Expand Up @@ -332,7 +354,7 @@ impl<'cfg> PathSource<'cfg> {
}
});

let mut subpackages_found = Vec::new();
let mut ignored_subpackages_found = Vec::new();

for (file_path, is_dir) in index_files.chain(untracked) {
let file_path = file_path?;
Expand All @@ -351,16 +373,20 @@ impl<'cfg> PathSource<'cfg> {
Some("Cargo.lock") |
Some("target") => continue,

// Keep track of all sub-packages found and also strip out all
// matches we've found so far. Note, though, that if we find
// our own `Cargo.toml` we keep going.
// Keep track of all published sub-packages found and also
// strip out all matches we've found so far. Note, though,
// that if we find our own `Cargo.toml` we keep going.
Some("Cargo.toml") => {
let path = file_path.parent().unwrap();
if path != pkg_path {
if !is_same_file(&path, pkg_path)? {
warn!("subpackage found: {}", path.display());
ret.retain(|p| !p.starts_with(path));
subpackages_found.push(path.to_path_buf());
continue
if is_remote_source(&file_path) {
// if package is allowed to be published,
// then path dependency will become crates.io deps
ret.retain(|p| !p.starts_with(&path));
ignored_subpackages_found.push(path.to_path_buf());
continue
}
}
}

Expand All @@ -369,7 +395,7 @@ impl<'cfg> PathSource<'cfg> {

// If this file is part of any other sub-package we've found so far,
// skip it.
if subpackages_found.iter().any(|p| file_path.starts_with(p)) {
if ignored_subpackages_found.iter().any(|p| file_path.starts_with(p)) {
continue
}

Expand Down Expand Up @@ -433,8 +459,8 @@ impl<'cfg> PathSource<'cfg> {
}
return Ok(())
}
// Don't recurse into any sub-packages that we have
if !is_root && fs::metadata(&path.join("Cargo.toml")).is_ok() {

if !is_root && is_remote_source(&path.join("Cargo.toml")) {
return Ok(())
}

Expand Down
75 changes: 58 additions & 17 deletions src/cargo/util/toml/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,41 @@ in the future.", file.display());
})
}

#[derive(Deserialize)]
struct SourceManifest {
package: Option<SourcePackage>,
project: Option<SourcePackage>,
}

#[derive(Deserialize)]
struct SourcePackage {
publish: Option<VecStringOrBool>,
}

/// Check if the manifest file (if exists) has package.publish=true
///
/// We do not validate the manifest here to avoid dealing with recursion.
pub fn is_remote_source(manifest_path: &Path) -> bool {
use ::std::io::Read;

if let Ok(mut file) = fs::File::open(manifest_path) {
let mut content = String::new();
if file.read_to_string(&mut content).is_ok() {
if let Ok(config) = toml::from_str::<SourceManifest>(&content) {
let pkg = config.package.or(config.project);
if let Some(pkg) = pkg {
return match pkg.publish {
Some(VecStringOrBool::VecString(ref registries)) => !registries.is_empty(),
Some(VecStringOrBool::Bool(false)) => false,
Some(VecStringOrBool::Bool(true)) | None => true,
};
}
}
}
}
false
}

type TomlLibTarget = TomlTarget;
type TomlBinTarget = TomlTarget;
type TomlExampleTarget = TomlTarget;
Expand Down Expand Up @@ -447,7 +482,7 @@ pub struct TomlProject {
metadata: Option<toml::Value>,
}

#[derive(Debug, Deserialize, Serialize)]
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct TomlWorkspace {
members: Option<Vec<String>>,
exclude: Option<Vec<String>>,
Expand All @@ -472,12 +507,11 @@ struct Context<'a, 'b> {
}

impl TomlManifest {
pub fn prepare_for_publish(&self) -> TomlManifest {
let mut package = self.package.as_ref()
pub fn prepare_for_publish(&self, root: &Path) -> TomlManifest {
let package = self.package.as_ref()
.or_else(|| self.project.as_ref())
.unwrap()
.clone();
package.workspace = None;
return TomlManifest {
package: Some(package),
project: None,
Expand All @@ -487,50 +521,57 @@ impl TomlManifest {
example: self.example.clone(),
test: self.test.clone(),
bench: self.bench.clone(),
dependencies: map_deps(self.dependencies.as_ref()),
dependencies: map_deps(self.dependencies.as_ref(), root),
dev_dependencies: map_deps(self.dev_dependencies.as_ref()
.or_else(|| self.dev_dependencies2.as_ref())),
.or_else(|| self.dev_dependencies2.as_ref()), root),
dev_dependencies2: None,
build_dependencies: map_deps(self.build_dependencies.as_ref()
.or_else(|| self.build_dependencies2.as_ref())),
.or_else(|| self.build_dependencies2.as_ref()), root),
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()),
dependencies: map_deps(v.dependencies.as_ref(), root),
dev_dependencies: map_deps(v.dev_dependencies.as_ref()
.or_else(|| v.dev_dependencies2.as_ref())),
.or_else(|| v.dev_dependencies2.as_ref()), root),
dev_dependencies2: None,
build_dependencies: map_deps(v.build_dependencies.as_ref()
.or_else(|| v.build_dependencies2.as_ref())),
.or_else(|| v.build_dependencies2.as_ref()), root),
build_dependencies2: None,
})
}).collect()
}),
replace: None,
patch: None,
workspace: None,
workspace: self.workspace.clone(),
badges: self.badges.clone(),
cargo_features: self.cargo_features.clone(),
};

fn map_deps(deps: Option<&BTreeMap<String, TomlDependency>>)
fn map_deps(deps: Option<&BTreeMap<String, TomlDependency>>, root: &Path)
-> Option<BTreeMap<String, TomlDependency>>
{
let deps = match deps {
Some(deps) => deps,
None => return None
};
Some(deps.iter().map(|(k, v)| (k.clone(), map_dependency(v))).collect())
Some(deps.iter().map(|(k, v)| (k.clone(), map_dependency(v, root))).collect())
}

fn map_dependency(dep: &TomlDependency) -> TomlDependency {
fn map_dependency(dep: &TomlDependency, root: &Path) -> TomlDependency {
match *dep {
TomlDependency::Detailed(ref d) => {
let mut d = d.clone();
d.path.take(); // path dependencies become crates.io deps
TomlDependency::Detailed(d)
let mut dep = d.clone();
if let Some(ref path) = d.path {
let path = root.join(PathBuf::from(path));
if is_remote_source(&path.join("Cargo.toml")) {
// if package is allowed to be published,
// then path dependency will become crates.io deps
dep.path.take();
}
}
TomlDependency::Detailed(dep)
}
TomlDependency::Simple(ref s) => {
TomlDependency::Detailed(DetailedTomlDependency {
Expand Down
Loading