Skip to content

Commit

Permalink
Basic functionality (and initial repro crate)
Browse files Browse the repository at this point in the history
Adds extremely basic functionality for invoking `cargo build --locked`
as `cargo repro build`.

Though this may seem fairly pointless in and of itself, the goal of a
followup commit would be to collect environmental information during
this step (OS/release, rustc/cargo version, CWD, environment variables,
git commit, C/C++ compiler versions if applicable) and use that during
the verification process to detect and highlight mismatches.

This commit attempts to split the CLI app (i.e. `cargo-repro`) from a
library-level crate containing the core functionality (ala `cargo-audit`
and the `rustsec` crate), in case there is interest in driving these
sorts of builds from external tooling.
  • Loading branch information
tarcieri committed Aug 12, 2019
1 parent 9340d98 commit a8a4ac5
Show file tree
Hide file tree
Showing 12 changed files with 275 additions and 46 deletions.
10 changes: 5 additions & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@ matrix:
include:
- name: rustfmt
script:
- cargo fmt -- --check
- cargo fmt --all -- --check
- name: clippy
script:
- cargo clippy
- cargo clippy --all
- name: build
script:
- cargo build --release
- cargo build --all --release
- name: test
script:
- cargo test --release
- cargo test --all --release
- name: test (1.31.0)
rust: 1.31.0
script:
- cargo test --release
- cargo test --all --release
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ keywords = ["cargo", "deterministic", "reproducible", "security", "verifiable
maintenance = { status = "experimental" }

[dependencies]
repro = { version = "0", path = "repro" }

[workspace]
members = [".", "repro"]
20 changes: 20 additions & 0 deletions repro/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "repro"
description = """
Support crate for cargo-repro, a tool for building and verifying
Rust packages that are reproducible byte-for-byte using a
Cargo-driven workflow.
"""
version = "0.0.0"
authors = ["Rust Secure Code WG <[email protected]>"]
edition = "2018"
license = "Apache-2.0 OR MIT"
readme = "README.md"
repository = "https://github.com/rust-secure-code/cargo-repro"
categories = ["command-line-utilities", "development-tools", "rust-patterns"]
keywords = ["cargo", "deterministic", "reproducible", "security", "verifiable"]

[badges]
maintenance = { status = "experimental" }

[dependencies]
78 changes: 78 additions & 0 deletions repro/src/builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//! Rust project builder - wrapper for invoking Cargo
use std::{
ffi::OsString,
process::{Child, Command, ExitStatus},
};

/// Name of the `cargo` executable
const CARGO_EXE: &str = "cargo";

/// Rust project builder
#[derive(Clone, Debug)]
pub struct Builder {
program: OsString,
args: Vec<OsString>,
}

impl Default for Builder {
fn default() -> Self {
Self::new(CARGO_EXE)
}
}

impl Builder {
/// Create `Builder` that invokes the given command with the given arguments
pub fn new<S>(program: S) -> Self
where
S: Into<OsString>,
{
Self {
program: program.into(),
args: vec![],
}
}

/// Append an argument to the set of arguments to run
pub fn arg<S>(&mut self, arg: S) -> &mut Self
where
S: Into<OsString>,
{
self.args.push(arg.into());
self
}

/// Append multiple arguments to the set of arguments to run
pub fn args<I, S>(&mut self, args: I) -> &mut Self
where
I: IntoIterator<Item = S>,
S: Into<OsString>,
{
self.args.extend(args.into_iter().map(|a| a.into()));
self
}

/// Run the given subcommand
pub fn run(&self) -> Process {
let child = Command::new(&self.program)
.args(&self.args)
.spawn()
.unwrap_or_else(|e| {
panic!("error running command: {}", e);
});

Process(child)
}
}

/// Wrapper for the builder subprocess
pub struct Process(Child);

impl Process {
/// Wait for the child to finish
pub fn wait(mut self) -> ExitStatus {
self.0
.wait()
.unwrap_or_else(|e| panic!("couldn't get child's exit status: {}", e))
}
}
10 changes: 10 additions & 0 deletions repro/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//! `repro` crate: perform and verify reproducible builds of Rust code
#![forbid(unsafe_code)]
#![deny(warnings, missing_docs, trivial_casts, unused_qualifications)]
#![doc(
html_logo_url = "https://avatars3.githubusercontent.com/u/44121472",
html_root_url = "https://docs.rs/repro/0.0.0"
)]

pub mod builder;
15 changes: 0 additions & 15 deletions src/bin/cargo-repro/main.rs

