Skip to content
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

Basic functionality (and initial repro crate) #5

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
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
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 self::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);
}