Skip to content

Commit 78a60bc

Browse files
committed
Stricter package change detection.
1 parent dd76122 commit 78a60bc

File tree

3 files changed

+83
-19
lines changed

3 files changed

+83
-19
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ termcolor = "1.0"
5959
toml = "0.4.2"
6060
url = "1.1"
6161
url_serde = "0.2.0"
62+
walkdir = "2.2"
6263
clap = "2.31.2"
6364
unicode-width = "0.1.5"
6465
openssl = { version = '0.10.11', optional = true }

src/cargo/ops/cargo_package.rs

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::collections::HashMap;
12
use std::fs::{self, File};
23
use std::io::prelude::*;
34
use std::io::SeekFrom;
@@ -439,7 +440,7 @@ fn run_verify(ws: &Workspace<'_>, tar: &FileLock, opts: &PackageOpts<'_>) -> Car
439440
let id = SourceId::for_path(&dst)?;
440441
let mut src = PathSource::new(&dst, id, ws.config());
441442
let new_pkg = src.root_package()?;
442-
let pkg_fingerprint = src.last_modified_file(&new_pkg)?;
443+
let pkg_fingerprint = hash_all(&dst)?;
443444
let ws = Workspace::ephemeral(new_pkg, config, None, true)?;
444445

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

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

480481
Ok(())
481482
}
482483

484+
fn hash_all(path: &Path) -> CargoResult<HashMap<PathBuf, u64>> {
485+
fn wrap(path: &Path) -> CargoResult<HashMap<PathBuf, u64>> {
486+
let mut result = HashMap::new();
487+
let walker = walkdir::WalkDir::new(path).into_iter();
488+
for entry in walker.filter_entry(|e| {
489+
!(e.depth() == 1 && (e.file_name() == "target" || e.file_name() == "Cargo.lock"))
490+
}) {
491+
let entry = entry?;
492+
let file_type = entry.file_type();
493+
if file_type.is_file() {
494+
let contents = fs::read(entry.path())?;
495+
let hash = util::hex::hash_u64(&contents);
496+
result.insert(entry.path().to_path_buf(), hash);
497+
} else if file_type.is_symlink() {
498+
let hash = util::hex::hash_u64(&fs::read_link(entry.path())?);
499+
result.insert(entry.path().to_path_buf(), hash);
500+
}
501+
}
502+
Ok(result)
503+
}
504+
let result = wrap(path).chain_err(|| format!("failed to verify output at {:?}", path))?;
505+
Ok(result)
506+
}
507+
508+
fn report_hash_difference(
509+
orig: &HashMap<PathBuf, u64>,
510+
after: &HashMap<PathBuf, u64>,
511+
) -> String {
512+
let mut changed = Vec::new();
513+
let mut removed = Vec::new();
514+
for (key, value) in orig {
515+
match after.get(key) {
516+
Some(after_value) => {
517+
if value != after_value {
518+
changed.push(key.to_string_lossy());
519+
}
520+
}
521+
None => removed.push(key.to_string_lossy()),
522+
}
523+
}
524+
let mut added: Vec<_> = after
525+
.keys()
526+
.filter(|key| !orig.contains_key(*key))
527+
.map(|key| key.to_string_lossy())
528+
.collect();
529+
let mut result = Vec::new();
530+
if !changed.is_empty() {
531+
changed.sort_unstable();
532+
result.push(format!("Changed: {}", changed.join("\n\t")));
533+
}
534+
if !added.is_empty() {
535+
added.sort_unstable();
536+
result.push(format!("Added: {}", added.join("\n\t")));
537+
}
538+
if !removed.is_empty() {
539+
removed.sort_unstable();
540+
result.push(format!("Removed: {}", removed.join("\n\t")));
541+
}
542+
assert!(!result.is_empty(), "unexpected empty change detection");
543+
result.join("\n")
544+
}
545+
483546
// It can often be the case that files of a particular name on one platform
484547
// can't actually be created on another platform. For example files with colons
485548
// in the name are allowed on Unix but not on Windows.

tests/testsuite/package.rs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ use std::fs::File;
33
use std::io::prelude::*;
44
use std::path::Path;
55

6+
use crate::support::cargo_process;
67
use crate::support::registry::Package;
78
use crate::support::{
89
basic_manifest, git, is_nightly, path2url, paths, project, publish::validate_crate_contents,
910
registry,
1011
};
11-
use crate::support::{cargo_process, sleep_ms};
1212
use git2;
1313

1414
#[test]
@@ -1201,27 +1201,24 @@ fn lock_file_and_workspace() {
12011201
fn do_not_package_if_src_was_modified() {
12021202
let p = project()
12031203
.file("src/main.rs", r#"fn main() { println!("hello"); }"#)
1204+
.file("foo.txt", "")
1205+
.file("bar.txt", "")
12041206
.file(
12051207
"build.rs",
12061208
r#"
1207-
use std::fs::File;
1208-
use std::io::Write;
1209+
use std::fs;
12091210
12101211
fn main() {
1211-
let mut file = File::create("src/generated.txt").expect("failed to create file");
1212-
file.write_all(b"Hello, world of generated files.").expect("failed to write");
1212+
fs::write("src/generated.txt",
1213+
"Hello, world of generated files."
1214+
).expect("failed to create file");
1215+
fs::remove_file("foo.txt").expect("failed to remove");
1216+
fs::write("bar.txt", "updated content").expect("failed to update");
12131217
}
12141218
"#,
12151219
)
12161220
.build();
12171221

1218-
if cfg!(target_os = "macos") {
1219-
// MacOS has 1s resolution filesystem.
1220-
// If src/main.rs is created within 1s of src/generated.txt, then it
1221-
// won't trigger the modification check.
1222-
sleep_ms(1000);
1223-
}
1224-
12251222
p.cargo("package")
12261223
.with_status(101)
12271224
.with_stderr_contains(
@@ -1230,7 +1227,10 @@ error: failed to verify package tarball
12301227
12311228
Caused by:
12321229
Source directory was modified by build.rs during cargo publish. \
1233-
Build scripts should not modify anything outside of OUT_DIR. Modified file: [..]src/generated.txt
1230+
Build scripts should not modify anything outside of OUT_DIR.
1231+
Changed: [CWD]/target/package/foo-0.0.1/bar.txt
1232+
Added: [CWD]/target/package/foo-0.0.1/src/generated.txt
1233+
Removed: [CWD]/target/package/foo-0.0.1/foo.txt
12341234
12351235
To proceed despite this, pass the `--no-verify` flag.",
12361236
)

0 commit comments

Comments
 (0)