This file was deleted.

44 changes: 44 additions & 0 deletions src/commands.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//! `cargo repro` subcommands
pub mod build;
pub mod verify;

use self::{build::BuildCommand, verify::VerifyCommand};

/// `cargo repro` subcommands
pub enum Command {
/// `cargo repro build` subcommand
Build(BuildCommand),

/// `cargo repro verify` subcommand
Verify(VerifyCommand),
}

impl Command {
/// Parse command to execute from CLI args
pub fn from_args(mut args: impl Iterator<Item = String>) -> Option<Self> {
// ARGV[0] is always the name of the executed binary
args.next().unwrap();

// Cargo passes `repro` as the first argument when invoking `cargo repro`
if args.next().as_ref().map(String::as_str) != Some("repro") {
return None;
}

let command = match args.next().as_ref().map(String::as_str) {
Some("build") => Command::Build(BuildCommand::from_args(args)),
Some("verify") => Command::Verify(VerifyCommand::from_args(args)),
_ => return None,
};

Some(command)
}

/// Run the parsed command
pub fn run(&self) {
match self {
Command::Build(build) => build.run(),
Command::Verify(verify) => verify.run(),
}
}
}
48 changes: 48 additions & 0 deletions src/commands/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//! `cargo repro build` subcommand
use repro::builder::Builder;

/// Cargo argument for a locked build. This is needed to ensure the build
/// is reproducible.
pub const LOCKED_ARG: &str = "--locked";

/// `cargo repro build` subcommand
pub struct BuildCommand {
/// Arguments passed to `cargo repro build` (to be passed to Cargo)
pub args: Vec<String>,
}

impl BuildCommand {
/// Initialize this command from the given arguments, which should *NOT*
/// include `["cargo", "repro", "build"]`
pub fn from_args(args: impl Iterator<Item = String>) -> Self {
Self {
args: args.collect(),
}
}

/// Run this subcommand
// TODO(tarcieri): factor more of this logic into the `repro` crate?
pub fn run(&self) {
let mut builder = Builder::default();
builder.arg("build");

// Add the `--locked` argument unless it's been specified explicitly
if !self.args.iter().any(|arg| arg.as_str() == LOCKED_ARG) {
builder.arg(LOCKED_ARG);
}

builder.args(&self.args);
let exit_status = builder.run().wait();

if !exit_status.success() {
panic!(
"cargo exited with non-zero status: {}",
exit_status
.code()
.map(|code| code.to_string())
.unwrap_or_else(|| "unknown".to_owned())
);
}
}
}
28 changes: 28 additions & 0 deletions src/commands/verify.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//! `cargo repro verify` subcommand
/// `cargo repro verify` subcommand
pub struct VerifyCommand {
/// Arguments passed to `cargo repro verify` (to be passed to Cargo)
pub args: Vec<String>,
}

impl VerifyCommand {
/// Initialize this command from the given arguments, which should *NOT*
/// include `["cargo", "repro", "verify"]`
pub fn from_args(args: impl Iterator<Item = String>) -> Self {
Self {
args: args.collect(),
}
}

/// Run this subcommand
pub fn run(&self) {
println!("cargo repro: build and verify byte-for-byte reproducible Rust packages");
println!();
println!("WORK IN PROGRESS: The 'verify' functionality of this tool is unimplemented.");
println!("If you are interested in contributing, please see the GitHub issues:");
println!();
println!(" https://github.com/rust-secure-code/cargo-repro/issues");
println!();
}
}
26 changes: 0 additions & 26 deletions src/lib.rs

This file was deleted.

31 changes: 31 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//! cargo-repro: perform and verify reproducible builds of Rust code with Cargo
#![deny(warnings, missing_docs, trivial_casts, unused_qualifications)]
#![forbid(unsafe_code)]

pub mod commands;

use commands::Command;
use std::{env, process};

fn main() {
let command = Command::from_args(env::args()).unwrap_or_else(|| usage());
command.run();
}

fn usage() -> ! {
println!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
println!(
"{}\n",
env!("CARGO_PKG_DESCRIPTION")
.split_whitespace()
.collect::<Vec<_>>()
.join(" ")
);

println!("SUBCOMMANDS:");
println!(" build\tPerform a reproducible build of a Cargo project");
println!(" verify\t(UNIMPLEMENTED) Verify a reproducible build");

process::exit(1);
}

0 comments on commit a8a4ac5

Please sign in to comment.