Make builds reproducible

This is done by using `BTreeMap`s and `BTreeSet`s to get deterministic
ordering.

Also updated the CI job to check reproducibility of all examples.
This commit is contained in:
Hugo van der Wijst 2019-01-15 22:42:50 -08:00 committed by Jorge Aparicio
parent fdba26525c
commit 2f89688ca9
5 changed files with 66 additions and 38 deletions

View file

@ -23,7 +23,10 @@ matrix:
rust: nightly rust: nightly
if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master)
before_install: set -e before_install:
- set -e
- sudo apt-get update
- sudo apt-get install -y binutils-arm-none-eabi
install: install:
- bash ci/install.sh - bash ci/install.sh

View file

@ -81,20 +81,45 @@ main() {
continue continue
fi fi
if [ $ex != types ]; then test_arm_example() {
cargo run --example $ex --target $T | \ local EXAMPLE=$1
diff -u ci/expected/$ex.run - local TARGET=$2
local BUILD_MODE=$3
local FEATURES=$4
cargo run --example $ex --target $T --release | \ if [ $BUILD_MODE = "release" ]; then
diff -u ci/expected/$ex.run - local RELEASE_FLAG="--release"
else
local RELEASE_FLAG=""
fi
if [ -n "$FEATURES" ]; then
local FEATURES_FLAG="--features $FEATURES"
else
local FEATURES_FLAG=""
fi
local CARGO_FLAGS="--example $EXAMPLE --target $TARGET $RELEASE_FLAG $FEATURES_FLAG"
cargo run $CARGO_FLAGS | diff -u ci/expected/$EXAMPLE.run -
arm-none-eabi-objcopy -O ihex target/$TARGET/$BUILD_MODE/examples/$EXAMPLE ${EXAMPLE}_1.hex
# build again to ensure that the build is reproducable
cargo clean
cargo build $CARGO_FLAGS
arm-none-eabi-objcopy -O ihex target/$TARGET/$BUILD_MODE/examples/$EXAMPLE ${EXAMPLE}_2.hex
# compare results of both builds
cmp ${EXAMPLE}_1.hex ${EXAMPLE}_2.hex
}
if [ $ex != types ]; then
test_arm_example $ex $T "debug" ""
test_arm_example $ex $T "release" ""
fi fi
if [ $TARGET != thumbv6m-none-eabi ]; then if [ $TARGET != thumbv6m-none-eabi ]; then
cargo run --features timer-queue --example $ex --target $T | \ test_arm_example $ex $T "debug" "timer-queue"
diff -u ci/expected/$ex.run - test_arm_example $ex $T "release" "timer-queue"
cargo run --features timer-queue --example $ex --target $T --release | \
diff -u ci/expected/$ex.run -
fi fi
done done
esac esac

View file

@ -1,6 +1,6 @@
use std::{ use std::{
cmp, cmp,
collections::{HashMap, HashSet}, collections::{BTreeMap, HashMap, HashSet},
}; };
use syn::{Attribute, Ident, Type}; use syn::{Attribute, Ident, Type};
@ -65,7 +65,7 @@ pub struct Dispatcher {
} }
/// Priority -> Dispatcher /// Priority -> Dispatcher
pub type Dispatchers = HashMap<u8, Dispatcher>; pub type Dispatchers = BTreeMap<u8, Dispatcher>;
pub type Capacities = HashMap<Ident, u8>; pub type Capacities = HashMap<Ident, u8>;

View file

