Skip to content

Commit 668a6c6

Browse files
committed
Auto merge of #8864 - bk2204:reproducible-crates, r=alexcrichton
Reproducible crate builds This series introduces reproducible crate builds. Since crates are essentially gzipped tar archives, we canonicalize the fields such that they don't contain extraneous and potentially privacy-leaking data such as user and group names and IDs, device major and minor, and system timestamps. Outside of the timestamps, the user probably did not intend to share information about their user or system, so this also improves developer privacy somewhat. The individual commit messages include copious details about the individual changes involved and the rationale for this change, but roughly, the idea is that by setting the environment variable `SOURCE_DATE_EPOCH`, which is [the preferred way to specify a fixed timestamp by the Reproducible Builds project](https://reproducible-builds.org/docs/source-date-epoch/), we will produce a fully reproducible archive. In any event, we will now produce consistent timestamps throughout the archive and avoid looking up the system time repeatedly. If desired, I could hash the produced crate in the tests, but I feel that would be a little overkill, especially since it's possible that one of our dependencies (e.g., flate2) might change and result in us producing an equivalent but different archive. Since reproducible builds use a consistent toolchain, that's not a problem here. Fixes #8612
2 parents 0d8cab4 + 449ead0 commit 668a6c6

File tree

2 files changed

+38
-9
lines changed

2 files changed

+38
-9
lines changed

src/cargo/ops/cargo_package.rs

+2-9
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@ use std::io::SeekFrom;
55
use std::path::{Path, PathBuf};
66
use std::rc::Rc;
77
use std::sync::Arc;
8-
use std::time::SystemTime;
98

109
use flate2::read::GzDecoder;
1110
use flate2::{Compression, GzBuilder};
1211
use log::debug;
13-
use tar::{Archive, Builder, EntryType, Header};
12+
use tar::{Archive, Builder, EntryType, Header, HeaderMode};
1413

1514
use crate::core::compiler::{BuildConfig, CompileMode, DefaultExecutor, Executor};
1615
use crate::core::{Feature, Shell, Verbosity, Workspace};
@@ -510,7 +509,7 @@ fn tar(
510509
let metadata = file.metadata().chain_err(|| {
511510
format!("could not learn metadata for: `{}`", disk_path.display())
512511
})?;
513-
header.set_metadata(&metadata);
512+
header.set_metadata_in_mode(&metadata, HeaderMode::Deterministic);
514513
header.set_cksum();
515514
ar.append_data(&mut header, &ar_path, &mut file)
516515
.chain_err(|| {
@@ -525,12 +524,6 @@ fn tar(
525524
};
526525
header.set_entry_type(EntryType::file());
527526
header.set_mode(0o644);
528-
header.set_mtime(
529-
SystemTime::now()
530-
.duration_since(SystemTime::UNIX_EPOCH)
531-
.unwrap()
532-
.as_secs(),
533-
);
534527
header.set_size(contents.len() as u64);
535528
header.set_cksum();
536529
ar.append_data(&mut header, &ar_path, contents.as_bytes())

tests/testsuite/package.rs

+36
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ use cargo_test_support::registry::{self, Package};
66
use cargo_test_support::{
77
basic_manifest, cargo_process, git, path2url, paths, project, symlink_supported, t,
88
};
9+
use flate2::read::GzDecoder;
910
use std::fs::{self, read_to_string, File};
1011
use std::path::Path;
12+
use tar::Archive;
1113

1214
#[cargo_test]
1315
fn simple() {
@@ -1917,3 +1919,37 @@ src/main.rs
19171919
))
19181920
.run();
19191921
}
1922+
1923+
#[cargo_test]
1924+
fn reproducible_output() {
1925+
let p = project()
1926+
.file(
1927+
"Cargo.toml",
1928+
r#"
1929+
[project]
1930+
name = "foo"
1931+
version = "0.0.1"
1932+
authors = []
1933+
exclude = ["*.txt"]
1934+
license = "MIT"
1935+
description = "foo"
1936+
"#,
1937+
)
1938+
.file("src/main.rs", r#"fn main() { println!("hello"); }"#)
1939+
.build();
1940+
1941+
p.cargo("package").run();
1942+
assert!(p.root().join("target/package/foo-0.0.1.crate").is_file());
1943+
1944+
let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap();
1945+
let decoder = GzDecoder::new(f);
1946+
let mut archive = Archive::new(decoder);
1947+
for ent in archive.entries().unwrap() {
1948+
let ent = ent.unwrap();
1949+
let header = ent.header();
1950+
assert_eq!(header.mode().unwrap(), 0o644);
1951+
assert_eq!(header.mtime().unwrap(), 0);
1952+
assert_eq!(header.username().unwrap().unwrap(), "");
1953+
assert_eq!(header.groupname().unwrap().unwrap(), "");
1954+
}
1955+
}

0 commit comments

Comments
 (0)