Skip to content

Commit

Permalink
Stricter package change detection.
Browse files Browse the repository at this point in the history
  • Loading branch information
ehuss committed Mar 13, 2019
1 parent dd76122 commit 78a60bc
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 19 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ termcolor = "1.0"
toml = "0.4.2"
url = "1.1"
url_serde = "0.2.0"
walkdir = "2.2"
clap = "2.31.2"
unicode-width = "0.1.5"
openssl = { version = '0.10.11', optional = true }
Expand Down
75 changes: 69 additions & 6 deletions src/cargo/ops/cargo_package.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::collections::HashMap;
use std::fs::{self, File};
use std::io::prelude::*;
use std::io::SeekFrom;
Expand Down Expand Up @@ -439,7 +440,7 @@ fn run_verify(ws: &Workspace<'_>, tar: &FileLock, opts: &PackageOpts<'_>) -> Car
let id = SourceId::for_path(&dst)?;
let mut src = PathSource::new(&dst, id, ws.config());
let new_pkg = src.root_package()?;
let pkg_fingerprint = src.last_modified_file(&new_pkg)?;
let pkg_fingerprint = hash_all(&dst)?;
let ws = Workspace::ephemeral(new_pkg, config, None, true)?;

let exec: Arc<dyn Executor> = Arc::new(DefaultExecutor);
Expand All @@ -465,21 +466,83 @@ fn run_verify(ws: &Workspace<'_>, tar: &FileLock, opts: &PackageOpts<'_>) -> Car
)?;

// Check that `build.rs` didn't modify any files in the `src` directory.
let ws_fingerprint = src.last_modified_file(ws.current()?)?;
let ws_fingerprint = hash_all(&dst)?;
if pkg_fingerprint != ws_fingerprint {
let (_, path) = ws_fingerprint;
let changes = report_hash_difference(&pkg_fingerprint, &ws_fingerprint);
failure::bail!(
"Source directory was modified by build.rs during cargo publish. \
Build scripts should not modify anything outside of OUT_DIR. \
Modified file: {}\n\n\
Build scripts should not modify anything outside of OUT_DIR.\n\
{}\n\n\
To proceed despite this, pass the `--no-verify` flag.",
path.display()
changes
)
}

Ok(())
}

fn hash_all(path: &Path) -> CargoResult<HashMap<PathBuf, u64>> {
fn wrap(path: &Path) -> CargoResult<HashMap<PathBuf, u64>> {
let mut result = HashMap::new();
let walker = walkdir::WalkDir::new(path).into_iter();
for entry in walker.filter_entry(|e| {
!(e.depth() == 1 && (e.file_name() == "target" || e.file_name() == "Cargo.lock"))
}) {
let entry = entry?;
let file_type = entry.file_type();
if file_type.is_file() {
let contents = fs::read(entry.path())?;
let hash = util::hex::hash_u64(&contents);
result.insert(entry.path().to_path_buf(), hash);
} else if file_type.is_symlink() {
let hash = util::hex::hash_u64(&fs::read_link(entry.path())?);
result.insert(entry.path().to_path_buf(), hash);
}
}
Ok(result)
}
let result = wrap(path).chain_err(|| format!("failed to verify output at {:?}", path))?;
Ok(result)
}

fn report_hash_difference(
orig: &HashMap<PathBuf, u64>,
after: &HashMap<PathBuf, u64>,
) -> String {
let mut changed = Vec::new();
let mut removed = Vec::new();
for (key, value) in orig {
match after.get(key) {
Some(after_value) => {
if value != after_value {
changed.push(key.to_string_lossy());
}
}
None => removed.push(key.to_string_lossy()),
}
}
let mut added: Vec<_> = after
.keys()
.filter(|key| !orig.contains_key(*key))
.map(|key| key.to_string_lossy())
.collect();
let mut result = Vec::new();
if !changed.is_empty() {
changed.sort_unstable();
result.push(format!("Changed: {}", changed.join("\n\t")));
}
if !added.is_empty() {
added.sort_unstable();
result.push(format!("Added: {}", added.join("\n\t")));
}
if !removed.is_empty() {
removed.sort_unstable();
result.push(format!("Removed: {}", removed.join("\n\t")));
}
assert!(!result.is_empty(), "unexpected empty change detection");
result.join("\n")
}

// It can often be the case that files of a particular name on one platform
// can't actually be created on another platform. For example files with colons
// in the name are allowed on Unix but not on Windows.
Expand Down
26 changes: 13 additions & 13 deletions tests/testsuite/package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ use std::fs::File;
use std::io::prelude::*;
use std::path::Path;

use crate::support::cargo_process;
use crate::support::registry::Package;
use crate::support::{
basic_manifest, git, is_nightly, path2url, paths, project, publish::validate_crate_contents,
registry,
};
use crate::support::{cargo_process, sleep_ms};
use git2;

#[test]
Expand Down Expand Up @@ -1201,27 +1201,24 @@ fn lock_file_and_workspace() {
fn do_not_package_if_src_was_modified() {
let p = project()
.file("src/main.rs", r#"fn main() { println!("hello"); }"#)
.file("foo.txt", "")
.file("bar.txt", "")
.file(
"build.rs",
r#"
use std::fs::File;
use std::io::Write;
use std::fs;
fn main() {
let mut file = File::create("src/generated.txt").expect("failed to create file");
file.write_all(b"Hello, world of generated files.").expect("failed to write");
fs::write("src/generated.txt",
"Hello, world of generated files."
).expect("failed to create file");
fs::remove_file("foo.txt").expect("failed to remove");
fs::write("bar.txt", "updated content").expect("failed to update");
}
"#,
)
.build();

if cfg!(target_os = "macos") {
// MacOS has 1s resolution filesystem.
// If src/main.rs is created within 1s of src/generated.txt, then it
// won't trigger the modification check.
sleep_ms(1000);
}

p.cargo("package")
.with_status(101)
.with_stderr_contains(
Expand All @@ -1230,7 +1227,10 @@ error: failed to verify package tarball
Caused by:
Source directory was modified by build.rs during cargo publish. \
Build scripts should not modify anything outside of OUT_DIR. Modified file: [..]src/generated.txt
Build scripts should not modify anything outside of OUT_DIR.
Changed: [CWD]/target/package/foo-0.0.1/bar.txt
Added: [CWD]/target/package/foo-0.0.1/src/generated.txt
Removed: [CWD]/target/package/foo-0.0.1/foo.txt
To proceed despite this, pass the `--no-verify` flag.",
)
Expand Down

0 comments on commit 78a60bc

Please sign in to comment.