@ -2,7 +2,7 @@
use proc_macro::TokenStream; use proc_macro::TokenStream;
use std::{ use std::{
collections::HashMap, collections::{BTreeMap, HashMap},
sync::atomic::{AtomicUsize, Ordering}, sync::atomic::{AtomicUsize, Ordering},
time::{SystemTime, UNIX_EPOCH}, time::{SystemTime, UNIX_EPOCH},
}; };
@ -20,13 +20,13 @@ use crate::{
// NOTE to avoid polluting the user namespaces we map some identifiers to pseudo-hygienic names. // NOTE to avoid polluting the user namespaces we map some identifiers to pseudo-hygienic names.
// In some instances we also use the pseudo-hygienic names for safety, for example the user should // In some instances we also use the pseudo-hygienic names for safety, for example the user should
// not modify the priority field of resources. // not modify the priority field of resources.
type Aliases = HashMap<Ident, Ident>; type Aliases = BTreeMap<Ident, Ident>;
struct Context { struct Context {
// Alias // Alias
#[cfg(feature = "timer-queue")] #[cfg(feature = "timer-queue")]
baseline: Ident, baseline: Ident,
dispatchers: HashMap<u8, Dispatcher>, dispatchers: BTreeMap<u8, Dispatcher>,
// Alias (`fn`) // Alias (`fn`)
idle: Ident, idle: Ident,
// Alias (`fn`) // Alias (`fn`)
@ -41,7 +41,7 @@ struct Context {
schedule_enum: Ident, schedule_enum: Ident,
// Task -> Alias (`fn`) // Task -> Alias (`fn`)
schedule_fn: Aliases, schedule_fn: Aliases,
tasks: HashMap<Ident, Task>, tasks: BTreeMap<Ident, Task>,
// Alias (`struct` / `static mut`) // Alias (`struct` / `static mut`)
timer_queue: Ident, timer_queue: Ident,
} }
@ -66,7 +66,7 @@ impl Default for Context {
Context { Context {
#[cfg(feature = "timer-queue")] #[cfg(feature = "timer-queue")]
baseline: mk_ident(None), baseline: mk_ident(None),
dispatchers: HashMap::new(), dispatchers: BTreeMap::new(),
idle: mk_ident(Some("idle")), idle: mk_ident(Some("idle")),
init: mk_ident(Some("init")), init: mk_ident(Some("init")),
priority: mk_ident(None), priority: mk_ident(None),
@ -74,7 +74,7 @@ impl Default for Context {
resources: HashMap::new(), resources: HashMap::new(),
schedule_enum: mk_ident(None), schedule_enum: mk_ident(None),
schedule_fn: Aliases::new(), schedule_fn: Aliases::new(),
tasks: HashMap::new(), tasks: BTreeMap::new(),
timer_queue: mk_ident(None), timer_queue: mk_ident(None),
} }
} }
@ -2034,7 +2034,7 @@ fn mk_ident(name: Option<&str>) -> Ident {
} }
// `once = true` means that these locals will be called from a function that will run *once* // `once = true` means that these locals will be called from a function that will run *once*
fn mk_locals(locals: &HashMap<Ident, Static>, once: bool) -> proc_macro2::TokenStream { fn mk_locals(locals: &BTreeMap<Ident, Static>, once: bool) -> proc_macro2::TokenStream {
let lt = if once { Some(quote!('static)) } else { None }; let lt = if once { Some(quote!('static)) } else { None };
let locals = locals let locals = locals

View file

@ -1,5 +1,5 @@
use std::{ use std::{
collections::{HashMap, HashSet}, collections::{BTreeMap, BTreeSet},
iter, u8, iter, u8,
}; };
@ -120,10 +120,10 @@ impl App {
pub fn parse(items: Vec<Item>, args: AppArgs) -> parse::Result<Self> { pub fn parse(items: Vec<Item>, args: AppArgs) -> parse::Result<Self> {
let mut idle = None; let mut idle = None;
let mut init = None; let mut init = None;
let mut exceptions = HashMap::new(); let mut exceptions = BTreeMap::new();
let mut interrupts = HashMap::new(); let mut interrupts = BTreeMap::new();
let mut resources = HashMap::new(); let mut resources = BTreeMap::new();
let mut tasks = HashMap::new(); let mut tasks = BTreeMap::new();
let mut free_interrupts = None; let mut free_interrupts = None;
for item in items { for item in items {
@ -418,25 +418,25 @@ impl App {
} }
} }
pub type Idents = HashSet<Ident>; pub type Idents = BTreeSet<Ident>;
pub type Exceptions = HashMap<Ident, Exception>; pub type Exceptions = BTreeMap<Ident, Exception>;
pub type Interrupts = HashMap<Ident, Interrupt>; pub type Interrupts = BTreeMap<Ident, Interrupt>;
pub type Resources = HashMap<Ident, Resource>; pub type Resources = BTreeMap<Ident, Resource>;
pub type Statics = Vec<ItemStatic>; pub type Statics = Vec<ItemStatic>;
pub type Tasks = HashMap<Ident, Task>; pub type Tasks = BTreeMap<Ident, Task>;
pub type FreeInterrupts = HashMap<Ident, FreeInterrupt>; pub type FreeInterrupts = BTreeMap<Ident, FreeInterrupt>;
pub struct Idle { pub struct Idle {
pub args: IdleArgs, pub args: IdleArgs,
pub attrs: Vec<Attribute>, pub attrs: Vec<Attribute>,
pub unsafety: Option<Token![unsafe]>, pub unsafety: Option<Token![unsafe]>,
pub statics: HashMap<Ident, Static>, pub statics: BTreeMap<Ident, Static>,
pub stmts: Vec<Stmt>, pub stmts: Vec<Stmt>,
} }
@ -607,7 +607,7 @@ pub struct Init {
pub args: InitArgs, pub args: InitArgs,
pub attrs: Vec<Attribute>, pub attrs: Vec<Attribute>,
pub unsafety: Option<Token![unsafe]>, pub unsafety: Option<Token![unsafe]>,
pub statics: HashMap<Ident, Static>, pub statics: BTreeMap<Ident, Static>,
pub stmts: Vec<Stmt>, pub stmts: Vec<Stmt>,
// TODO remove in v0.5.x // TODO remove in v0.5.x
pub assigns: Vec<Assign>, pub assigns: Vec<Assign>,
@ -703,7 +703,7 @@ pub struct Exception {
pub args: ExceptionArgs, pub args: ExceptionArgs,
pub attrs: Vec<Attribute>, pub attrs: Vec<Attribute>,
pub unsafety: Option<Token![unsafe]>, pub unsafety: Option<Token![unsafe]>,
pub statics: HashMap<Ident, Static>, pub statics: BTreeMap<Ident, Static>,
pub stmts: Vec<Stmt>, pub stmts: Vec<Stmt>,
} }
@ -791,7 +791,7 @@ pub struct Interrupt {
pub args: InterruptArgs, pub args: InterruptArgs,
pub attrs: Vec<Attribute>, pub attrs: Vec<Attribute>,
pub unsafety: Option<Token![unsafe]>, pub unsafety: Option<Token![unsafe]>,
pub statics: HashMap<Ident, Static>, pub statics: BTreeMap<Ident, Static>,
pub stmts: Vec<Stmt>, pub stmts: Vec<Stmt>,
} }
@ -1070,8 +1070,8 @@ pub struct Static {
} }
impl Static { impl Static {
fn parse(items: Vec<ItemStatic>) -> parse::Result<HashMap<Ident, Static>> { fn parse(items: Vec<ItemStatic>) -> parse::Result<BTreeMap<Ident, Static>> {
let mut statics = HashMap::new(); let mut statics = BTreeMap::new();
for item in items { for item in items {
if statics.contains_key(&item.ident) { if statics.contains_key(&item.ident) {
@ -1104,7 +1104,7 @@ pub struct Task {
pub attrs: Vec<Attribute>, pub attrs: Vec<Attribute>,
pub unsafety: Option<Token![unsafe]>, pub unsafety: Option<Token![unsafe]>,
pub inputs: Vec<ArgCaptured>, pub inputs: Vec<ArgCaptured>,
pub statics: HashMap<Ident, Static>, pub statics: BTreeMap<Ident, Static>,
pub stmts: Vec<Stmt>, pub stmts: Vec<Stmt>,
} }