diff --git a/Cargo.lock b/Cargo.lock index 9525f47e..716fff40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,12 +1,5 @@ -[[package]] -name = "advapi32-sys" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. [[package]] name = "aho-corasick" version = "0.5.3" @@ -17,10 +10,10 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.6.3" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -30,80 +23,181 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "ansi_term" -version = "0.9.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "ansi_term" -version = "0.10.2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "antidote" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "argon2rs" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", + "scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "arrayvec" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "atty" -version = "0.2.2" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", + "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "bitflags" -version = "0.8.2" +name = "autocfg" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "backtrace" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-demangle 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "backtrace-sys" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "base64" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "safemem 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "bitflags" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "bitflags" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "blake2-rfc" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", + "constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "byteorder" +version = "1.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cc" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "chrono" version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "num 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", + "num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "clap" -version = "2.27.1" +version = "2.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "clicolors-control" -version = "0.1.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "clicolors-control 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "clicolors-control" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "constant_time_eq" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "core-foundation" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -111,101 +205,145 @@ name = "core-foundation-sys" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "crypt32-sys" -version = "0.2.0" +name = "dirs" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_users 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "dtoa" -version = "0.4.1" +name = "failure" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)", + "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "failure_derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.24 (registry+https://github.com/rust-lang/crates.io-index)", + "synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "filetime" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.50 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "filetime" -version = "0.1.10" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.50 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "flate2" -version = "0.2.19" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", - "miniz-sys 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", + "miniz-sys 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "foreign-types" -version = "0.2.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] -name = "gcc" -version = "0.3.45" +name = "foreign-types-shared" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "gdi32-sys" -version = "0.2.0" +name = "fs2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "httparse" -version = "1.2.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "hyper" -version = "0.10.9" +version = "0.10.15" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "httparse 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", + "httparse 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", - "mime 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "hyper-native-tls" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.10.9 (registry+https://github.com/rust-lang/crates.io-index)", - "native-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.10.15 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "idna" -version = "0.1.1" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-bidi 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-normalization 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -213,19 +351,19 @@ name = "indicatif" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "clicolors-control 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "clicolors-control 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "itoa" -version = "0.3.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -239,27 +377,30 @@ dependencies = [ [[package]] name = "lal" -version = "3.8.1" +version = "4.0.0" dependencies = [ "ansi_term 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)", - "clap 2.27.1 (registry+https://github.com/rust-lang/crates.io-index)", - "filetime 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "flate2 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.10.9 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper-native-tls 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", + "dirs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "filetime 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", + "flate2 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)", + "fs2 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.10.15 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper-native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "indicatif 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "loggerv 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-probe 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)", "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)", "sha1 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tar 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "tar 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -270,18 +411,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "lazy_static" -version = "0.2.8" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lazy_static" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "libc" -version = "0.2.22" +version = "0.2.46" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lock_api" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "log" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "log" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "loggerv" @@ -289,13 +455,13 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", - "atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "matches" -version = "0.1.4" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -303,167 +469,308 @@ name = "memchr" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "memchr" -version = "1.0.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "mime" -version = "0.2.3" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "miniz-sys" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "gcc 0.3.45 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "native-tls" -version = "0.1.2" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "openssl 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)", - "schannel 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework-sys 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", - "tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)", + "schannel 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "nodrop" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "num" -version = "0.1.37" +version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "num-integer 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", - "num-iter 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", + "num-iter 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-integer" -version = "0.1.34" +version = "0.1.39" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-iter" -version = "0.1.33" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "num-integer 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-traits" -version = "0.1.37" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "num_cpus" -version = "1.4.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "openssl" -version = "0.9.11" +version = "0.9.24" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", - "foreign-types 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", + "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.40 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "openssl-probe" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "openssl-sys" -version = "0.9.11" +version = "0.9.40" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "gcc 0.3.45 (registry+https://github.com/rust-lang/crates.io-index)", - "gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", - "user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "owning_ref" -version = "0.3.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "stable_deref_trait 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "parking_lot" -version = "0.4.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot_core 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "parking_lot_core" -version = "0.2.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "percent-encoding" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "pkg-config" -version = "0.3.9" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "proc-macro2" +version = "0.4.24" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "quote" -version = "0.3.15" +version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "rand" -version = "0.3.15" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_os 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_pcg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_chacha" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rand_hc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_isaac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_os" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_pcg" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_xorshift" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "redox_syscall" -version = "0.1.17" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "redox_termios" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "redox_syscall 0.1.50 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "redox_users" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "argon2rs 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.50 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "regex" @@ -479,14 +786,14 @@ dependencies = [ [[package]] name = "regex" -version = "0.2.1" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -496,22 +803,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "regex-syntax" -version = "0.4.0" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] -name = "rustc-serialize" -version = "0.3.24" +name = "remove_dir_all" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "rustc_version" -version = "0.1.7" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", + "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ryu" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "safemem" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "same-file" version = "0.1.3" @@ -523,52 +851,43 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.4" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "advapi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "crypt32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "secur32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "secur32-sys" -version = "0.2.0" +name = "scoped_threadpool" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "scopeguard" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] name = "security-framework" -version = "0.1.14" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework-sys 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "security-framework-sys" -version = "0.1.14" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "semver" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "semver" version = "0.9.0" @@ -584,37 +903,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "serde" -version = "1.0.24" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "serde_derive" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive_internals 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "serde_derive_internals" -version = "0.18.0" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", - "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.24 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "serde_json" -version = "1.0.8" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -624,79 +933,88 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "smallvec" -version = "0.3.3" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "stable_deref_trait" -version = "1.0.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "strsim" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "syn" -version = "0.11.11" +version = "0.15.24" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", - "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "synom" -version = "0.11.3" +name = "synstructure" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.24 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "tar" -version = "0.4.11" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "filetime 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", - "xattr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", + "filetime 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.50 (registry+https://github.com/rust-lang/crates.io-index)", + "xattr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "tempdir" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "textwrap" -version = "0.9.0" +name = "termion" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.50 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "thread-id" -version = "2.0.0" +name = "textwrap" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "thread-id" -version = "3.0.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -709,22 +1027,20 @@ dependencies = [ [[package]] name = "thread_local" -version = "0.3.3" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "time" -version = "0.1.37" +version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.50 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -737,40 +1053,45 @@ name = "typeable" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "ucd-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "unicase" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "unicode-bidi" -version = "0.2.5" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "unicode-normalization" -version = "0.1.4" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "unicode-width" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "unicode-xid" -version = "0.0.4" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "unreachable" -version = "0.1.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -778,35 +1099,37 @@ dependencies = [ [[package]] name = "url" -version = "1.4.0" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "idna 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "user32-sys" -version = "0.2.0" +name = "utf8-ranges" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] name = "utf8-ranges" -version = "0.1.3" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "utf8-ranges" -version = "1.0.0" +name = "vcpkg" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "vec_map" -version = "0.8.0" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "version_check" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -829,122 +1152,182 @@ name = "winapi" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "winapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "winapi-build" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "xattr" -version = "0.1.11" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", ] [metadata] -"checksum advapi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e06588080cb19d0acb6739808aafa5f26bfb2ca015b2b6370028b44cf7cb8a9a" "checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66" -"checksum aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "500909c4f87a9e52355b26626d890833e9e1d53ac566db76c36faa984b889699" +"checksum aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1e9a933f4e58658d7b12defcf96dc5c720f20832deebe3e0a19efd3b6aaeeb9e" "checksum ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6b3568b48b7cefa6b8ce125f9bb4989e52fbcc29ebea88df04cc7c5f12f70455" +"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" "checksum ansi_term 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)" = "30275ad0ad84ec1c06dde3b3f7d23c6006b7d76d61a85e7060b426b747eff70d" -"checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6" "checksum antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5" -"checksum atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d912da0db7fa85514874458ca3651fe2cddace8d0b0505571dbdcd41ab490159" -"checksum bitflags 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1370e9fc2a6ae53aea8b7a5110edbd08836ed87c88736dfabccade1c2b44bff4" +"checksum argon2rs 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f67b0b6a86dae6e67ff4ca2b6201396074996379fba2b92ff649126f37cb392" +"checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71" +"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" +"checksum autocfg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4e5f34df7a019573fb8bdc7e24a2bfebe51a2a1d6bfdbaeccedb3c41fc574727" +"checksum backtrace 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)" = "b5b493b66e03090ebc4343eb02f94ff944e0cbc9ac6571491d170ba026741eb5" +"checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6" +"checksum base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" "checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" +"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" +"checksum blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" +"checksum byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "94f88df23a25417badc922ab0f5716cc1330e87f71ddd9203b3a3ccd9cedf75d" +"checksum cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4a8b715cb4597106ea87c7c84b2f1d452c7492033765df7f32651e66fcf749" +"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4" "checksum chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)" = "9213f7cd7c27e95c2b57c49f0e69b1ea65b27138da84a170133fd21b07659c00" -"checksum clap 2.27.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1b8c532887f1a292d17de05ae858a8fe50a301e196f9ef0ddb7ccd0d1d00f180" -"checksum clicolors-control 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "61bd6bff2f99f947c2dbdc73cd0ebd55d8263b921fd4f68a44598555be49f32b" +"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" +"checksum clicolors-control 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "495303f76458db51aa330df9510cfbb2e3ad57c6b82da7b38aad2a89088a91da" +"checksum clicolors-control 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "73abfd4c73d003a674ce5d2933fca6ce6c42480ea84a5ffe0a2dc39ed56300f9" +"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +"checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" "checksum core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25bfd746d203017f7d5cbd31ee5d8e17f94b6521c7af77ece6c9e4b2d4b16c67" "checksum core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "065a5d7ffdcbc8fa145d6f0746f3555025b9097a9e9cda59f7467abae670c78d" -"checksum crypt32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e34988f7e069e0b2f3bfc064295161e489b2d4e04a2e4248fb94360cdf00b4ec" -"checksum dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "80c8b71fd71146990a9742fc06dcbbde19161a267e0ad4e572c35162f4578c90" -"checksum filetime 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "5363ab8e4139b8568a6237db5248646e5a8a2f89bd5ccb02092182b11fd3e922" -"checksum flate2 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)" = "36df0166e856739905cd3d7e0b210fe818592211a008862599845e012d8d304c" -"checksum foreign-types 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e4056b9bd47f8ac5ba12be771f77a0dae796d1bbaaf5fd0b9c2d38b69b8a29d" -"checksum gcc 0.3.45 (registry+https://github.com/rust-lang/crates.io-index)" = "40899336fb50db0c78710f53e87afc54d8c7266fb76262fecc78ca1a7f09deae" -"checksum gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0912515a8ff24ba900422ecda800b52f4016a56251922d397c576bf92c690518" -"checksum httparse 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77f756bed9ee3a83ce98774f4155b42a31b787029013f3a7d83eca714e500e21" -"checksum hyper 0.10.9 (registry+https://github.com/rust-lang/crates.io-index)" = "94da93321c171e26481afeebe8288757b0501901b7c5492648163d8ec4942ec5" -"checksum hyper-native-tls 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "afe68f772f0497a7205e751626bb8e1718568b58534b6108c73a74ef80483409" -"checksum idna 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6ac85ec3f80c8e4e99d9325521337e14ec7555c458a14e377d189659a427f375" +"checksum dirs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "88972de891f6118092b643d85a0b28e0678e0f948d7f879aa32f2d5aafe97d2a" +"checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" +"checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" +"checksum filetime 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "714653f3e34871534de23771ac7b26e999651a0a228f47beb324dfdf1dd4b10f" +"checksum filetime 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a2df5c1a8c4be27e7707789dc42ae65976e60b394afd293d1419ab915833e646" +"checksum flate2 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)" = "e6234dd4468ae5d1e2dbb06fe2b058696fdc50a339c68a393aefbf00bc81e423" +"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +"checksum fs2 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +"checksum httparse 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e8734b0cfd3bc3e101ec59100e101c2eecd19282202e87808b3037b442777a83" +"checksum hyper 0.10.15 (registry+https://github.com/rust-lang/crates.io-index)" = "df0caae6b71d266b91b4a83111a61d2b94ed2e2bea024c532b933dcff867e58c" +"checksum hyper-native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "72332e4a35d3059583623b50e98e491b78f8b96c5521fcb3f428167955aa56e8" +"checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" "checksum indicatif 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6521f315e66b082d1f622105fa9c556a7cba346848984cf4b482b9066f9f89" -"checksum itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2f404fbc66fd9aac13e998248505e7ecb2ad8e44ab6388684c5fb11c6c251c" +"checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" -"checksum lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3b37545ab726dd833ec6420aaba8231c5b320814b9029ad585555d2a03e94fbf" -"checksum libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)" = "babb8281da88cba992fa1f4ddec7d63ed96280a1a53ec9b919fd37b53d71e502" -"checksum log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "5141eca02775a762cc6cd564d8d2c50f67c0ea3a372cbf1c51592b3e029e10ad" +"checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" +"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1" +"checksum libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)" = "023a4cd09b2ff695f9734c1934145a315594b7986398496841c7031a5a1bbdbd" +"checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" +"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" +"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" "checksum loggerv 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b178879253fab6ddb4ea931e1e6f514d45ce6a53f7fe618a0a8751f43e42e4f1" -"checksum matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "efd7622e3022e1a6eaa602c4cea8912254e5582c9c692e9167714182244801b1" +"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" "checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" -"checksum memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4" -"checksum mime 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5514f038123342d01ee5f95129e4ef1e0470c93bc29edf058a46f9ee3ba6737e" -"checksum miniz-sys 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "28eaee17666671fa872e567547e8428e83308ebe5808cdf6a0e28397dbe2c726" -"checksum native-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1e94a2fc65a44729fe969cc973da87c1052ae3f000b2cb33029f14aeb85550d5" -"checksum num 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "98b15ba84e910ea7a1973bccd3df7b31ae282bf9d8bd2897779950c9b8303d40" -"checksum num-integer 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)" = "ef1a4bf6f9174aa5783a9b4cc892cacd11aebad6c69ad027a0b65c6ca5f8aa37" -"checksum num-iter 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)" = "f7d1891bd7b936f12349b7d1403761c8a0b85a18b148e9da4429d5d102c1a41e" -"checksum num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "e1cbfa3781f3fe73dc05321bed52a06d2d491eaa764c52335cf4399f046ece99" -"checksum num_cpus 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca313f1862c7ec3e0dfe8ace9fa91b1d9cb5c84ace3d00f5ec4216238e93c167" -"checksum openssl 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)" = "241bcf67b1bb8d19da97360a925730bdf5b6176d434ab8ded55b4ca632346e3a" -"checksum openssl-probe 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d98df0270d404ccd3c050a41d579c52d1db15375168bb3471e04ec0f5f378daf" -"checksum openssl-sys 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)" = "e5e0fd64cb2fa018ed2e7b2c8d9649114fe5da957c9a67432957f01e5dcc82e9" -"checksum owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37" -"checksum parking_lot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aebb68eebde2c99f89592d925288600fde220177e46b5c9a91ca218d245aeedf" -"checksum parking_lot_core 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "56a19dcbb5d1e32b6cccb8a9aa1fc2a38418c8699652e735e2bf391a3dc0aa16" -"checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" -"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" -"checksum rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "022e0636ec2519ddae48154b028864bdce4eaf7d35226ab8e65c611be97b189d" -"checksum redox_syscall 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "29dbdfd4b9df8ab31dec47c6087b7b13cbf4a776f335e4de8efba8288dda075b" +"checksum memchr 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "db4c41318937f6e76648f42826b1d9ade5c09cafb5aef7e351240a70f39206e9" +"checksum mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" +"checksum miniz-sys 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "0300eafb20369952951699b68243ab4334f4b10a88f411c221d444b36c40e649" +"checksum native-tls 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f74dbadc8b43df7864539cedb7bc91345e532fdd913cfdc23ad94f4d2d40fbc0" +"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" +"checksum num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e" +"checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" +"checksum num-iter 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "af3fdbbc3291a5464dc57b03860ec37ca6bf915ed6ee385e7c6c052c422b2124" +"checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" +"checksum num_cpus 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5a69d464bdc213aaaff628444e99578ede64e9c854025aa43b9796530afa9238" +"checksum openssl 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)" = "a3605c298474a3aa69de92d21139fb5e2a81688d308262359d85cdd0d12a7985" +"checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" +"checksum openssl-sys 0.9.40 (registry+https://github.com/rust-lang/crates.io-index)" = "1bb974e77de925ef426b6bc82fce15fd45bdcbeb5728bffcfc7cdeeb7ce1c2d6" +"checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13" +"checksum parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab41b4aed082705d1056416ae4468b6ea99d52599ecf3169b00088d43113e337" +"checksum parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9" +"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" +"checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c" +"checksum proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)" = "77619697826f31a02ae974457af0b29b723e5619e113e9397b8b82c6bd253f09" +"checksum quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "53fa22a1994bd0f9372d7a816207d8a2677ad0325b073f5c5332760f0fb62b5c" +"checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1" +"checksum rand 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "dee497e66d8d76bf08ce20c8d36e16f93749ab0bf89975b4f8ae5cee660c2da2" +"checksum rand 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3906503e80ac6cbcacb2c2973fa8e473f24d7e2747c8c92bb230c2441cad96b5" +"checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +"checksum rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0905b6b7079ec73b314d4c748701f6931eb79fd97c668caa3f1899b22b32c6db" +"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +"checksum rand_os 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f46fbd5550acf75b0c2730f5dd1873751daf9beb8f11b44027778fae50d7feca" +"checksum rand_pcg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "086bd09a33c7044e56bb44d5bdde5a60e7f119a9e95b0775f545de759a32fe05" +"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +"checksum redox_syscall 0.1.50 (registry+https://github.com/rust-lang/crates.io-index)" = "52ee9a534dc1301776eff45b4fa92d2c39b1d8c3d3357e6eb593e0d795506fc2" +"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" +"checksum redox_users 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "214a97e49be64fd2c86f568dd0cb2c757d2cc53de95b273b6ad0a1c908482f26" "checksum regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f" -"checksum regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4278c17d0f6d62dfef0ab00028feb45bd7d2102843f80763474eeb1be8a10c01" +"checksum regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9329abc99e39129fcceabd24cf5d85b4671ef7c29c50e972bc5afe32438ec384" "checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957" -"checksum regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9191b1f57603095f105d317e375d19b1c9c5c3185ea9633a99a6dcbed04457" -"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" -"checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084" +"checksum regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7" +"checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5" +"checksum rustc-demangle 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "adacaae16d02b6ec37fdc7acfcddf365978de76d1983d3ee22afc260e1ca9619" +"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +"checksum ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "eb9e9b8cde282a9fe6a42dd4681319bfb63f121b8a8ee9439c6f4107e58a46f7" +"checksum safemem 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dca453248a96cb0749e36ccdfe2b0b4e54a61bfef89fb97ec621eb8e0a93dd9" "checksum same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d931a44fdaa43b8637009e7632a02adc4f2b2e0733c08caa4cf00e8da4a117a7" -"checksum schannel 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c8b291854e37196c2b67249e09d6bdeff410b19e1acf05558168e9c4413b4e95" -"checksum secur32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3f412dfa83308d893101dd59c10d6fda8283465976c28c287c5c855bf8d216bc" -"checksum security-framework 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "42ddf098d78d0b64564b23ee6345d07573e7d10e52ad86875d89ddf5f8378a02" -"checksum security-framework-sys 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "5bacdada57ea62022500c457c8571c17dfb5e6240b7c8eac5916ffa8c7138a55" -"checksum semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac" +"checksum schannel 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "0e1a231dc10abf6749cfa5d7767f25888d484201accbd919b66ab5413c502d56" +"checksum scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" +"checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" +"checksum security-framework 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "dfa44ee9c54ce5eecc9de7d5acbad112ee58755239381f687e564004ba4a2332" +"checksum security-framework-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "5421621e836278a0b139268f36eee0dc7e389b784dc3f79d8f11aabadf41bead" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -"checksum serde 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)" = "1c57ab4ec5fa85d08aaf8ed9245899d9bbdd66768945b21113b84d5f595cb6a1" -"checksum serde_derive 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)" = "02c92ea07b6e49b959c1481804ebc9bfd92d3c459f1274c9a9546829e42a66ce" -"checksum serde_derive_internals 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "75c6aac7b99801a16db5b40b7bf0d7e4ba16e76fbf231e32a4677f271cac0603" -"checksum serde_json 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7cf5b0b5b4bd22eeecb7e01ac2e1225c7ef5e4272b79ee28a8392a8c8489c839" +"checksum serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)" = "0e732ed5a5592c17d961555e3b552985baf98d50ce418b7b655f31f6ba7eb1b7" +"checksum serde_derive 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d6115a3ca25c224e409185325afc16a0d5aaaabc15c42b09587d6f1ba39a5b" +"checksum serde_json 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)" = "bdf540260cfee6da923831f4776ddc495ada940c30117977c70f1313a6130545" "checksum sha1 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f35e6e47328ec7d599a0adba8233559dc4711d752ba9c4c6078274b8be9d5a77" -"checksum smallvec 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4f8266519bc1d17d0b5b16f6c21295625d562841c708f6376f49028a43e9c11e" -"checksum stable_deref_trait 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "15132e0e364248108c5e2c02e3ab539be8d6f5d52a01ca9bbf27ed657316f02b" -"checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694" -"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" -"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" -"checksum tar 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c2374f318bbe2c5ac6c83dd6240d5f1a73106f72d39b3f7d6f8d8637c7b425d8" -"checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6" -"checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693" +"checksum smallvec 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "b73ea3738b47563803ef814925e69be00799a8c07420be8b996f8e98fb2336db" +"checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" +"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" +"checksum syn 0.15.24 (registry+https://github.com/rust-lang/crates.io-index)" = "734ecc29cd36e8123850d9bf21dfd62ef8300aaa8f879aabaa899721808be37c" +"checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015" +"checksum tar 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)" = "a303ba60a099fcd2aaa646b14d2724591a96a75283e4b7ed3d1a1658909d9ae2" +"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" +"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" +"checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6" "checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" -"checksum thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4437c97558c70d129e40629a5b385b3fb1ffac301e63941335e4d354081ec14a" "checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5" -"checksum thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c85048c6260d17cf486ceae3282d9fb6b90be220bf5b28c400f5485ffc29f0c7" -"checksum time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "ffd7ccbf969a892bf83f1e441126968a07a3941c24ff522a26af9f9f4585d1a3" +"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" +"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" "checksum traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" "checksum typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" -"checksum unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "13a5906ca2b98c799f4b1ab4557b76367ebd6ae5ef14930ec841c74aed5f3764" -"checksum unicode-bidi 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d3a078ebdd62c0e71a709c3d53d2af693fe09fe93fbff8344aebe289b78f9032" -"checksum unicode-normalization 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e28fa37426fceeb5cf8f41ee273faa7c82c47dc8fba5853402841e665fcd86ff" -"checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f" -"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" -"checksum unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2ae5ddb18e1c92664717616dd9549dde73f539f01bd7b77c2edb2446bdff91" -"checksum url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f5ba8a749fb4479b043733416c244fa9d1d3af3d7c23804944651c8a448cb87e" -"checksum user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ef4711d107b21b410a3a974b1204d9accc8b10dad75d8324b5d755de1617d47" +"checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" +"checksum unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" +"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +"checksum unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6a0180bc61fc5a987082bfa111f4cc95c4caff7f9799f3e46df09163a937aa25" +"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" +"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" +"checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" "checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" -"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" -"checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c" +"checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737" +"checksum vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "def296d3eb3b12371b2c7d0e83bfe1403e4db2d7a0bba324a12b21c4ee13143d" +"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "bb08f9e670fab86099470b97cd2b252d6527f0b3cc1401acdb595ffc9dd288ff" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" -"checksum xattr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "5f04de8a1346489a2f9e9bd8526b73d135ec554227b17568456e86aa35b6f3fc" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +"checksum xattr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c" diff --git a/Cargo.toml b/Cargo.toml index d48764ce..23db7a50 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lal" -version = "3.8.1" +version = "4.0.0" authors = ["Eirik Albrigtsen "] description = "A strict, language-agnostic build system and dependency manager" documentation = "http://lalbuild.github.io/lal" @@ -8,6 +8,8 @@ license = "MIT" categories = ["command-line-utilities"] keywords = ["package", "dependency", "build", "docker", "artifactory"] readme = "README.md" +autotests = false +edition = "2018" [badges] travis-ci = { repository = "lalbuild/lal", branch = "master" } @@ -17,6 +19,7 @@ coveralls = { repository = "lalbuild/lal", branch = "master" } doc = false name = "lal" path = "src/main.rs" +edition = "2018" [[test]] harness = false @@ -28,6 +31,7 @@ chrono = "0.2" clap = "2.27.1" filetime = "0.1" flate2 = "0.2" +fs2 = "0.4.2" hyper = "0.10.9" hyper-native-tls = "0.2.2" log = "0.3.5" @@ -42,13 +46,15 @@ serde_json = "1.0.8" sha1 = "0.3.0" tar = "0.4.10" walkdir = "1.0.7" +dirs = "1.0" +bitflags = "1.0.4" [dependencies.indicatif] optional = true version = "0.3.3" [features] -default = ["progress"] +default = ["progress", "upgrade"] progress = ["indicatif"] upgrade = [] diff --git a/docs.sh b/docs.sh index 5bb434b3..cda40ce9 100755 --- a/docs.sh +++ b/docs.sh @@ -1,6 +1,7 @@ #!/bin/bash set -ex +# NB: requires the ghp-import pip module cargo doc echo "" > target/doc/index.html ghp-import -n target/doc diff --git a/src/build.rs b/src/build.rs index 640364ba..22a550aa 100644 --- a/src/build.rs +++ b/src/build.rs @@ -1,11 +1,12 @@ -use std::path::Path; use std::fs; +use std::path::Path; -use shell; -use verify::verify; -use super::{ensure_dir_exists_fresh, output, Lockfile, Manifest, Container, Config, LalResult, - CliError, DockerRunFlags, ShellModes}; - +use super::{ + ensure_dir_exists_fresh, output, CliError, Config, Container, DockerRunFlags, LalResult, + Lockfile, Manifest, ShellModes, +}; +use crate::shell; +use crate::verify::{verify, Flags}; fn find_valid_build_script() -> LalResult { use std::os::unix::fs::PermissionsExt; @@ -37,7 +38,6 @@ fn find_valid_build_script() -> LalResult { Ok(build_string.into()) } - /// Configurable build flags for `lal build` pub struct BuildOptions { /// Component to build if specified @@ -54,11 +54,10 @@ pub struct BuildOptions { pub sha: Option, /// Ignore verify failures pub force: bool, - /// Use the `simple` verify algorithm - pub simple_verify: bool, + /// Flags used to tweak the verification algorithm + pub verify_flags: Flags, } - /// Runs the `./BUILD` script in a container and packages artifacts. /// /// The function performs basic sanity checks, before shelling out to `docker run` @@ -68,31 +67,30 @@ pub fn build( cfg: &Config, manifest: &Manifest, opts: &BuildOptions, - envname: String, - _modes: ShellModes, + envname: &str, + mut modes: ShellModes, ) -> LalResult<()> { - let mut modes = _modes; - // have a better warning on first file-io operation // if nfs mounts and stuff cause issues this usually catches it - ensure_dir_exists_fresh("./OUTPUT") - .map_err(|e| { - error!("Failed to clean out OUTPUT dir: {}", e); - e - })?; + ensure_dir_exists_fresh("./OUTPUT").map_err(|e| { + error!("Failed to clean out OUTPUT dir: {}", e); + e + })?; debug!("Version flag is {:?}", opts.version); // Verify INPUT - let mut verify_failed = false; - if let Some(e) = verify(manifest, &envname, opts.simple_verify).err() { - if !opts.force { - return Err(e); + let verify_failed = { + if let Some(e) = verify(manifest, &envname, opts.verify_flags).err() { + if !opts.force { + return Err(e); + } + warn!("Verify failed - build will fail on jenkins, but continuing"); + true + } else { + false } - verify_failed = true; - warn!("Verify failed - build will fail on jenkins, but continuing"); - } - + }; let component = opts.name.clone().unwrap_or_else(|| manifest.name.clone()); debug!("Getting configurations for {}", component); @@ -110,18 +108,24 @@ pub fn build( } else { component_settings.defaultConfig.clone() }; - if !component_settings.configurations.contains(&configuration_name) { + if !component_settings + .configurations + .contains(&configuration_name) + { let ename = format!("{} not found in configurations list", configuration_name); return Err(CliError::InvalidBuildConfiguration(ename)); } - let lockfile = Lockfile::new(&component, - &opts.container, - &envname, - opts.version.clone(), - Some(&configuration_name)) - .set_default_env(manifest.environment.clone()) - .attach_revision_id(opts.sha.clone()) - .populate_from_input()?; + let lockfile = Lockfile::new( + &component, + &opts.container, + &envname, + opts.version.clone(), + Some(&configuration_name), + ) + .with_channel(manifest.channel.clone()) + .set_default_env(manifest.environment.clone()) + .attach_revision_id(opts.sha.clone()) + .populate_from_input()?; let lockpth = Path::new("./OUTPUT/lockfile.json"); lockfile.write(lockpth)?; // always put a lockfile in OUTPUT at the start of a build diff --git a/src/channel.rs b/src/channel.rs new file mode 100644 index 00000000..e07af3de --- /dev/null +++ b/src/channel.rs @@ -0,0 +1,425 @@ +use super::{CliError, LalResult, Manifest}; +use std::fmt; +use std::path::PathBuf; + +/// Set the channel. The channel will be tidied before use, to ensure it +/// is valid. A channel are rejected iff a component of the channel's path +/// exactly matches "testing", and allow_testing is false. +pub fn set(manifest: &mut Manifest, path: &str, allow_testing: bool) -> LalResult<()> { + let channel = Channel::new(path); + channel.verify()?; + if !allow_testing && channel.is_testing() { + return Err(CliError::InvalidTestingChannel(channel.to_string())); + } + debug!("Storing channel: {}", channel); + manifest.channel = Some(channel.to_string()); + manifest.write() +} + +/// Ensure a channel is correctly formatted. e.g. a//b/c/ goes to /a/b/c +pub fn tidy_channel(path: &str) -> String { Channel::new(path).to_string() } + +fn path_to_vec(channel: &str) -> Vec { + component_iter(channel).map(ToOwned::to_owned).collect() +} + +const DEFAULT_CHANNEL: &str = "/"; + +/// Structure representing a channel's path. +#[derive(Debug, Eq, Clone)] +pub struct Channel { + components: Vec, +} + +impl fmt::Display for Channel { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "/{}", self.components.join(&"/")) + } +} + +impl PartialEq for Channel { + fn eq(&self, other: &Self) -> bool { self.components == other.components } +} + +impl Default for Channel { + fn default() -> Self { Self::new(DEFAULT_CHANNEL) } +} + +impl Channel { + /// Identifier for a testing channel. + pub const TESTING: &'static str = "testing"; + + /// Creates a new Channel object with a given path. + pub fn new(path: &str) -> Self { + Self { + components: path_to_vec(path), + } + } + + /// Verifies that the channel is valid. A channel is invalid + /// iff any of the following are true: + /// - A non-leaf component in the channel path is "testing" + /// - The channel contains a NUL character + pub fn verify(&self) -> LalResult<()> { + if self.components.len() > 1 { + for c in &self.components[0..self.components.len() - 1] { + if *c == Self::TESTING { + return Err(CliError::InvalidTestingChannel(self.to_string())); + } + } + } + + for c in &self.components { + for b in c.bytes() { + if b == b'\0' { + return Err(CliError::InvalidChannelCharacter(self.to_string())); + } + } + + if c.is_empty() { + unreachable!("Empty channel component - this should not be able to happen") + } + } + Ok(()) + } + + /// Creates a new Channel with a given path if provided, else uses a default path. + pub fn from_option(path: &Option) -> Self + where + S: ToString, + { + match path { + Some(path) => Self::new(&path.to_string()), + None => Self::default(), + } + } + + /// Verifies whether one channel contains the other, and therefore + /// is a valid parent in the channel hierarchy. + pub fn contains(&self, other: &Self) -> LalResult<()> { + // Zip truncates to the shortest length. The rest of the verification + // will therefore be invalid if the other is longer than self. + if other.components.len() > self.components.len() { + return Err(CliError::ChannelMismatch( + other.to_string(), + self.to_string(), + )); + } + + let mut iter = self + .components + .iter() + .zip(other.components.iter()) + .peekable(); + while iter.peek().is_some() { + let (a, b) = iter.next().unwrap(); + let is_last = iter.peek().is_none(); + + // Testing branches support a special case of contains, + // where "/a/testing" is considered a parent of "/a/b/testing". + if self.is_testing() && *b == Self::TESTING && is_last { + break; + } + + // If the path is different then b cannot contain a. + if *a != *b { + return Err(CliError::ChannelMismatch( + self.to_string(), + other.to_string(), + )); + } + } + + Ok(()) + } + + /// Verifies whether a channel is designated as a testing channel. + pub fn is_testing(&self) -> bool { + if let Some(ch) = self.components.last() { + *ch == Self::TESTING + } else { + false + } + } + + fn convert(&self, prefix: &str, join: &str, suffix: &str) -> String { + if self.components.is_empty() { + String::new() + } else { + format!("{}{}{}", prefix, self.components.join(join), suffix) + } + } + + /// Provides a formatted string suitable for insertion into a web address. + pub fn http_string(&self) -> String { self.convert("ch/", "/ch/", "/") } + + /// Provides a formatted string suitable for insertion into a file path. + pub fn fs_string(&self) -> String { self.convert("/channels/", "/channels/", "") } + + /// Provides a formatted string suitable for insertion into a version string (i.e. repository=/chan/version) + pub fn version_string(&self) -> String { self.convert("/", "/", "/") } + + /// Converts the channel to an owned path object. + pub fn to_path(&self) -> PathBuf { PathBuf::from(&self.convert("channels/", "/channels/", "")) } +} + +fn component_iter(channel: &str) -> impl Iterator { + channel.split('/').filter(|s| !s.is_empty()) +} + +/// Returns the current channel path. If no channel is found then the default channel is returned. +pub fn get(manifest: &Manifest) -> &str { + if let Some(ref channel) = manifest.channel { + &channel + } else { + DEFAULT_CHANNEL + } +} + +/// Retrieves coordinates from a coordinate string. +pub fn parse_coords(coords: &str) -> (Option, Option) { + let split_coords = coords.split('/').collect::>(); + let version = split_coords.last().unwrap().parse::().ok(); + + // The parsing works as follows for coords /a/b: + // - If b is a number: + // * Channel: /a + // * Version: b + // - Otherwise: + // * Channel: /a/b + // * Version: None + let channel = if split_coords.len() == 1 { + // There is no slash + None + } else { + let path = if version.is_some() { + split_coords + .iter() + .take(split_coords.len() - 1) + .cloned() + .collect::>() + } else { + split_coords + } + .join(&"/"); + Some(Channel::new(&path)) + }; + (version, channel) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::path::Path; + + #[test] + fn test_parse() { + fn assert_parse(input: &str, version: Option, channel: Option<&str>) { + let coords = parse_coords(input); + assert_eq!(coords.0, version); + if let Some(channel) = channel { + assert!(coords.1.is_some()); + assert_eq!(coords.1.unwrap(), Channel::new(&channel)); + } else { + assert!(coords.1.is_none()); + } + } + + assert_parse("", None, None); + assert_parse("a", None, None); + assert_parse("1", Some(1), None); + assert_parse("/a", None, Some("/a")); + assert_parse("/a/", None, Some("/a")); + assert_parse("/1", Some(1), Some("/")); + assert_parse("/1/", None, Some("/1")); + assert_parse("/a/1", Some(1), Some("/a")); + assert_parse("/a/1/", None, Some("/a/1")); + assert_parse("/1/2", Some(2), Some("/1")); + assert_parse("/1/2/", None, Some("/1/2")); + } + + #[test] + fn test_tidy() { + fn assert_tidy(input: &str, output: &str) { + assert_eq!(Channel::new(input).to_string(), output); + assert_eq!(tidy_channel(input), output); + } + + assert_tidy("", DEFAULT_CHANNEL); + assert_tidy("/", "/"); + assert_tidy("//", "/"); + assert_tidy("///", "/"); + assert_tidy("a", "/a"); + assert_tidy("µ", "/µ"); + assert_tidy("µ/Ø///", "/µ/Ø"); + assert_tidy("aa", "/aa"); + assert_tidy("////a///", "/a"); + assert_tidy("a/b", "/a/b"); + assert_tidy("a////b/c/d////", "/a/b/c/d"); + } + + #[test] + fn test_http() { + fn assert_http(input: Channel, output: &str) { + assert_eq!(input.http_string(), output); + } + + let input = Channel::default(); + let output = ""; + assert_http(input, output); + + let input = Channel::new("a"); + let output = "ch/a/"; + assert_http(input, output); + + let input = Channel::new("/a/b/c"); + let output = "ch/a/ch/b/ch/c/"; + assert_http(input, output); + } + + #[test] + fn test_fs() { + fn assert_fs(input: Channel, output: &str) { + assert_eq!(input.fs_string(), output); + } + + let input = Channel::default(); + let output = ""; + assert_fs(input, output); + + let input = Channel::new("a"); + let output = "/channels/a"; + assert_fs(input, output); + + let input = Channel::new("/a/b/c"); + let output = "/channels/a/channels/b/channels/c"; + assert_fs(input, output); + } + + #[test] + fn test_path() { + fn assert_path(input: Channel, output: &str) { + assert_eq!(input.to_path(), Path::new(output)) + } + + let input = Channel::default(); + let output = ""; + assert_path(input, output); + + let input = Channel::new("a"); + let output = "channels/a"; + assert_path(input, output); + + let input = Channel::new("/a/b/c"); + let output = "channels/a/channels/b/channels/c"; + assert_path(input, output); + } + + #[test] + fn test_contains() { + fn assert_contains(child: Channel, parent: Channel) { + assert!(child.contains(&parent).is_ok()) + } + + fn assert_separate(child: Channel, parent: Channel) { + assert!(child.contains(&parent).is_err()) + } + + let child = Channel::default(); + let parent = Channel::default(); + assert_contains(child, parent); + + let child = Channel::new("/a"); + let parent = Channel::new("/a"); + assert_contains(child, parent); + + let child = Channel::new("/a"); + let parent = Channel::new("/b"); + assert_separate(child, parent); + + let child = Channel::new("/a/b"); + let parent = Channel::new("/a"); + assert_contains(child, parent); + + let child = Channel::new("/a/b"); + let parent = Channel::new("/b"); + assert_separate(child, parent); + + let child = Channel::new("/a/testing"); + let parent = Channel::new("/a"); + assert_contains(child, parent); + + let child = Channel::new("/a/testing"); + let parent = Channel::new("/"); + assert_contains(child, parent); + + let child = Channel::new("/a/testing"); + let parent = Channel::new("/b"); + assert_separate(child, parent); + + let child = Channel::new("/a/testing"); + let parent = Channel::new("/b/testing"); + assert_separate(child, parent); + + let child = Channel::new("/a/testing"); + let parent = Channel::new("/a/testing"); + assert_contains(child, parent); + + let child = Channel::new("/a/b/testing"); + let parent = Channel::new("/a/testing"); + assert_contains(child, parent); + + let child = Channel::new("/a/b/testing"); + let parent = Channel::new("/a/c/testing"); + assert_separate(child, parent); + + let child = Channel::new("/a/b/testing"); + let parent = Channel::new("/b/testing"); + assert_separate(child, parent); + } + + #[test] + fn test_testing() { + let ch = Channel::default(); + assert!(!ch.is_testing()); + + let ch = Channel::new("/a/b/c"); + assert!(!ch.is_testing()); + + let ch = Channel::new("/testing"); + assert!(ch.is_testing()); + + let ch = Channel::new("/a/testing"); + assert!(ch.is_testing()); + } + + #[test] + fn test_verify() { + let ch = Channel::default(); + assert!(ch.verify().is_ok()); + + let ch = Channel::new("/a/b"); + assert!(ch.verify().is_ok()); + + let ch = Channel::new("\n"); + assert!(ch.verify().is_ok()); + + let ch = Channel::new("\0"); + match ch.verify() { + Err(CliError::InvalidChannelCharacter(_)) => (), + _ => assert!(false), + } + + let ch = Channel::new("/testing"); + assert!(ch.verify().is_ok()); + + let ch = Channel::new("/a/testing"); + assert!(ch.verify().is_ok()); + + let ch = Channel::new("/testing/a"); + match ch.verify() { + Err(CliError::InvalidTestingChannel(_)) => (), + _ => assert!(false), + } + } +} diff --git a/src/clean.rs b/src/clean.rs index fedbd22f..253f2cb1 100644 --- a/src/clean.rs +++ b/src/clean.rs @@ -1,7 +1,7 @@ use std::fs; use std::path::Path; -use chrono::{DateTime, UTC, Duration, TimeZone}; +use chrono::{DateTime, Duration, TimeZone, UTC}; use filetime::FileTime; use walkdir::WalkDir; @@ -9,14 +9,17 @@ use super::LalResult; // helper for `lal::clean` fn clean_in_dir(cutoff: DateTime, dirs: WalkDir) -> LalResult<()> { - let drs = dirs.into_iter().filter_map(|e| e.ok()).filter(|e| e.path().is_dir()); + let dirs = dirs + .into_iter() + .filter_map(Result::ok) + .filter(|e| e.path().is_dir()); - for d in drs { + for d in dirs { let pth = d.path(); trace!("Checking {}", pth.to_str().unwrap()); let mtime = FileTime::from_last_modification_time(&d.metadata().unwrap()); - let mtimedate = UTC.ymd(1970, 1, 1).and_hms(0, 0, 0) + - Duration::seconds(mtime.seconds_relative_to_1970() as i64); + let mtimedate = UTC.ymd(1970, 1, 1).and_hms(0, 0, 0) + + Duration::seconds(mtime.seconds_relative_to_1970() as i64); trace!("Found {} with mtime {}", pth.to_str().unwrap(), mtimedate); if mtimedate < cutoff { @@ -36,9 +39,9 @@ pub fn clean(cachedir: &str, days: i64) -> LalResult<()> { debug!("Cleaning all artifacts from before {}", cutoff); // clean out environment subdirectories - let edir = Path::new(&cachedir).join("environments"); - let edirs = WalkDir::new(&edir).min_depth(3).max_depth(3); - clean_in_dir(cutoff, edirs)?; + let dir = Path::new(&cachedir).join("environments"); + let dirs = WalkDir::new(&dir).min_depth(3).max_depth(3); + clean_in_dir(cutoff, dirs)?; // clean out stash let dirs = WalkDir::new(&cachedir).min_depth(3).max_depth(3); diff --git a/src/configure.rs b/src/configure.rs index ad001f43..85c0fec9 100644 --- a/src/configure.rs +++ b/src/configure.rs @@ -1,23 +1,27 @@ -use std::path::{Path, PathBuf}; -use std::fs; +use semver::Version; use std::env; +use std::fs; +use std::path::{Path, PathBuf}; use std::process::Command; -use semver::Version; -use super::{LalResult, Config, ConfigDefaults, CliError, config_dir}; +use super::{config_dir, CliError, Config, ConfigDefaults, LalResult}; fn executable_on_path(exe: &str) -> LalResult<()> { trace!("Verifying executable {}", exe); let s = Command::new("which").arg(exe).output()?; if !s.status.success() { - debug!("Failed to find {}: {}", - exe, - String::from_utf8_lossy(&s.stderr).trim()); + debug!( + "Failed to find {}: {}", + exe, + String::from_utf8_lossy(&s.stderr).trim() + ); return Err(CliError::ExecutableMissing(exe.into())); }; - debug!("Found {} at {}", - exe, - String::from_utf8_lossy(&s.stdout).trim()); + debug!( + "Found {} at {}", + exe, + String::from_utf8_lossy(&s.stdout).trim() + ); Ok(()) } @@ -51,24 +55,32 @@ fn kernel_sanity() -> LalResult<()> { match uname.trim().parse::() { Ok(ver) => { debug!("Found linux kernel version {}", ver); - trace!("found major {} minor {} patch {} - prelen {}", - ver.major, - ver.minor, - ver.patch, - ver.pre.len()); - trace!("req major {} minor {} patch {} - prelen {}", - req.major, - req.minor, - req.patch, - req.pre.len()); + trace!( + "found major {} minor {} patch {} - prelen {}", + ver.major, + ver.minor, + ver.patch, + ver.pre.len() + ); + trace!( + "req major {} minor {} patch {} - prelen {}", + req.major, + req.minor, + req.patch, + req.pre.len() + ); if ver >= req { - debug!("Minimum kernel requirement of {} satisfied ({})", - req.to_string(), - ver.to_string()); + debug!( + "Minimum kernel requirement of {} satisfied ({})", + req.to_string(), + ver.to_string() + ); } else { warn!("Your Linux kernel {} is very old", ver.to_string()); - warn!("A kernel >= {} is highly recommended on Linux systems", - req.to_string()) + warn!( + "A kernel >= {} is highly recommended on Linux systems", + req.to_string() + ) } } Err(e) => { @@ -94,29 +106,36 @@ fn docker_version_check() -> LalResult<()> { let dver_output = Command::new("docker").arg("--version").output()?; let dverstr = String::from_utf8_lossy(&dver_output.stdout); trace!("docker version string {}", dverstr); - let dverary = dverstr.trim().split(" ").collect::>(); + let dverary = dverstr.trim().split(' ').collect::>(); if dverary.len() < 3 { warn!("Failed to parse docker version: ({})", dverstr); return Ok(()); // assume it's a really weird docker } - let mut dver = dverary[2].to_string(); // third entry is the semver version - dver.pop(); // remove trailing comma (even if it goes, this parses) - match dver.parse::() { + let mut ver = dverary[2].to_string(); // third entry is the semver version + ver.pop(); // remove trailing comma (even if it goes, this parses) + match ver.parse::() { Ok(ver) => { debug!("Found docker version {}", ver); if ver < req { warn!("Your docker version {} is very old", ver.to_string()); - warn!("A docker version >= {} is highly recommended", - req.to_string()) + warn!( + "A docker version >= {} is highly recommended", + req.to_string() + ) } else { - debug!("Minimum docker requirement of {} satisfied ({})", - req.to_string(), - ver.to_string()); + debug!( + "Minimum docker requirement of {} satisfied ({})", + req.to_string(), + ver.to_string() + ); } } Err(e) => { - warn!("Failed to parse docker version from `docker --version`: {}", - e); + warn!( + "Failed to parse docker version from `docker --version`: {}", + e + ); + warn!("Found version {}", dverstr); warn!("Note that a docker version >= 1.12 is expected"); } } @@ -124,11 +143,11 @@ fn docker_version_check() -> LalResult<()> { } fn ssl_cert_sanity() -> LalResult<()> { + use openssl_probe; // SSL_CERT_FILE are overridden by openssl_probe in main.rs // BUT this happens AFTER lal configure // evars are currently empty (unless set manually) - so we can provide debug here let is_overridden = env::var_os("SSL_CERT_FILE").is_some(); - use openssl_probe; let proberes = openssl_probe::probe(); if let Some(cert) = proberes.cert_dir { debug!("Using SSL_CERT_DIR as {}", cert.display()); @@ -154,9 +173,11 @@ fn lal_version_check(minlal: &str) -> LalResult<()> { if current < req { Err(CliError::OutdatedLal(current.to_string(), req.to_string())) } else { - debug!("Minimum lal requirement of {} satisfied ({})", - req.to_string(), - current.to_string()); + debug!( + "Minimum lal requirement of {} satisfied ({})", + req.to_string(), + current.to_string() + ); Ok(()) } } @@ -196,17 +217,9 @@ fn create_lal_dir() -> LalResult { pub fn configure(save: bool, interactive: bool, defaults: &str) -> LalResult { let _ = create_lal_dir()?; - for exe in [ - "docker", - "tar", - "touch", - "id", - "find", - "mkdir", - "chmod", - "uname", - ].into_iter() - { + for exe in &[ + "docker", "tar", "touch", "id", "find", "mkdir", "chmod", "uname", + ] { executable_on_path(exe)?; } docker_sanity()?; diff --git a/src/core/config.rs b/src/core/config.rs index e5cf3b0b..17ed12fc 100644 --- a/src/core/config.rs +++ b/src/core/config.rs @@ -1,21 +1,21 @@ -use serde_json; use chrono::UTC; -use std::path::{Path, PathBuf}; -use std::fs; -use std::vec::Vec; -use std::io::prelude::*; +use serde_json; use std::collections::BTreeMap; use std::env; +use std::fs; +use std::io::prelude::*; +use std::path::{Path, PathBuf}; +use std::vec::Vec; -use super::{Container, LalResult, CliError}; -use storage::BackendConfiguration; +use super::{CliError, Container, LalResult}; +use crate::storage::BackendConfiguration; fn find_home_dir() -> PathBuf { // Either we have LAL_CONFIG_HOME evar, or HOME if let Ok(lh) = env::var("LAL_CONFIG_HOME") { Path::new(&lh).to_owned() } else { - env::home_dir().unwrap() + dirs::home_dir().unwrap() } } @@ -75,15 +75,20 @@ pub struct ConfigDefaults { impl ConfigDefaults { /// Open and deserialize a defaults file - pub fn read(file: &str) -> LalResult { + pub fn read(file: &str) -> LalResult { let pth = Path::new(file); if !pth.exists() { - error!("No such defaults file '{}'", file); // file open will fail below + // Panics only if the current directory does not exist or the user does not have permissions to see the current directory. + error!( + "No such defaults file '{}' found from path '{:#?}", + file, + std::env::current_dir().unwrap() + ); // file open will fail below } let mut f = fs::File::open(&pth)?; let mut data = String::new(); f.read_to_string(&mut data)?; - let defaults: ConfigDefaults = serde_json::from_str(&data)?; + let defaults: Self = serde_json::from_str(&data)?; Ok(defaults) } } @@ -98,10 +103,15 @@ fn check_mount(name: &str) -> LalResult { return Ok(src.clone()); // pass along the real source } - // Otherwise, if it does not contain a slash - if !name.contains("/") { + if name.contains('/') { + warn!("Discarding missing mount {}", src); + Err(CliError::MissingMount(src.clone())) + } else { + // Otherwise, if it does not contain a slash use std::process::Command; - let volume_output = Command::new("docker").args(vec!["volume", "ls", "-q"]).output()?; + let volume_output = Command::new("docker") + .args(vec!["volume", "ls", "-q"]) + .output()?; let volstr = String::from_utf8_lossy(&volume_output.stdout); // If it exists, do nothing: if volstr.contains(name) { @@ -111,19 +121,15 @@ fn check_mount(name: &str) -> LalResult { // Otherwise warn warn!("Discarding missing docker volume {}", name); Err(CliError::MissingMount(name.into())) - } else { - warn!("Discarding missing mount {}", src); - Err(CliError::MissingMount(src.clone())) } } - impl Config { /// Initialize a Config with ConfigDefaults /// /// This will locate you homedir, and set last update check 2 days in the past. /// Thus, with a blank default config, you will always trigger an upgrade check. - pub fn new(defaults: ConfigDefaults) -> Config { + pub fn new(defaults: ConfigDefaults) -> Self { let cachepath = config_dir().join("cache"); let cachedir = cachepath.as_path().to_str().unwrap(); @@ -144,9 +150,9 @@ impl Config { } } - Config { + Self { cache: cachedir.into(), - mounts: mounts, // the filtered defaults + mounts, // the filtered defaults lastUpgrade: time.to_rfc3339(), autoupgrade: cfg!(feature = "upgrade"), environments: defaults.environments, @@ -157,7 +163,7 @@ impl Config { } /// Read and deserialize a Config from ~/.lal/config - pub fn read() -> LalResult { + pub fn read() -> LalResult { let cfg_path = config_dir().join("config"); if !cfg_path.exists() { return Err(CliError::MissingConfig); @@ -165,14 +171,14 @@ impl Config { let mut f = fs::File::open(&cfg_path)?; let mut cfg_str = String::new(); f.read_to_string(&mut cfg_str)?; - let res: Config = serde_json::from_str(&cfg_str)?; + let res: Self = serde_json::from_str(&cfg_str)?; Ok(res) } /// Checks if it is time to perform an upgrade check #[cfg(feature = "upgrade")] pub fn upgrade_check_time(&self) -> bool { - use chrono::{Duration, DateTime}; + use chrono::{DateTime, Duration}; let last = self.lastUpgrade.parse::>().unwrap(); let cutoff = UTC::now() - Duration::days(1); last < cutoff @@ -181,7 +187,7 @@ impl Config { #[cfg(feature = "upgrade")] pub fn performed_upgrade(&mut self) -> LalResult<()> { self.lastUpgrade = UTC::now().to_rfc3339(); - Ok(self.write(true)?) + self.write(true) } /// Overwrite `~/.lal/config` with serialized data from this struct @@ -190,7 +196,7 @@ impl Config { let encoded = serde_json::to_string_pretty(self)?; let mut f = fs::File::create(&cfg_path)?; - write!(f, "{}\n", encoded)?; + writeln!(f, "{}", encoded)?; if !silent { info!("Wrote config to {}", cfg_path.display()); } diff --git a/src/core/ensure.rs b/src/core/ensure.rs index 7c4f2cd6..70d30e51 100644 --- a/src/core/ensure.rs +++ b/src/core/ensure.rs @@ -1,7 +1,6 @@ -use std::path::Path; use std::fs; use std::io; - +use std::path::Path; /// Ensure a directory exists and is empty pub fn ensure_dir_exists_fresh(dir: &str) -> io::Result<()> { diff --git a/src/core/errors.rs b/src/core/errors.rs index a7cc9f06..ec537d86 100644 --- a/src/core/errors.rs +++ b/src/core/errors.rs @@ -1,7 +1,8 @@ -use std::fmt; -use std::io; use hyper; use serde_json; +use std::env; +use std::fmt; +use std::io; /// The one and only error type for the lal library /// @@ -26,6 +27,12 @@ pub enum CliError { MissingComponent(String), /// Value in manifest is not lowercase InvalidComponentName(String), + /// Value in manifest is not a well-formatted channel + InvalidChannelName(String), + /// Value in manifest contains invalid characters + InvalidChannelCharacter(String), + /// Attempt to set testing channel outside of tooling + InvalidTestingChannel(String), /// Manifest cannot be overwritten without forcing ManifestExists, /// Executable we shell out to is missing @@ -42,6 +49,8 @@ pub enum CliError { // status/verify errors /// Core dependencies missing in INPUT MissingDependencies, + /// Channel hierarchy not maintained + ChannelMismatch(String, String), /// Cyclical dependency loop found in INPUT DependencyCycle(String), /// Dependency present at wrong version @@ -125,33 +134,34 @@ pub enum CliError { // Format implementation used when printing an error impl fmt::Display for CliError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { CliError::Io(ref err) => { let knd = err.kind(); if knd == io::ErrorKind::PermissionDenied { - warn!("If you are on norman - ensure you have access to clean ./OUTPUT and \ - ./INPUT"); + warn!( + "If you are on norman - ensure you have access to clean ./OUTPUT and \ + ./INPUT" + ); } err.fmt(f) } CliError::Parse(ref err) => err.fmt(f), CliError::Hype(ref err) => err.fmt(f), - CliError::MissingManifest => { - write!(f, - "No manifest.json found - are you at repository toplevel?") - } - CliError::ExecutableMissing(ref s) => { - write!(f, - "Please ensure you have `{}` installed on your system first.", - s) - } - CliError::OutdatedLal(ref o, ref n) => { - write!(f, - "Your version of lal `{}` is too old (<{}). Please `lal upgrade`.", - o, - n) - } + CliError::MissingManifest => write!( + f, + "No manifest.json found - are you at repository toplevel?" + ), + CliError::ExecutableMissing(ref s) => write!( + f, + "Please ensure you have `{}` installed on your system first.", + s + ), + CliError::OutdatedLal(ref o, ref n) => write!( + f, + "Your version of lal `{}` is too old (<{}). Please `lal upgrade`.", + o, n + ), CliError::MissingSslCerts => write!(f, "Missing SSL certificates"), CliError::UnmappableRootUser => write!(f, "Root user is not supported for lal builds"), CliError::MissingMount(ref s) => write!(f, "Missing mount {}", s), @@ -159,14 +169,24 @@ impl fmt::Display for CliError { CliError::MissingComponent(ref s) => { write!(f, "Component '{}' not found in manifest", s) } + CliError::ChannelMismatch(ref a, ref b) => { + write!(f, "Channel {} is not a valid parent of channel {}", a, b) + } CliError::InvalidComponentName(ref s) => { write!(f, "Invalid component name {} - not lowercase", s) } + CliError::InvalidChannelName(ref s) => write!(f, "Invalid channel {}", s), + CliError::InvalidChannelCharacter(ref s) => write!(f, "Invalid channel {}, byte representation {:#?}", s, s.as_bytes()), + CliError::InvalidTestingChannel(ref s) => write!( + f, + "Invalid channel {} - testing components are leaf only components for use in tooling only", + s + ), CliError::ManifestExists => write!(f, "Manifest already exists (use -f to force)"), - CliError::MissingDependencies => { - write!(f, - "Core dependencies missing in INPUT - try `lal fetch` first") - } + CliError::MissingDependencies => write!( + f, + "Core dependencies missing in INPUT - try `lal fetch` first" + ), CliError::DependencyCycle(ref s) => { write!(f, "Cyclical dependencies found for {} in INPUT", s) } @@ -186,17 +206,18 @@ impl fmt::Display for CliError { CliError::EnvironmentMismatch(ref dep, ref env) => { write!(f, "Environment mismatch for {} - built in {}", dep, env) } - CliError::NonGlobalDependencies(ref s) => { - write!(f, - "Depending on a custom version of {} (use -s to allow stashed versions)", - s) - } + CliError::NonGlobalDependencies(ref s) => write!( + f, + "Depending on a custom version of {} (use -s to allow stashed versions)", + s + ), CliError::NoSupportedEnvironments => { write!(f, "Need to specify supported environments in the manifest") } - CliError::UnsupportedEnvironment => { - write!(f, "manifest.environment must exist in manifest.supportedEnvironments") - } + CliError::UnsupportedEnvironment => write!( + f, + "manifest.environment must exist in manifest.supportedEnvironments" + ), CliError::MissingEnvironment(ref s) => { write!(f, "Environment '{}' not found in ~/.lal/config", s) } @@ -213,24 +234,26 @@ impl fmt::Display for CliError { CliError::MissingScript(ref s) => { write!(f, "Missing script '{}' in local folder .lal/scripts/", s) } - CliError::MissingTarball => write!(f, "Tarball missing in PWD"), + CliError::MissingTarball => write!( + f, + "Tarball missing in {}", + env::current_dir().unwrap().to_string_lossy() + ), CliError::MissingBuild => write!(f, "No build found in OUTPUT"), - CliError::InvalidStashName(n) => { - write!(f, - "Invalid name '{}' to stash under - must not be an integer", - n) - } + CliError::InvalidStashName(n) => write!( + f, + "Invalid name '{}' to stash under - must not be an integer", + n + ), CliError::MissingStashArtifact(ref s) => { write!(f, "No stashed artifact '{}' found in ~/.lal/cache/stash", s) } CliError::SubprocessFailure(n) => write!(f, "Process exited with {}", n), - CliError::DockerPermissionSafety(ref s, u, g) => { - write!(f, - "ID mismatch inside and outside docker - {}; UID and GID are {}:{}", - s, - u, - g) - } + CliError::DockerPermissionSafety(ref s, u, g) => write!( + f, + "ID mismatch inside and outside docker - {}; UID and GID are {}:{}", + s, u, g + ), CliError::DockerImageNotFound(ref s) => write!(f, "Could not find docker image {}", s), CliError::InstallFailure => write!(f, "Install failed"), CliError::BackendFailure(ref s) => write!(f, "Backend - {}", s), @@ -241,17 +264,16 @@ impl fmt::Display for CliError { CliError::MissingBackendCredentials => { write!(f, "Missing backend credentials in ~/.lal/config") } - CliError::MissingPrefixPermissions(ref s) => { - write!(f, - "No write access in {} - consider chowning: `sudo chown -R $USER {}`", - s, - s) - } - CliError::UpgradeValidationFailure(ref s) => { - write!(f, - "Failed to validate new lal version - rolling back ({})", - s) - } + CliError::MissingPrefixPermissions(ref s) => write!( + f, + "No write access in {} - consider chowning: `sudo chown -R $USER {}`", + s, s + ), + CliError::UpgradeValidationFailure(ref s) => write!( + f, + "Failed to validate new lal version - rolling back ({})", + s + ), CliError::UploadFailure(ref up) => write!(f, "Upload failure: {}", up), } } @@ -259,15 +281,15 @@ impl fmt::Display for CliError { // Allow io and json errors to be converted to `CliError` in a try! without map_err impl From for CliError { - fn from(err: io::Error) -> CliError { CliError::Io(err) } + fn from(err: io::Error) -> Self { CliError::Io(err) } } impl From for CliError { - fn from(err: hyper::Error) -> CliError { CliError::Hype(err) } + fn from(err: hyper::Error) -> Self { CliError::Hype(err) } } impl From for CliError { - fn from(err: serde_json::error::Error) -> CliError { CliError::Parse(err) } + fn from(err: serde_json::error::Error) -> Self { CliError::Parse(err) } } /// Type alias to stop having to type out `CliError` everywhere. diff --git a/src/core/input.rs b/src/core/input.rs index 133b58a9..3b0b2f16 100644 --- a/src/core/input.rs +++ b/src/core/input.rs @@ -1,14 +1,15 @@ #![allow(missing_docs)] -use std::io::prelude::*; +use crate::channel::Channel; +use serde_json; +use std::collections::BTreeMap; use std::fs::File; +use std::io::prelude::*; use std::path::Path; -use std::collections::BTreeMap; -use serde_json; use walkdir::WalkDir; -use super::{Manifest, Lockfile, CliError, LalResult}; +use super::{CliError, Coordinates, LalResult, Lockfile, Manifest}; #[derive(Deserialize)] struct PartialLock { @@ -25,9 +26,7 @@ fn read_partial_lockfile(component: &str) -> LalResult { Ok(serde_json::from_str(&lock_str)?) } -pub fn present() -> bool { - Path::new("./INPUT").is_dir() -} +pub fn present() -> bool { Path::new("./INPUT").is_dir() } /// Simple INPUT analyzer for the lockfile generator and `analyze_full` pub fn analyze() -> LalResult> { @@ -41,7 +40,7 @@ pub fn analyze() -> LalResult> { .min_depth(1) .max_depth(1) .into_iter() - .filter_map(|e| e.ok()) + .filter_map(Result::ok) .filter(|e| e.path().is_dir()); for d in dirs { @@ -59,7 +58,7 @@ pub struct InputDependency { pub missing: bool, pub extraneous: bool, pub development: bool, - pub version: String, // on disk + pub version: String, // on disk pub requirement: Option, // from manifest } @@ -85,30 +84,34 @@ pub fn analyze_full(manifest: &Manifest) -> LalResult { Some(v) => v.clone(), None => v.to_string(), }; - depmap.insert(d.clone(), - InputDependency { - name: d.clone(), - version: version, - requirement: Some(format!("{}", v)), - missing: deps.get(&d).is_none(), - development: manifest.devDependencies.contains_key(&d), - extraneous: false, - }); + depmap.insert( + d.clone(), + InputDependency { + name: d.clone(), + version, + requirement: Some(format!("{}", v)), + missing: deps.get(&d).is_none(), + development: manifest.devDependencies.contains_key(&d), + extraneous: false, + }, + ); } // check for potentially non-manifested deps // i.e. something in INPUT, but not in manifest for name in deps.keys() { let actual_ver = deps[name].clone(); if !saved_deps.contains_key(name) { - depmap.insert(name.clone(), - InputDependency { - name: name.clone(), - version: actual_ver, - requirement: None, - missing: false, - development: false, - extraneous: true, - }); + depmap.insert( + name.clone(), + InputDependency { + name: name.clone(), + version: actual_ver, + requirement: None, + missing: false, + development: false, + extraneous: true, + }, + ); } } @@ -123,7 +126,7 @@ pub fn verify_dependencies_present(m: &Manifest) -> LalResult<()> { .min_depth(1) .max_depth(1) .into_iter() - .filter_map(|e| e.ok()) + .filter_map(Result::ok) .filter(|e| e.path().is_dir()); for entry in dirs { let pth = entry.path().strip_prefix("INPUT").unwrap(); @@ -142,31 +145,34 @@ pub fn verify_dependencies_present(m: &Manifest) -> LalResult<()> { error = Some(CliError::MissingDependencies); } } - if let Some(e) = error { Err(e) } else { Ok(()) } + if let Some(e) = error { + Err(e) + } else { + Ok(()) + } } /// Optional part of input verifier - checks that all versions use correct versions pub fn verify_global_versions(lf: &Lockfile, m: &Manifest) -> LalResult<()> { let all_deps = m.all_dependencies(); for (name, dep) in &lf.dependencies { - let v = dep.version - .parse::() - .map_err(|e| { - debug!("Failed to parse first version of {} as int ({:?})", name, e); - CliError::NonGlobalDependencies(name.clone()) - })?; + let v = dep.version.parse::().map_err(|e| { + debug!("Failed to parse first version of {} as int ({:?})", name, e); + CliError::NonGlobalDependencies(name.clone()) + })?; // also ensure it matches the version in the manifest - let vreq = *all_deps - .get(name) - .ok_or_else(|| { - // This is a first level dependency - it should be in the manifest - CliError::ExtraneousDependencies(name.clone()) - })?; + let vreq = match all_deps.get(name).ok_or_else(|| { + // This is a first level dependency - it should be in the manifest + CliError::ExtraneousDependencies(name.clone()) + })? { + Coordinates::OneD(v) => *v, + Coordinates::TwoD(c) => c.version, + }; if v != vreq { - warn!("Dependency {} has version {}, but manifest requires {}", - name, - v, - vreq); + warn!( + "Dependency {} has version {}, but manifest requires {}", + name, v, vreq + ); return Err(CliError::InvalidVersion(name.clone())); } // Prevent Cycles (enough to stop it at one manifest level) @@ -177,17 +183,36 @@ pub fn verify_global_versions(lf: &Lockfile, m: &Manifest) -> LalResult<()> { Ok(()) } +/// Optional part of input verifier - checks that the Channel hierarchy is maintained. +/// i.e dependencies of Channel "/a/b" must be in channel "/a/b", "/a", or "/". +pub fn verify_global_channels(lf: &Lockfile) -> LalResult<()> { + let channel = Channel::from_option(&lf.channel); + channel.verify()?; + for dep_lf in lf.dependencies.values() { + let dep_ch = Channel::from_option(&dep_lf.channel); + channel.contains(&dep_ch)?; + + verify_global_channels(&dep_lf)?; + } + + Ok(()) +} + /// Strict requirement for verifier - dependency tree must be flat-equivalent pub fn verify_consistent_dependency_versions(lf: &Lockfile, m: &Manifest) -> LalResult<()> { for (name, vers) in lf.find_all_dependency_versions() { debug!("Found version(s) for {} as {:?}", name, vers); assert!(!vers.is_empty(), "found versions"); if vers.len() != 1 && m.dependencies.contains_key(&name) { - warn!("Multiple version requirements on {} found in lockfile", - name.clone()); - warn!("If you are trying to propagate {0} into the tree, \ - you need to follow `lal propagate {0}`", - name); + warn!( + "Multiple version requirements on {} found in lockfile", + name.clone() + ); + warn!( + "If you are trying to propagate {0} into the tree, \ + you need to follow `lal propagate {0}`", + name + ); return Err(CliError::MultipleVersions(name.clone())); } } @@ -198,15 +223,107 @@ pub fn verify_consistent_dependency_versions(lf: &Lockfile, m: &Manifest) -> Lal pub fn verify_environment_consistency(lf: &Lockfile, env: &str) -> LalResult<()> { for (name, envs) in lf.find_all_environments() { debug!("Found environment(s) for {} as {:?}", name, envs); - if envs.len() != 1 { - warn!("Multiple environments used to build {}", name.clone()); - return Err(CliError::MultipleEnvironments(name.clone())); - } else { + if envs.len() == 1 { let used_env = envs.iter().next().unwrap(); if used_env != env { - return Err(CliError::EnvironmentMismatch(name.clone(), used_env.clone())); + return Err(CliError::EnvironmentMismatch( + name.clone(), + used_env.clone(), + )); } + } else { + warn!("Multiple environments used to build {}", name.clone()); + return Err(CliError::MultipleEnvironments(name.clone())); } } Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + + fn assert_consistent(lf: Lockfile) { + let result = verify_global_channels(&lf); + assert!(result.is_ok()) + } + + fn assert_invalid(lf: Lockfile) { + let result = verify_global_channels(&lf); + assert!(result.is_err()) + } + + #[test] + fn both_default_is_valid() { + let mut main_lf = Lockfile::default().with_channel(None); + let dep_lf = Lockfile::default().with_channel(None); + + main_lf.dependencies.insert("".to_string(), dep_lf); + + assert_consistent(main_lf); + } + + #[test] + fn test_consistent_recursion() { + let mut main_lf = Lockfile::default().with_channel(None); + let mut dep_lf = Lockfile::default().with_channel(None); + let dep_dep_lf = Lockfile::default().with_channel(None); + + dep_lf.dependencies.insert("".to_string(), dep_dep_lf); + main_lf.dependencies.insert("".to_string(), dep_lf); + + assert_consistent(main_lf); + } + + #[test] + fn default_channel_cannot_have_child_channel() { + let mut main_lf = Lockfile::default().with_channel(None); + let dep_lf = Lockfile::default().with_channel(Some("/a".to_string())); + + main_lf.dependencies.insert("".to_string(), dep_lf); + + assert_invalid(main_lf); + } + + #[test] + fn test_invalid_recursion() { + let mut main_lf = Lockfile::default().with_channel(None); + let mut dep_lf = Lockfile::default().with_channel(None); + let dep_dep_lf = Lockfile::default().with_channel(Some("/a".to_string())); + + dep_lf.dependencies.insert("".to_string(), dep_dep_lf); + main_lf.dependencies.insert("".to_string(), dep_lf); + + assert_invalid(main_lf); + } + + #[test] + fn explicit_parent_with_same_child_consistent() { + let mut main_lf = Lockfile::default().with_channel(Some("/a".to_string())); + let dep_lf = Lockfile::default().with_channel(Some("/a".to_string())); + + main_lf.dependencies.insert("".to_string(), dep_lf); + + assert_consistent(main_lf); + } + + #[test] + fn explicit_parent_with_different_child_invalid() { + let mut main_lf = Lockfile::default().with_channel(Some("/a".to_string())); + let dep_lf = Lockfile::default().with_channel(Some("/b".to_string())); + + main_lf.dependencies.insert("".to_string(), dep_lf); + + assert_invalid(main_lf); + } + + #[test] + fn child_contains_parent_valid() { + let mut main_lf = Lockfile::default().with_channel(Some("/a/b".to_string())); + let dep_lf = Lockfile::default().with_channel(Some("/a".to_string())); + + main_lf.dependencies.insert("".to_string(), dep_lf); + + assert_consistent(main_lf); + } +} diff --git a/src/core/lockfile.rs b/src/core/lockfile.rs index ce7f3f98..e27fb263 100644 --- a/src/core/lockfile.rs +++ b/src/core/lockfile.rs @@ -1,16 +1,17 @@ -use serde_json; use chrono::UTC; use rand; +use serde_json; +use std::fmt::Debug; -use std::path::{Path, PathBuf}; use std::fs::File; use std::io::prelude::*; +use std::path::{Path, PathBuf}; -use std::collections::{HashMap, BTreeMap}; use std::collections::BTreeSet; +use std::collections::{BTreeMap, HashMap}; use std::fmt; -use super::{CliError, LalResult, input}; +use super::{input, CliError, LalResult}; /// Representation of a docker container image #[derive(Serialize, Deserialize, Debug, Clone)] @@ -24,7 +25,7 @@ pub struct Container { impl Container { /// Container struct with latest tag pub fn latest(name: &str) -> Self { - Container { + Self { name: name.into(), tag: "latest".into(), } @@ -39,7 +40,7 @@ impl fmt::Display for Container { /// Intentionally kept distinct from normal build images impl Default for Container { fn default() -> Self { - Container { + Self { name: "ubuntu".into(), tag: "xenial".into(), } @@ -51,11 +52,15 @@ impl Container { /// /// This will split the container on `:` to actually fetch the tag, and if no tag /// was present, it will assume tag is latest as per docker conventions. - pub fn new(container: &str) -> Container { + pub fn new(container: &str) -> Self { let split: Vec<&str> = container.split(':').collect(); let tag = if split.len() == 2 { split[1] } else { "latest" }; - let cname = if split.len() == 2 { split[0] } else { container }; - Container { + let cname = if split.len() == 2 { + split[0] + } else { + container + }; + Self { name: cname.into(), tag: tag.into(), } @@ -80,6 +85,8 @@ pub struct Lockfile { pub sha: Option, /// Version of the component built pub version: String, + /// Channel of the component built + pub channel: Option, /// Version of the lal tool pub tool: String, /// Built timestamp @@ -90,7 +97,7 @@ pub struct Lockfile { /// Generates a temporary empty lockfile for internal analysis impl Default for Lockfile { - fn default() -> Self { Lockfile::new("templock", &Container::default(), "none", None, None) } + fn default() -> Self { Self::new("templock", &Container::default(), "none", None, None) } } impl Lockfile { @@ -106,9 +113,10 @@ impl Lockfile { ) -> Self { let def_version = format!("EXPERIMENTAL-{:x}", rand::random::()); let time = UTC::now(); - Lockfile { + Self { name: name.to_string(), version: v.unwrap_or(def_version), + channel: None, config: build_cfg.unwrap_or("release").to_string(), container: container.clone(), tool: env!("CARGO_PKG_VERSION").to_string(), @@ -120,6 +128,12 @@ impl Lockfile { } } + /// Provide a channel for the lockfile. + pub fn with_channel(mut self, channel: Option) -> Self { + self.channel = channel; + self + } + /// Opened lockfile at a path pub fn from_path(lock_path: &PathBuf, name: &str) -> LalResult { if !lock_path.exists() { @@ -133,16 +147,15 @@ impl Lockfile { /// A reader from ARTIFACT directory pub fn release_build() -> LalResult { let lpath = Path::new("ARTIFACT").join("lockfile.json"); - Ok(Lockfile::from_path(&lpath, "release build")?) + Ok(Self::from_path(&lpath, "release build")?) } // Helper constructor for input populator below fn from_input_component(component: &str) -> LalResult { let lock_path = Path::new("./INPUT").join(component).join("lockfile.json"); - Ok(Lockfile::from_path(&lock_path, component)?) + Ok(Self::from_path(&lock_path, component)?) } - /// Read all the lockfiles in INPUT to generate the full lockfile /// /// NB: This currently reads all the lockfiles partially in `analyze`, @@ -152,7 +165,7 @@ impl Lockfile { let deps = input::analyze()?; for name in deps.keys() { trace!("Populating lockfile with {}", name); - let deplock = Lockfile::from_input_component(name)?; + let deplock = Self::from_input_component(name)?; self.dependencies.insert(name.clone(), deplock); } Ok(self) @@ -180,31 +193,24 @@ impl Lockfile { pub fn write(&self, pth: &Path) -> LalResult<()> { let encoded = serde_json::to_string_pretty(self)?; let mut f = File::create(pth)?; - write!(f, "{}\n", encoded)?; + writeln!(f, "{}", encoded)?; debug!("Wrote lockfile {}: \n{}", pth.display(), encoded); Ok(()) } } - // name of component -> (value1, value2, ..) -pub type ValueUsage = HashMap>; +pub type ValueUsageGen = HashMap>; +pub type ValueUsage = ValueUsageGen; // The hardcore dependency analysis parts impl Lockfile { - // helper to extract specific keys out of a struct - fn get_value(&self, key: &str) -> String { - if key == "version" { - self.version.clone() - } else if key == "environment" { - self.environment.clone() - } else { - unreachable!("Only using get_value internally"); - } - } - /// Recursive function to check for multiple version/environment (key) use - fn find_all_values(&self, key: &str) -> ValueUsage { + fn find_all_values(&self, f: &F) -> ValueUsageGen + where + F: Fn(&Self) -> T, + T: Ord + Debug, + { let mut acc = HashMap::new(); // for each entry in dependencies for (main_name, dep) in &self.dependencies { @@ -215,17 +221,13 @@ impl Lockfile { { // Only borrow as mutable once - so creating a temporary scope let first_value_set = acc.get_mut(main_name).unwrap(); - first_value_set.insert(dep.get_value(key)); + first_value_set.insert(f(dep)); } // Recurse into its dependencies trace!("Recursing into deps for {}, acc is {:?}", main_name, acc); - for (name, value_set) in dep.find_all_values(key) { - trace!("Found {} for for {} under {} as {:?}", - key, - name, - main_name, - value_set); + for (name, value_set) in dep.find_all_values(f) { + trace!("Found for {} under {} as {:?}", name, main_name, value_set); // ensure each entry from above exists in current accumulator if !acc.contains_key(&name) { acc.insert(name.clone(), BTreeSet::new()); @@ -240,11 +242,20 @@ impl Lockfile { acc } - /// List all used versions used of each dependency - pub fn find_all_dependency_versions(&self) -> ValueUsage { self.find_all_values("version") } + /// List the versions used by each dependency + pub fn find_all_dependency_versions(&self) -> ValueUsage { + self.find_all_values(&|lf| lf.version.clone()) + } + + /// List the environments used by each dependency + pub fn find_all_environments(&self) -> ValueUsage { + self.find_all_values(&|lf| lf.environment.clone()) + } - /// List all used environments used of each dependency - pub fn find_all_environments(&self) -> ValueUsage { self.find_all_values("environment") } + /// List the channels used by each dependency + pub fn find_all_channels(&self) -> ValueUsageGen> { + self.find_all_values(&|lf| lf.channel.clone()) + } /// List all dependency names used by each dependency (not transitively) pub fn find_all_dependency_names(&self) -> ValueUsage { @@ -299,7 +310,7 @@ impl Lockfile { } // merge in values from recursion let full_value_set = acc.get_mut(&name).unwrap(); // know this exists now - // union in values from recursion + // union in values from recursion for value in value_set { full_value_set.insert(value); } @@ -315,9 +326,10 @@ impl Lockfile { let mut res = BTreeSet::new(); if !revdeps.contains_key(&component) { - warn!("Could not find {} in the dependency tree for {}", - component, - self.name); + warn!( + "Could not find {} in the dependency tree for {}", + component, self.name + ); return res; } diff --git a/src/core/manifest.rs b/src/core/manifest.rs index 703f5bab..8e236e78 100644 --- a/src/core/manifest.rs +++ b/src/core/manifest.rs @@ -1,11 +1,13 @@ -use std::io::prelude::*; -use std::fs::{self, File}; -use std::collections::BTreeMap; -use std::vec::Vec; use serde_json; +use std::collections::BTreeMap; +use std::fs::{self, File}; +use std::io::prelude::*; use std::path::{Path, PathBuf}; +use std::vec::Vec; use super::{CliError, LalResult}; +use crate::channel::Channel; +use crate::verify; /// A startup helper used in a few places pub fn create_lal_subdir(pwd: &PathBuf) -> LalResult<()> { @@ -27,20 +29,59 @@ pub struct ComponentConfiguration { } impl Default for ComponentConfiguration { - fn default() -> ComponentConfiguration { - ComponentConfiguration { + fn default() -> Self { + Self { configurations: vec!["release".to_string()], defaultConfig: "release".to_string(), } } } +/// Coordinates when a channel and version are present. +#[derive(Serialize, Deserialize, Clone)] +pub struct TwoDCoordinates { + /// Channel + pub channel: String, + /// Version + pub version: u32, +} + +impl std::fmt::Display for TwoDCoordinates { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}/{}", self.channel, self.version) + } +} + +/// Coordinates enum, maps to the various ways a build's coordinates can be described. +#[derive(Serialize, Deserialize, Clone)] +#[serde(untagged)] +pub enum Coordinates { + /// Coordinates with only a version + OneD(u32), + /// Coordinates with a version and a channel + TwoD(TwoDCoordinates), +} + +impl std::fmt::Display for Coordinates { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Coordinates::OneD(c) => write!(f, "{}", c), + Coordinates::TwoD(c) => write!(f, "{}", c), + } + } +} + +/// Type alias used for manifest dependencies. +type Dep = BTreeMap; + /// Representation of `manifest.json` #[allow(non_snake_case)] #[derive(Serialize, Deserialize, Clone, Default)] pub struct Manifest { /// Name of the main component pub name: String, + /// Channel the component is currently in + pub channel: Option, /// Default environment to build in pub environment: String, /// All the environments dependencies can currently be found in @@ -48,9 +89,9 @@ pub struct Manifest { /// Components and their available configurations that are buildable pub components: BTreeMap, /// Dependencies that are always needed - pub dependencies: BTreeMap, + pub dependencies: Dep, /// Development dependencies - pub devDependencies: BTreeMap, + pub devDependencies: Dep, /// Internal path of this manifest #[serde(skip_serializing, skip_deserializing)] @@ -65,7 +106,7 @@ pub enum ManifestLocation { LalSubfolder, } impl Default for ManifestLocation { - fn default() -> ManifestLocation { ManifestLocation::LalSubfolder } + fn default() -> Self { ManifestLocation::LalSubfolder } } impl ManifestLocation { /// Generate path for Manifest assuming pwd is the root @@ -79,7 +120,7 @@ impl ManifestLocation { /// Find the manifest file /// /// Looks first in `./.lal/manifest.json` and falls back to `./manifest.json` - pub fn identify(pwd: &PathBuf) -> LalResult { + pub fn identify(pwd: &PathBuf) -> LalResult { if ManifestLocation::LalSubfolder.as_path(pwd).exists() { // Show a warning if we have two manifests - we only use the new one then // This could happen on other codebases - some javascript repos use manifest.json @@ -97,43 +138,42 @@ impl ManifestLocation { } } - impl Manifest { /// Initialize a manifest struct based on a name /// /// The name is assumed to be the default component and will create a /// component configuration for it with its default values. - pub fn new(name: &str, env: &str, location: PathBuf) -> Manifest { + pub fn new(name: &str, env: &str, location: &PathBuf) -> Self { let mut comps = BTreeMap::new(); comps.insert(name.into(), ComponentConfiguration::default()); - Manifest { + Self { name: name.into(), components: comps, environment: env.into(), supportedEnvironments: vec![env.into()], location: location.to_string_lossy().into(), - ..Default::default() + ..Self::default() } } /// Merge dependencies and devDependencies into one convenience map - pub fn all_dependencies(&self) -> BTreeMap { + pub fn all_dependencies(&self) -> Dep { let mut deps = self.dependencies.clone(); for (k, v) in &self.devDependencies { - deps.insert(k.clone(), *v); + deps.insert(k.clone(), v.clone()); } deps } /// Read a manifest file in PWD - pub fn read() -> LalResult { Ok(Manifest::read_from(&Path::new(".").to_path_buf())?) } + pub fn read() -> LalResult { Ok(Self::read_from(&Path::new(".").to_path_buf())?) } /// Read a manifest file in an arbitrary path - pub fn read_from(pwd: &PathBuf) -> LalResult { + pub fn read_from(pwd: &PathBuf) -> LalResult { let mpath = ManifestLocation::identify(pwd)?.as_path(pwd); trace!("Using manifest in {}", mpath.display()); let mut f = File::open(&mpath)?; let mut data = String::new(); f.read_to_string(&mut data)?; - let mut res: Manifest = serde_json::from_str(&data)?; + let mut res: Self = serde_json::from_str(&data)?; // store the location internally (not serialized to disk) res.location = mpath.to_string_lossy().into(); Ok(res) @@ -144,13 +184,13 @@ impl Manifest { let encoded = serde_json::to_string_pretty(self)?; trace!("Writing manifest in {}", self.location); let mut f = File::create(&self.location)?; - write!(f, "{}\n", encoded)?; + writeln!(f, "{}", encoded)?; debug!("Wrote manifest in {}: \n{}", self.location, encoded); Ok(()) } /// Verify assumptions about configurations - pub fn verify(&self) -> LalResult<()> { + pub fn verify(&self, flags: verify::Flags) -> LalResult<()> { for (name, conf) in &self.components { if &name.to_lowercase() != name { return Err(CliError::InvalidComponentName(name.clone())); @@ -158,27 +198,107 @@ impl Manifest { // Verify ComponentSettings (manifest.components[x]) debug!("Verifying component {}", name); if !conf.configurations.contains(&conf.defaultConfig) { - let ename = format!("default configuration '{}' not found in configurations list", - conf.defaultConfig); + let ename = format!( + "default configuration '{}' not found in configurations list", + conf.defaultConfig + ); return Err(CliError::InvalidBuildConfiguration(ename)); } } - for (name, _) in &self.dependencies { + for (name, values) in self.dependencies.iter().chain(self.devDependencies.iter()) { if &name.to_lowercase() != name { return Err(CliError::InvalidComponentName(name.clone())); } - } - for (name, _) in &self.devDependencies { - if &name.to_lowercase() != name { - return Err(CliError::InvalidComponentName(name.clone())); + + match values { + Coordinates::OneD(_) => (), + Coordinates::TwoD(v) => { + let child = Channel::from_option(&self.channel); + let parent = Channel::new(&v.channel); + child.contains(&parent)?; + } } } if self.supportedEnvironments.is_empty() { return Err(CliError::NoSupportedEnvironments); } - if !self.supportedEnvironments.iter().any(|x| x == &self.environment) { + if !self + .supportedEnvironments + .iter() + .any(|x| x == &self.environment) + { return Err(CliError::UnsupportedEnvironment); } + if let Some(ch) = &self.channel { + let channel = Channel::new(ch); + if channel.to_string() != *ch { + return Err(CliError::InvalidChannelName(ch.to_string())); + } + + let allow_testing = flags.contains(verify::Flags::TESTING); + if !allow_testing && channel.is_testing() { + return Err(CliError::InvalidTestingChannel(ch.to_string())); + } + } Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + + macro_rules! assert_ok { + ( $e:expr ) => { + match $e { + Ok(_) => (), + Err(e) => { + println!("{:?}", e); + assert!(false); + } + } + }; + } + + macro_rules! assert_no { + ( $e:expr ) => { + match $e { + Ok(_) => assert!(false), + Err(_) => (), + } + }; + } + + #[test] + fn test_verify() { + let mut manifest = Manifest::new("", "", &PathBuf::default()); + assert_ok!(manifest.verify(verify::Flags::default())); + assert_ok!(manifest.verify(verify::Flags::SIMPLE)); + assert_ok!(manifest.verify(verify::Flags::TESTING)); + assert_ok!(manifest.verify(verify::Flags::TESTING | verify::Flags::SIMPLE)); + + manifest.channel = Some("/".to_string()); + assert_ok!(manifest.verify(verify::Flags::default())); + assert_ok!(manifest.verify(verify::Flags::SIMPLE)); + assert_ok!(manifest.verify(verify::Flags::TESTING)); + assert_ok!(manifest.verify(verify::Flags::TESTING | verify::Flags::SIMPLE)); + + manifest.channel = Some("/testing".to_string()); + assert_no!(manifest.verify(verify::Flags::default())); + assert_no!(manifest.verify(verify::Flags::SIMPLE)); + assert_ok!(manifest.verify(verify::Flags::TESTING)); + assert_ok!(manifest.verify(verify::Flags::TESTING | verify::Flags::SIMPLE)); + + manifest.channel = Some("/a/testing".to_string()); + assert_no!(manifest.verify(verify::Flags::default())); + assert_no!(manifest.verify(verify::Flags::SIMPLE)); + assert_ok!(manifest.verify(verify::Flags::TESTING)); + assert_ok!(manifest.verify(verify::Flags::TESTING | verify::Flags::SIMPLE)); + + manifest.channel = Some("".to_string()); + assert_no!(manifest.verify(verify::Flags::default())); + assert_no!(manifest.verify(verify::Flags::SIMPLE)); + assert_no!(manifest.verify(verify::Flags::TESTING)); + assert_no!(manifest.verify(verify::Flags::TESTING | verify::Flags::SIMPLE)); + } +} diff --git a/src/core/mod.rs b/src/core/mod.rs index ca59e24b..dd261e1f 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,15 +1,17 @@ +pub use self::config::{config_dir, Config, ConfigDefaults, Mount}; +pub use self::ensure::ensure_dir_exists_fresh; pub use self::errors::{CliError, LalResult}; -pub use self::manifest::{Manifest, ComponentConfiguration, ManifestLocation}; -pub use self::lockfile::{Lockfile, Container}; -pub use self::config::{Config, ConfigDefaults, Mount, config_dir}; +pub use self::lockfile::{Container, Lockfile}; +pub use self::manifest::{ + ComponentConfiguration, Coordinates, Manifest, ManifestLocation, TwoDCoordinates, +}; pub use self::sticky::StickyOptions; -pub use self::ensure::ensure_dir_exists_fresh; mod config; +mod ensure; mod errors; mod lockfile; mod sticky; -mod ensure; /// Manifest module can be used directly pub mod manifest; diff --git a/src/core/output.rs b/src/core/output.rs index db29aec9..99a09ee2 100644 --- a/src/core/output.rs +++ b/src/core/output.rs @@ -1,5 +1,5 @@ -use std::process::Command; use std::path::Path; +use std::process::Command; use super::{CliError, LalResult}; diff --git a/src/core/sticky.rs b/src/core/sticky.rs index 1f121b28..0c98c6ff 100644 --- a/src/core/sticky.rs +++ b/src/core/sticky.rs @@ -1,11 +1,11 @@ -use std::fs; +use serde_json; use std::env; +use std::fs; use std::io::prelude::{Read, Write}; use std::path::Path; -use serde_json; use super::LalResult; -use manifest::create_lal_subdir; +use crate::manifest::create_lal_subdir; /// Representation of .lal/opts /// @@ -18,12 +18,12 @@ pub struct StickyOptions { impl StickyOptions { /// Initialize a StickyOptions with defaults - pub fn new() -> StickyOptions { Default::default() } + pub fn new() -> Self { Self::default() } /// Read and deserialize a StickyOptions from `.lal/opts` - pub fn read() -> LalResult { + pub fn read() -> LalResult { let opts_path = Path::new(".lal/opts"); if !opts_path.exists() { - return Ok(StickyOptions::default()); // everything off + return Ok(Self::default()); // everything off } let mut opts_data = String::new(); fs::File::open(&opts_path)?.read_to_string(&mut opts_data)?; @@ -39,13 +39,14 @@ impl StickyOptions { let encoded = serde_json::to_string_pretty(self)?; let mut f = fs::File::create(&opts_path)?; - write!(f, "{}\n", encoded)?; + writeln!(f, "{}", encoded)?; debug!("Wrote {}: \n{}", opts_path.display(), encoded); Ok(()) } /// Delete local `.lal/opts` pub fn delete_local() -> LalResult<()> { let opts_path = Path::new(".lal/opts"); - Ok(fs::remove_file(&opts_path)?) + fs::remove_file(&opts_path)?; + Ok(()) } } diff --git a/src/env.rs b/src/env.rs index 450dedce..0e142f5c 100644 --- a/src/env.rs +++ b/src/env.rs @@ -1,7 +1,7 @@ use std::process::Command; use std::vec::Vec; -use super::{StickyOptions, LalResult, CliError, Container, Config}; +use super::{CliError, Config, Container, LalResult, StickyOptions}; /// Pull the current environment from docker pub fn update(container: &Container, env: &str) -> LalResult<()> { diff --git a/src/export.rs b/src/export.rs index a2a821aa..9fb50e63 100644 --- a/src/export.rs +++ b/src/export.rs @@ -1,22 +1,23 @@ use std::fs; use std::path::Path; -use storage::CachedBackend; -use super::{LalResult, CliError}; +use super::{CliError, LalResult}; +use crate::channel::{parse_coords, Channel}; +use crate::storage::CachedBackend; /// Export a specific component from the storage backend pub fn export( backend: &T, comp: &str, output: Option<&str>, - _env: Option<&str>, + env: Option<&str>, ) -> LalResult<()> { - let env = match _env { + let env = match env { None => { error!("export is no longer allowed without an explicit environment"); - return Err(CliError::EnvironmentUnspecified) - }, - Some(e) => e + return Err(CliError::EnvironmentUnspecified); + } + Some(e) => e, }; if comp.to_lowercase() != comp { @@ -26,24 +27,36 @@ pub fn export( let dir = output.unwrap_or("."); info!("Export {} {} to {}", env, comp, dir); - let mut component_name = comp; // this is only correct if no =version suffix - let tarname = if comp.contains('=') { - let pair: Vec<&str> = comp.split('=').collect(); - if let Ok(n) = pair[1].parse::() { + let comp_vec = comp.split('=').collect::>(); + let comp_name = comp_vec[0]; + let tarname = if comp_vec.len() > 1 { + // Put the original `=` signs back in place. + let coords = comp_vec.iter().skip(1).cloned().collect::>().join("="); + let (version, channel) = parse_coords(&coords); + let channel = match channel { + Some(ch) => ch, + None => Channel::default(), + }; + + // Verify the channel is valid. + channel.verify()?; + + if version.is_some() { // standard fetch with an integer version - component_name = pair[0]; // save so we have sensible tarball names - backend.retrieve_published_component(pair[0], Some(n), env)?.0 + backend + .retrieve_published_component(comp_name, version, env, &channel)? + .0 } else { - // string version -> stash - component_name = pair[0]; // save so we have sensible tarball names - backend.retrieve_stashed_component(pair[0], pair[1])? + backend.retrieve_stashed_component(comp_name, &coords)? } } else { - // fetch without a specific version (latest) - backend.retrieve_published_component(comp, None, env)?.0 + // fetch without a specific version and channel (latest, and default) + backend + .retrieve_published_component(comp_name, None, env, &Channel::default())? + .0 }; - let dest = Path::new(dir).join(format!("{}.tar.gz", component_name)); + let dest = Path::new(dir).join(format!("{}.tar.gz", comp_name)); debug!("Copying {:?} to {:?}", tarname, dest); fs::copy(tarname, dest)?; diff --git a/src/fetch.rs b/src/fetch.rs index 1f7a69ef..c810fff1 100644 --- a/src/fetch.rs +++ b/src/fetch.rs @@ -1,8 +1,11 @@ +use fs2::FileExt; use std::fs; use std::path::Path; -use storage::CachedBackend; -use super::{CliError, LalResult, Lockfile, Manifest}; +use super::{CliError, Coordinates, LalResult, Lockfile, Manifest}; +use crate::channel::Channel; +use crate::storage::{Backend, CachedBackend}; +use crate::verify; fn clean_input() { let input = Path::new("./INPUT"); @@ -15,45 +18,51 @@ fn clean_input() { /// /// This will read, and HTTP GET all the dependencies at the specified versions. /// If the `core` bool is set, then `devDependencies` are not installed. -pub fn fetch( +pub fn fetch( manifest: &Manifest, backend: &T, core: bool, env: &str, ) -> LalResult<()> { // first ensure manifest is sane: - manifest.verify()?; + // do not check whether testing components are allowed now + manifest.verify(verify::Flags::TESTING)?; - debug!("Installing dependencies{}", - if !core { " and devDependencies" } else { "" }); + debug!( + "Installing dependencies{}", + if core { "" } else { " and devDependencies" } + ); // create the joined hashmap of dependencies and possibly devdependencies - let mut deps = manifest.dependencies.clone(); - if !core { - for (k, v) in &manifest.devDependencies { - deps.insert(k.clone(), *v); - } - } + let mut deps = if core { + manifest.dependencies.clone() + } else { + manifest.all_dependencies() + }; let mut extraneous = vec![]; // stuff we should remove // figure out what we have already - let lf = Lockfile::default() - .populate_from_input() - .map_err(|e| { - // Guide users a bit if they did something dumb - see #77 - warn!("Populating INPUT data failed - your INPUT may be corrupt"); - warn!("This can happen if you CTRL-C during `lal fetch`"); - warn!("Try to `rm -rf INPUT` and `lal fetch` again."); - e - })?; + let lf = Lockfile::default().populate_from_input().map_err(|e| { + // Guide users a bit if they did something dumb - see #77 + warn!("Populating INPUT data failed - your INPUT may be corrupt"); + warn!("This can happen if you CTRL-C during `lal fetch`"); + warn!("Try to `rm -rf INPUT` and `lal fetch` again."); + e + })?; // filter out what we already have (being careful to examine env) for (name, d) in lf.dependencies { // if d.name at d.version in d.environment matches something in deps - if let Some(&cand) = deps.get(&name) { + if let Some(coords) = deps.get(&name) { + let (ver, channel) = match coords { + Coordinates::OneD(v) => (*v, None), + Coordinates::TwoD(c) => (c.version, Some(&c.channel)), + }; + // Parse channel and compare. + let channel = Channel::from_option(&channel); // version found in manifest // ignore non-integer versions (stashed things must be overwritten) if let Ok(n) = d.version.parse::() { - if n == cand && d.environment == env { + if n == ver && d.environment == env && Channel::from_option(&d.channel) == channel { info!("Reuse {} {} {}", env, name, n); deps.remove(&name); } @@ -63,28 +72,60 @@ pub fn fetch( } } + // Fetch time -- acquire a file lock (not a lockfile!) in case other lal instances are running + // This was a bug with multiple executors, easy to reproduce by running two instances of + // `lal fetch` at the same time on a single machine let mut err = None; - for (k, v) in deps { - info!("Fetch {} {} {}", env, k, v); - - // first kill the folders we actually need to fetch: - let cmponent_dir = Path::new("./INPUT").join(&k); - if cmponent_dir.is_dir() { - // Don't think this can fail, but we are dealing with NFS - fs::remove_dir_all(&cmponent_dir) - .map_err(|e| { + if !deps.is_empty() { + let cache_dir = backend.get_cache_dir(); + let cache_path = Path::new(&cache_dir); + if !cache_path.is_dir() { + fs::create_dir_all(&cache_path)?; + } + + debug!("Acquiring backend lock..."); + let file_lock_path = Path::new(&cache_dir).join("fetch.lock"); + let file_lock = fs::OpenOptions::new() + .write(true) + .create(true) + .open(file_lock_path)?; + file_lock.lock_exclusive()?; // block until this process can lock the file + debug!("Lock acquired"); + + for (k, c) in deps { + info!("Fetch {} {}={}", env, k, c); + + let (version, channel) = match c { + Coordinates::OneD(v) => (v, None), + Coordinates::TwoD(c) => (c.version, Some(c.channel)), + }; + + let channel = Channel::from_option(&channel); + + // first kill the folders we actually need to fetch: + let cmponent_dir = Path::new("./INPUT").join(&k); + if cmponent_dir.is_dir() { + // Don't think this can fail, but we are dealing with NFS + fs::remove_dir_all(&cmponent_dir).map_err(|e| { warn!("Failed to remove INPUT/{} - {}", k, e); warn!("Please clean out your INPUT folder yourself to avoid corruption"); e })?; + } + + let _ = backend + .unpack_published_component(&k, Some(version), env, &channel) + .map_err(|e| { + warn!("Failed to completely install {} ({})", k, e); + // likely symlinks inside tarball that are being dodgy + // this is why we clean_input + err = Some(e); + }); } - let _ = backend.unpack_published_component(&k, Some(v), env).map_err(|e| { - warn!("Failed to completely install {} ({})", k, e); - // likely symlinks inside tarball that are being dodgy - // this is why we clean_input - err = Some(e); - }); + debug!("Releasing backend lock.."); + file_lock.unlock()?; + debug!("Lock released"); } // remove extraneous deps diff --git a/src/init.rs b/src/init.rs index 8a93ef2d..f8c3a798 100644 --- a/src/init.rs +++ b/src/init.rs @@ -1,8 +1,7 @@ use std::env; -use super::{Config, CliError, LalResult}; -use core::manifest::*; - +use super::{CliError, Config, LalResult}; +use crate::core::manifest::*; /// Generates a blank manifest in the current directory /// @@ -26,7 +25,7 @@ pub fn init(cfg: &Config, force: bool, env: &str) -> LalResult<()> { // we are allowed to overwrite or write a new manifest if we are here // always create new manifests in new default location create_lal_subdir(&pwd)?; // create the `.lal` subdir if it's not there already - Manifest::new(dirname, env, ManifestLocation::default().as_path(&pwd)).write()?; + Manifest::new(dirname, env, &ManifestLocation::default().as_path(&pwd)).write()?; // if the manifest already existed, warn about this now being placed elsewhere if let Ok(ManifestLocation::RepoRoot) = mpath { diff --git a/src/lib.rs b/src/lib.rs index 69d852f5..ddb761b4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,74 +19,62 @@ #[macro_use] extern crate hyper; -extern crate hyper_native_tls; -extern crate openssl_probe; #[macro_use] extern crate serde_derive; -extern crate serde_json; -extern crate regex; -extern crate tar; -extern crate flate2; -extern crate ansi_term; -extern crate sha1; #[macro_use] extern crate log; -extern crate walkdir; -extern crate chrono; -extern crate filetime; -extern crate rand; -extern crate semver; -#[cfg(feature = "progress")] -extern crate indicatif; +#[macro_use] +extern crate bitflags; // re-exports mod core; -pub use core::*; +pub use crate::core::*; mod storage; -pub use storage::*; +pub use crate::storage::*; +/// Channel module for channel subcommand +pub mod channel; /// Env module for env subcommand (which has further subcommands) pub mod env; /// List module for all the list-* subcommands pub mod list; /// Propagation module with all structs describing the steps pub mod propagate; - +/// Verification of state +pub mod verify; // lift most other pub functions into our libraries main scope // this avoids having to type lal::build::build in tests and main.rs -pub use build::{build, BuildOptions}; -pub use configure::configure; -pub use init::init; -pub use shell::{shell, docker_run, script, DockerRunFlags, ShellModes}; -pub use fetch::fetch; -pub use update::{update, update_all}; -pub use remove::remove; -pub use export::export; -pub use status::status; -pub use verify::verify; -pub use stash::stash; -pub use clean::clean; -pub use query::query; -pub use publish::publish; +pub use crate::build::{build, BuildOptions}; +pub use crate::clean::clean; +pub use crate::configure::configure; +pub use crate::export::export; +pub use crate::fetch::fetch; +pub use crate::init::init; +pub use crate::publish::publish; +pub use crate::query::query; +pub use crate::remove::remove; +pub use crate::shell::{docker_run, script, shell, DockerRunFlags, ShellModes}; +pub use crate::stash::stash; +pub use crate::status::status; +pub use crate::update::{update, update_all}; +mod build; +mod clean; mod configure; +mod export; +mod fetch; mod init; -mod shell; -mod build; +mod publish; mod query; -mod update; -mod fetch; mod remove; -mod export; -mod clean; -mod verify; +mod shell; mod stash; mod status; -mod publish; +mod update; #[cfg(feature = "upgrade")] -pub use upgrade::upgrade; +pub use crate::upgrade::upgrade; #[cfg(feature = "upgrade")] mod upgrade; diff --git a/src/list.rs b/src/list.rs index cf1534c5..2bb75f17 100644 --- a/src/list.rs +++ b/src/list.rs @@ -1,7 +1,6 @@ /// This file contains all the hidden `lal list-*` subcommands /// If you are looking for `lal ls` go to status.rs - -use super::{Manifest, Config, LalResult}; +use super::{Config, LalResult, Manifest}; /// Print the buildable components from the `Manifest` pub fn buildables(manifest: &Manifest) -> LalResult<()> { @@ -41,7 +40,11 @@ pub fn environments(cfg: &Config) -> LalResult<()> { /// Print the dependencies from the manifest pub fn dependencies(mf: &Manifest, core: bool) -> LalResult<()> { - let deps = if core { mf.dependencies.clone() } else { mf.all_dependencies() }; + let deps = if core { + mf.dependencies.clone() + } else { + mf.all_dependencies() + }; for k in deps.keys() { println!("{}", k); } diff --git a/src/main.rs b/src/main.rs index 7dab4c2b..c32e5c81 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,15 +2,18 @@ extern crate clap; #[macro_use] extern crate log; -extern crate loggerv; -extern crate openssl_probe; -extern crate lal; +use loggerv; +use openssl_probe; + +use clap::{App, AppSettings, Arg, ArgMatches, SubCommand}; +use lal; +use lal::channel::Channel; use lal::*; -use clap::{Arg, App, AppSettings, SubCommand, ArgMatches}; -use std::process; use std::ops::Deref; +use std::process; +#[allow(clippy::needless_pass_by_value)] fn is_integer(v: String) -> Result<(), String> { if v.parse::().is_ok() { return Ok(()); @@ -20,7 +23,7 @@ fn is_integer(v: String) -> Result<(), String> { fn result_exit(name: &str, x: LalResult) { let _ = x.map_err(|e| { - println!(""); // add a separator + println!(); // add a separator error!("{} error: {}", name, e); debug!("{}: {:?}", name, e); // in the off-chance that Debug is useful process::exit(1); @@ -30,87 +33,150 @@ fn result_exit(name: &str, x: LalResult) { // functions that work without a manifest, and thus can run without a set env fn handle_manifest_agnostic_cmds( - args: &ArgMatches, + args: &clap::ArgMatches, cfg: &Config, - backend: &Backend, + backend: &dyn Backend, explicit_env: Option<&str>, ) { let res = if let Some(a) = args.subcommand_matches("export") { - lal::export(backend, - a.value_of("component").unwrap(), - a.value_of("output"), - explicit_env) + let env = if let Some(override_env) = a.value_of("environment") { + Some(override_env) + } else { + explicit_env + }; + lal::export( + backend, + a.value_of("component").unwrap(), + a.value_of("output"), + env, + ) } else if let Some(a) = args.subcommand_matches("query") { - lal::query(backend, - explicit_env, - a.value_of("component").unwrap(), - a.is_present("latest")) + let env = if let Some(override_env) = a.value_of("environment") { + Some(override_env) + } else { + explicit_env + }; + lal::query( + backend, + env, + a.value_of("channel"), + a.value_of("component").unwrap(), + a.is_present("latest"), + ) } else if let Some(a) = args.subcommand_matches("publish") { lal::publish(a.value_of("component").unwrap(), backend) } else if args.subcommand_matches("list-environments").is_some() { lal::list::environments(cfg) } else { - return (); + return; }; result_exit(args.subcommand_name().unwrap(), res); } // functions that need a manifest, but do not depend on environment values -fn handle_environment_agnostic_cmds(args: &ArgMatches, mf: &Manifest, backend: &Backend) { +fn handle_environment_agnostic_cmds(args: &ArgMatches, mf: &mut Manifest, backend: &dyn Backend) { let res = if let Some(a) = args.subcommand_matches("status") { - lal::status(mf, - a.is_present("full"), - a.is_present("origin"), - a.is_present("time")) + lal::status( + mf, + a.is_present("full"), + a.is_present("origin"), + a.is_present("time"), + ) } else if args.subcommand_matches("list-components").is_some() { lal::list::buildables(mf) - } else if args.subcommand_matches("list-supported-environments").is_some() { + } else if args + .subcommand_matches("list-supported-environments") + .is_some() + { lal::list::supported_environments(mf) } else if let Some(a) = args.subcommand_matches("list-configurations") { lal::list::configurations(a.value_of("component").unwrap(), mf) } else if let Some(a) = args.subcommand_matches("list-dependencies") { lal::list::dependencies(mf, a.is_present("core")) } else if let Some(a) = args.subcommand_matches("remove") { - let xs = a.values_of("components").unwrap().map(String::from).collect::>(); + let xs = a + .values_of("components") + .unwrap() + .map(String::from) + .collect::>(); lal::remove(mf, xs, a.is_present("save"), a.is_present("savedev")) } else if let Some(a) = args.subcommand_matches("stash") { lal::stash(backend, mf, a.value_of("name").unwrap()) } else if let Some(a) = args.subcommand_matches("propagate") { - lal::propagate::print(mf, a.value_of("component").unwrap(), a.is_present("json")) + lal::propagate::print( + mf, + &a.values_of("component") + .unwrap() + .map(ToString::to_string) + .collect::>(), + a.is_present("json"), + ) + } else if let Some(a) = args.subcommand_matches("channel") { + handle_channel_cmds(&a, mf) } else { - return (); + return; }; result_exit(args.subcommand_name().unwrap(), res); } -fn handle_network_cmds(args: &ArgMatches, mf: &Manifest, backend: &Backend, env: &str) { +fn handle_network_cmds(args: &ArgMatches, mf: &Manifest, backend: &dyn Backend, env: &str) { let res = if let Some(a) = args.subcommand_matches("update") { - let xs = a.values_of("components").unwrap().map(String::from).collect::>(); - lal::update(mf, - backend, - xs, - a.is_present("save"), - a.is_present("savedev"), - env) + let xs = a + .values_of("components") + .unwrap() + .map(String::from) + .collect::>(); + lal::update( + mf, + backend, + &xs, + a.is_present("save"), + a.is_present("savedev"), + env, + ) } else if let Some(a) = args.subcommand_matches("update-all") { lal::update_all(mf, backend, a.is_present("save"), a.is_present("dev"), env) } else if let Some(a) = args.subcommand_matches("fetch") { lal::fetch(mf, backend, a.is_present("core"), env) } else { - return (); // not a network cmnd + return; // not a network cmnd }; result_exit(args.subcommand_name().unwrap(), res) } +fn handle_channel_cmds(args: &ArgMatches, mf: &mut Manifest) -> LalResult<()> { + let current_channel = lal::channel::get(mf); + let mut is_testing = false; + + let new_channel = { + if let Some(a) = args.subcommand_matches("set") { + a.value_of("channel").unwrap().to_owned() + } else if args.subcommand_matches("reset").is_some() { + "/".to_owned() + } else if args.subcommand_matches("testing").is_some() { + is_testing = true; + format!("{}/{}", current_channel, Channel::TESTING) + } else if let Some(a) = args.subcommand_matches("append") { + let subchannel = a.value_of("subchannel").unwrap(); + format!("{}/{}", current_channel, subchannel) + } else { + println!("{}", current_channel); + process::exit(0); + } + }; + + lal::channel::set(mf, &new_channel, is_testing) +} + fn handle_env_command( - args: &ArgMatches, + args: &ArgMatches<'_>, cfg: &Config, env: &str, stickies: &StickyOptions, ) -> Container { - // lookup associated container from - let container = cfg.get_container(env.into()) + let container = cfg + .get_container(env.into()) .map_err(|e| { error!("Environment error: {}", e); println!("Ensure that manifest.environment has a corresponding entry in ~/.lal/config"); @@ -124,13 +190,15 @@ fn handle_env_command( result_exit("env update", lal::env::update(&container, env)) } else if a.subcommand_matches("reset").is_some() { // NB: if .lal/opts.env points at an environment not in config - // reset will fail.. possible to fix, but complects this file too much + // reset will fail.. possible to fix, but complicates this file too much // .lal/opts writes are checked in lal::env::set anyway so this // would be purely the users fault for editing it manually result_exit("env clear", lal::env::clear()) } else if let Some(sa) = a.subcommand_matches("set") { - result_exit("env override", - lal::env::set(stickies, cfg, sa.value_of("environment").unwrap())) + result_exit( + "env override", + lal::env::set(stickies, cfg, sa.value_of("environment").unwrap()), + ) } else { // just print current environment println!("{}", env); @@ -143,7 +211,7 @@ fn handle_env_command( } #[cfg(feature = "upgrade")] -fn handle_upgrade(args: &ArgMatches, cfg: &Config) { +fn handle_upgrade(args: &ArgMatches<'_>, cfg: &Config) { // we have a subcommand because SubcommandRequiredElseHelp let subname = args.subcommand_name().unwrap(); @@ -154,8 +222,10 @@ fn handle_upgrade(args: &ArgMatches, cfg: &Config) { // Autoupgrade if enabled - runs once daily if enabled // also excluding all listers because they are used in autocomplete - if cfg.autoupgrade && subname != "upgrade" && !subname.contains("list-") && - cfg.upgrade_check_time() + if cfg.autoupgrade + && subname != "upgrade" + && !subname.contains("list-") + && cfg.upgrade_check_time() { debug!("Performing daily upgrade check"); let _ = lal::upgrade(false).map_err(|e| { @@ -170,10 +240,8 @@ fn handle_upgrade(args: &ArgMatches, cfg: &Config) { } } - - fn handle_docker_cmds( - args: &ArgMatches, + args: &ArgMatches<'_>, mf: &Manifest, cfg: &Config, env: &str, @@ -182,8 +250,22 @@ fn handle_docker_cmds( let res = if let Some(a) = args.subcommand_matches("verify") { // not really a docker related command, but it needs // the resolved env to verify consistent dependency usage - lal::verify(mf, env, a.is_present("simple")) + let mut flags = lal::verify::Flags::default(); + if a.is_present("simple") { + flags |= lal::verify::Flags::SIMPLE; + } + if a.is_present("testing") { + flags |= lal::verify::Flags::TESTING; + } + lal::verify::verify(mf, env, flags) } else if let Some(a) = args.subcommand_matches("build") { + let mut flags = lal::verify::Flags::default(); + if a.is_present("simple-verify") { + flags |= lal::verify::Flags::SIMPLE; + } + if a.is_present("testing") { + flags |= lal::verify::Flags::TESTING; + } let bopts = BuildOptions { name: a.value_of("component").map(String::from), configuration: a.value_of("configuration").map(String::from), @@ -192,15 +274,16 @@ fn handle_docker_cmds( sha: a.value_of("with-sha").map(String::from), container: container.clone(), force: a.is_present("force"), - simple_verify: a.is_present("simple-verify"), + verify_flags: flags, }; let modes = ShellModes { printonly: a.is_present("print"), x11_forwarding: a.is_present("x11"), host_networking: a.is_present("net-host"), - env_vars: values_t!(a.values_of("env-var"), String).unwrap_or(vec![]), + env_vars: values_t!(a.values_of("env-var"), String).unwrap_or_else(|_| vec![]), + capabilities: values_t!(a.values_of("cap-add"), String).unwrap_or_else(|_| vec![]), }; - lal::build(cfg, mf, &bopts, env.into(), modes) + lal::build(cfg, mf, &bopts, env, modes) } else if let Some(a) = args.subcommand_matches("shell") { let xs = if a.is_present("cmd") { Some(a.values_of("cmd").unwrap().collect::>()) @@ -211,7 +294,8 @@ fn handle_docker_cmds( printonly: a.is_present("print"), x11_forwarding: a.is_present("x11"), host_networking: a.is_present("net-host"), - env_vars: values_t!(a.values_of("env-var"), String).unwrap_or(vec![]), + env_vars: values_t!(a.values_of("env-var"), String).unwrap_or_else(|_| vec![]), + capabilities: values_t!(a.values_of("cap-add"), String).unwrap_or_else(|_| vec![]), }; lal::shell(cfg, container, &modes, xs, a.is_present("privileged")) } else if let Some(a) = args.subcommand_matches("run") { @@ -224,16 +308,19 @@ fn handle_docker_cmds( printonly: a.is_present("print"), x11_forwarding: a.is_present("x11"), host_networking: a.is_present("net-host"), - env_vars: values_t!(a.values_of("env-var"), String).unwrap_or(vec![]), + env_vars: values_t!(a.values_of("env-var"), String).unwrap_or_else(|_| vec![]), + capabilities: values_t!(a.values_of("cap-add"), String).unwrap_or_else(|_| vec![]), }; - lal::script(cfg, - container, - a.value_of("script").unwrap(), - xs, - &modes, - a.is_present("privileged")) + lal::script( + cfg, + container, + a.value_of("script").unwrap(), + &xs, + &modes, + a.is_present("privileged"), + ) } else { - return (); // no valid docker related command found + return; // no valid docker related command found }; result_exit(args.subcommand_name().unwrap(), res); } @@ -247,292 +334,549 @@ fn main() { .setting(AppSettings::DeriveDisplayOrder) .global_settings(&[AppSettings::ColoredHelp]) .about("lal dependency manager") - .arg(Arg::with_name("environment") - .short("e") - .long("env") - .takes_value(true) - .help("Override the default environment for this command")) - .arg(Arg::with_name("verbose") - .short("v") - .multiple(true) - .help("Increase verbosity")) - .arg(Arg::with_name("debug") - .short("d") - .long("debug") - .help("Adds line numbers to log statements")) - .subcommand(SubCommand::with_name("fetch") - .about("Fetch dependencies listed in the manifest into INPUT") - .arg(Arg::with_name("core") - .long("core") - .short("c") - .help("Only fetch core dependencies"))) - .subcommand(SubCommand::with_name("build") - .about("Runs BUILD script in current directory in the configured container") - .arg(Arg::with_name("component") - .help("Build a specific component (if other than the main manifest component)")) - .arg(Arg::with_name("configuration") - .long("config") - .short("c") - .takes_value(true) - .help("Build using a specific configuration (else will use defaultConfig)")) - .arg(Arg::with_name("simple-verify") - .short("s") - .long("simple-verify") - .help("Use verify --simple to check INPUT (allows stashed dependencies)")) - .arg(Arg::with_name("force") - .long("force") - .short("f") - .help("Ignore verify errors when using custom dependencies")) - .arg(Arg::with_name("release") - .long("release") - .short("r") - .help("Create a release tarball that can be published")) - .arg(Arg::with_name("with-version") - .long("with-version") + .arg( + Arg::with_name("environment") + .short("e") + .long("env") .takes_value(true) - .requires("release") - .help("Configure lockfiles with an explicit version number")) - .arg(Arg::with_name("with-sha") - .long("with-sha") - .takes_value(true) - .requires("release") - .help("Configure lockfiles with an explicit sha")) - .arg(Arg::with_name("x11") - .short("X") - .long("X11") - .help("Enable best effort X11 forwarding")) - .arg(Arg::with_name("net-host") - .short("n") - .long("net-host") - .help("Enable host networking")) - .arg(Arg::with_name("env-var") - .long("env-var") - .help("Set environment variables in the container") - .multiple(true) - .takes_value(true) - .number_of_values(1)) - .arg(Arg::with_name("print") - .long("print-only") - .conflicts_with("release") - .help("Only print the docker run command and exit"))) - .subcommand(SubCommand::with_name("update") - .about("Update arbitrary dependencies into INPUT") - .arg(Arg::with_name("components") - .help("The specific component=version pairs to update") - .required(true) - .multiple(true)) - .arg(Arg::with_name("save") - .short("S") - .long("save") - .conflicts_with("savedev") - .help("Save updated versions in dependencies in the manifest")) - .arg(Arg::with_name("savedev") - .short("D") - .long("save-dev") - .conflicts_with("save") - .help("Save updated versions in devDependencies in the manifest"))) - .subcommand(SubCommand::with_name("verify") - .arg(Arg::with_name("simple") - .short("s") - .long("simple") - .help("Allow stashed versions in this simpler verify algorithm")) - .about("verify consistency of INPUT")) - .subcommand(SubCommand::with_name("status") - .alias("ls") - .arg(Arg::with_name("full") - .short("f") - .long("full") - .help("Print the full dependency tree")) - .arg(Arg::with_name("time") - .short("t") - .long("time") - .help("Print build time of artifact")) - .arg(Arg::with_name("origin") - .short("o") - .long("origin") - .help("Print version and environment origin of artifact")) - .about("Prints current dependencies and their status")) - .subcommand(SubCommand::with_name("shell") - .about("Enters the configured container mounting the current directory") - .alias("sh") - .arg(Arg::with_name("privileged") - .short("p") - .long("privileged") - .help("Run docker in privileged mode")) - .arg(Arg::with_name("x11") - .short("X") - .long("X11") - .help("Enable X11 forwarding (best effort)")) - .arg(Arg::with_name("net-host") - .short("n") - .long("net-host") - .help("Enable host networking")) - .arg(Arg::with_name("env-var") - .long("env-var") - .help("Set environment variables in the container") + .hidden(true) + .help("DEPRECATED - Provide a default environment for commands which do not use the manifest's environment"), + ) + .arg( + Arg::with_name("verbose") + .short("v") .multiple(true) - .takes_value(true) - .number_of_values(1)) - .arg(Arg::with_name("print") - .long("print-only") - .help("Only print the docker run command and exit")) - .setting(AppSettings::TrailingVarArg) - .arg(Arg::with_name("cmd").multiple(true))) - .subcommand(SubCommand::with_name("run") - .about("Runs scripts from .lal/scripts in the configured container") - .alias("script") - .arg(Arg::with_name("script") - .help("Name of the script file to be run") - .required(true)) - .arg(Arg::with_name("x11") - .short("X") - .long("X11") - .help("Enable X11 forwarding (best effort)")) - .arg(Arg::with_name("net-host") - .short("n") - .long("net-host") - .help("Enable host networking")) - .arg(Arg::with_name("env-var") - .long("env-var") - .help("Set environment variables in the container") - .multiple(true) - .takes_value(true) - .number_of_values(1)) - .arg(Arg::with_name("print") - .long("print-only") - .help("Only print the docker run command and exit")) - .arg(Arg::with_name("privileged") - .short("p") - .long("privileged") - .help("Run docker in privileged mode")) - .setting(AppSettings::TrailingVarArg) - .arg(Arg::with_name("parameters") - .multiple(true) - .help("Parameters to pass on to the script"))) - .subcommand(SubCommand::with_name("init") - .about("Create a manifest file in the current directory") - .arg(Arg::with_name("environment") - .required(true) - .help("Environment to build this component in")) - .arg(Arg::with_name("force") - .short("f") - .help("overwrites manifest if necessary"))) - .subcommand(SubCommand::with_name("configure") - .about("Creates a default lal config ~/.lal/ from a defaults file") - .arg(Arg::with_name("file") - .required(true) - .help("An environments file to seed the config with"))) - .subcommand(SubCommand::with_name("export") - .about("Fetch a raw tarball from artifactory") - .arg(Arg::with_name("component") - .help("The component to export") - .required(true)) - .arg(Arg::with_name("output") - .short("o") - .long("output") - .takes_value(true) - .help("Output directory to save to"))) - .subcommand(SubCommand::with_name("env") - .about("Manages environment configurations") - .subcommand(SubCommand::with_name("set") - .about("Override the default environment for this folder") - .arg(Arg::with_name("environment") - .required(true) - .help("Name of the environment to use"))) - .subcommand(SubCommand::with_name("update").about("Update the current environment")) - .subcommand(SubCommand::with_name("reset").about("Return to the default environment"))) - .subcommand(SubCommand::with_name("stash") - .about("Stashes current build OUTPUT in cache for later reuse") - .alias("save") - .arg(Arg::with_name("name") - .required(true) - .help("Name used for current build"))) - .subcommand(SubCommand::with_name("remove") - .alias("rm") - .about("Remove specific dependencies from INPUT") - .arg(Arg::with_name("components") - .help("Remove specific components") - .required(true) - .multiple(true)) - .arg(Arg::with_name("save") - .short("S") - .long("save") - .conflicts_with("savedev") - .help("Save removal of dependencies in the manifest")) - .arg(Arg::with_name("savedev") - .short("D") - .long("save-dev") - .conflicts_with("save") - .help("Save removal of devDependencies in the manifest"))) - .subcommand(SubCommand::with_name("clean") - .about("Clean old artifacts in the cache directory to save space") - .arg(Arg::with_name("days") + .global(true) + .help("Increase verbosity"), + ) + .arg( + Arg::with_name("debug") .short("d") - .long("days") - .takes_value(true) - .default_value("14") - .validator(is_integer) - .help("Number of days to serve as cutoff"))) - .subcommand(SubCommand::with_name("query") - .about("Query for available versions on artifactory") - .arg(Arg::with_name("latest") - .long("latest") - .short("l") - .help("Return latest version only")) - .arg(Arg::with_name("component") - .required(true) - .help("Component name to search for"))) - .subcommand(SubCommand::with_name("propagate") - .about("Show steps to propagate a version fully through the tree") - .arg(Arg::with_name("component") - .required(true) - .help("Component to propagate")) - .arg(Arg::with_name("json") - .short("j") - .long("json") - .help("Produce a machine readable instruction set"))) - .subcommand(SubCommand::with_name("update-all") - .about("Update all dependencies in the manifest") - .arg(Arg::with_name("dev") - .short("D") - .long("dev") - .help("Update devDependencies instead of dependencies")) - .arg(Arg::with_name("save") - .short("S") - .long("save") - .help("Save updated versions in the right object in the manifest"))) - .subcommand(SubCommand::with_name("publish") - .setting(AppSettings::Hidden) - .arg(Arg::with_name("component") - .required(true) - .help("Component name to publish")) - .about("Publish a release build to the default artifactory location")) - .subcommand(SubCommand::with_name("list-components") - .setting(AppSettings::Hidden) - .about("list components that can be used with lal build")) - .subcommand(SubCommand::with_name("list-supported-environments") - .setting(AppSettings::Hidden) - .about("list supported environments from the manifest")) - .subcommand(SubCommand::with_name("list-environments") - .setting(AppSettings::Hidden) - .about("list environments that can be used with lal build")) - .subcommand(SubCommand::with_name("list-configurations") - .setting(AppSettings::Hidden) - .arg(Arg::with_name("component") - .required(true) - .help("Component name to look for in the manifest")) - .about("list configurations for a given component")) - .subcommand(SubCommand::with_name("list-dependencies") - .setting(AppSettings::Hidden) - .arg(Arg::with_name("core") - .short("c") - .long("core") - .help("Only list core dependencies")) - .about("list dependencies from the manifest")); + .long("debug") + .help("Adds line numbers to log statements"), + ) + .subcommand( + SubCommand::with_name("fetch") + .about("Fetch dependencies listed in the manifest into INPUT") + .arg( + Arg::with_name("core") + .long("core") + .short("c") + .help("Only fetch core dependencies"), + ), + ) + .subcommand( + SubCommand::with_name("build") + .about("Runs BUILD script in current directory in the configured container") + .arg( + Arg::with_name("component").help( + "Build a specific component (if other than the main manifest component)", + ), + ) + .arg( + Arg::with_name("configuration") + .long("config") + .short("c") + .takes_value(true) + .help("Build using a specific configuration (else will use defaultConfig)"), + ) + .arg( + Arg::with_name("simple-verify") + .short("s") + .long("simple-verify") + .help("Use verify --simple to check INPUT (allows stashed dependencies)"), + ) + .arg( + Arg::with_name("testing") + .short("t") + .long("testing") + .hidden(true) + .help("Allows testing components in the channel"), + ) + .arg( + Arg::with_name("force") + .long("force") + .short("f") + .help("Ignore verify errors when using custom dependencies"), + ) + .arg( + Arg::with_name("release") + .long("release") + .short("r") + .conflicts_with("simple-verify") + .conflicts_with("force") + .requires("with-sha") + .requires("with-version") + .help("Create a release tarball that can be published"), + ) + .arg( + Arg::with_name("with-version") + .long("with-version") + .takes_value(true) + .requires("release") + .help("Configure lockfiles with an explicit version number"), + ) + .arg( + Arg::with_name("with-sha") + .long("with-sha") + .takes_value(true) + .requires("release") + .help("Configure lockfiles with an explicit sha"), + ) + .arg( + Arg::with_name("x11") + .short("X") + .long("X11") + .help("Enable best effort X11 forwarding"), + ) + .arg( + Arg::with_name("net-host") + .short("n") + .long("net-host") + .help("Enable host networking"), + ) + .arg( + Arg::with_name("env-var") + .long("env-var") + .help("Set environment variables in the container") + .multiple(true) + .takes_value(true) + .number_of_values(1), + ) + .arg( + Arg::with_name("cap-add") + .long("cap-add") + .help("Add Linux capabilities") + .multiple(true) + .takes_value(true) + .number_of_values(1), + ) + .arg( + Arg::with_name("print") + .long("print-only") + .conflicts_with("release") + .help("Only print the docker run command and exit"), + ), + ) + .subcommand( + SubCommand::with_name("update") + .about("Update arbitrary dependencies into INPUT") + .arg( + Arg::with_name("components") + .help("The specific component=version pairs to update") + .required(true) + .multiple(true), + ) + .arg( + Arg::with_name("save") + .short("S") + .long("save") + .conflicts_with("savedev") + .help("Save updated versions in dependencies in the manifest"), + ) + .arg( + Arg::with_name("savedev") + .short("D") + .long("save-dev") + .conflicts_with("save") + .help("Save updated versions in devDependencies in the manifest"), + ), + ) + .subcommand( + SubCommand::with_name("verify") + .arg( + Arg::with_name("simple") + .short("s") + .long("simple") + .help("Allow stashed versions in this simpler verify algorithm"), + ) + .arg( + Arg::with_name("testing") + .short("t") + .long("testing") + .hidden(true) + .help("Allows testing components in the channel"), + ) + .about("verify consistency of INPUT"), + ) + .subcommand( + SubCommand::with_name("status") + .alias("ls") + .arg( + Arg::with_name("full") + .short("f") + .long("full") + .help("Print the full dependency tree"), + ) + .arg( + Arg::with_name("time") + .short("t") + .long("time") + .help("Print build time of artifact"), + ) + .arg( + Arg::with_name("origin") + .short("o") + .long("origin") + .help("Print version and environment origin of artifact"), + ) + .about("Prints current dependencies and their status"), + ) + .subcommand( + SubCommand::with_name("shell") + .about("Enters the configured container mounting the current directory") + .alias("sh") + .arg( + Arg::with_name("privileged") + .short("p") + .long("privileged") + .help("Run docker in privileged mode"), + ) + .arg( + Arg::with_name("x11") + .short("X") + .long("X11") + .help("Enable X11 forwarding (best effort)"), + ) + .arg( + Arg::with_name("net-host") + .short("n") + .long("net-host") + .help("Enable host networking"), + ) + .arg( + Arg::with_name("env-var") + .long("env-var") + .help("Set environment variables in the container") + .multiple(true) + .takes_value(true) + .number_of_values(1), + ) + .arg( + Arg::with_name("cap-add") + .long("cap-add") + .help("Add Linux capabilities") + .multiple(true) + .takes_value(true) + .number_of_values(1), + ) + .arg( + Arg::with_name("print") + .long("print-only") + .help("Only print the docker run command and exit"), + ) + .setting(AppSettings::TrailingVarArg) + .arg(Arg::with_name("cmd").multiple(true)), + ) + .subcommand( + SubCommand::with_name("run") + .about("Runs scripts from .lal/scripts in the configured container") + .alias("script") + .arg( + Arg::with_name("script") + .help("Name of the script file to be run") + .required(true), + ) + .arg( + Arg::with_name("x11") + .short("X") + .long("X11") + .help("Enable X11 forwarding (best effort)"), + ) + .arg( + Arg::with_name("net-host") + .short("n") + .long("net-host") + .help("Enable host networking"), + ) + .arg( + Arg::with_name("env-var") + .long("env-var") + .help("Set environment variables in the container") + .multiple(true) + .takes_value(true) + .number_of_values(1), + ) + .arg( + Arg::with_name("cap-add") + .long("cap-add") + .help("Add Linux capabilities") + .multiple(true) + .takes_value(true) + .number_of_values(1), + ) + .arg( + Arg::with_name("print") + .long("print-only") + .help("Only print the docker run command and exit"), + ) + .arg( + Arg::with_name("privileged") + .short("p") + .long("privileged") + .help("Run docker in privileged mode"), + ) + .setting(AppSettings::TrailingVarArg) + .arg( + Arg::with_name("parameters") + .multiple(true) + .help("Parameters to pass on to the script"), + ), + ) + .subcommand( + SubCommand::with_name("init") + .about("Create a manifest file in the current directory") + .arg( + Arg::with_name("environment") + .required(true) + .help("Environment to build this component in"), + ) + .arg( + Arg::with_name("force") + .short("f") + .help("overwrites manifest if necessary"), + ), + ) + .subcommand( + SubCommand::with_name("configure") + .about("Creates a default lal config ~/.lal/ from a defaults file") + .arg( + Arg::with_name("file") + .required(true) + .help("An environments file to seed the config with"), + ), + ) + .subcommand( + SubCommand::with_name("export") + .about("Fetch a raw tarball from artifactory") + .arg( + Arg::with_name("component") + .help("The component to export") + .required(true), + ) + .arg( + Arg::with_name("environment") + .long("env") + .short("e") + .takes_value(true) + .help("The environment to query. If this is not configured then a global environment argument must have been passed") + ) + .arg( + Arg::with_name("output") + .short("o") + .long("output") + .takes_value(true) + .help("Output directory to save to"), + ), + ) + .subcommand( + SubCommand::with_name("env") + .about("Manages environment configurations") + .subcommand( + SubCommand::with_name("set") + .about("Override the default environment for this folder") + .arg( + Arg::with_name("environment") + .required(true) + .help("Name of the environment to use"), + ), + ) + .subcommand(SubCommand::with_name("update").about("Update the current environment")) + .subcommand( + SubCommand::with_name("reset").about("Return to the default environment"), + ), + ) + .subcommand( + SubCommand::with_name("stash") + .about("Stashes current build OUTPUT in cache for later reuse") + .alias("save") + .arg( + Arg::with_name("name") + .required(true) + .help("Name used for current build"), + ), + ) + .subcommand( + SubCommand::with_name("remove") + .alias("rm") + .about("Remove specific dependencies from INPUT") + .arg( + Arg::with_name("components") + .help("Remove specific components") + .required(true) + .multiple(true), + ) + .arg( + Arg::with_name("save") + .short("S") + .long("save") + .conflicts_with("savedev") + .help("Save removal of dependencies in the manifest"), + ) + .arg( + Arg::with_name("savedev") + .short("D") + .long("save-dev") + .conflicts_with("save") + .help("Save removal of devDependencies in the manifest"), + ), + ) + .subcommand( + SubCommand::with_name("clean") + .about("Clean old artifacts in the cache directory to save space") + .arg( + Arg::with_name("days") + .short("d") + .long("days") + .takes_value(true) + .default_value("14") + .validator(is_integer) + .help("Number of days to serve as cutoff"), + ), + ) + .subcommand( + SubCommand::with_name("query") + .about("Query for available versions on artifactory") + .arg( + Arg::with_name("latest") + .long("latest") + .short("l") + .help("Return latest version only"), + ) + .arg( + Arg::with_name("environment") + .long("env") + .short("e") + .takes_value(true) + .help("The environment to query. If this is not configured then a global environment argument must have been passed") + ) + .arg( + Arg::with_name("channel") + .long("channel") + .short("c") + .takes_value(true) + .help("The channel to query. If not specified then the default channel is used") + ) + .arg( + Arg::with_name("component") + .required(true) + .help("Component name to search for"), + ), + ) + .subcommand( + SubCommand::with_name("propagate") + .about("Show steps to propagate a version fully through the tree") + .arg( + Arg::with_name("component") + .required(true) + .multiple(true) + .help("Component(s) to propagate"), + ) + .arg( + Arg::with_name("json") + .short("j") + .long("json") + .help("Produce a machine readable instruction set"), + ), + ) + .subcommand( + SubCommand::with_name("update-all") + .about("Update all dependencies in the manifest") + .arg( + Arg::with_name("dev") + .short("D") + .long("dev") + .help("Update devDependencies instead of dependencies"), + ) + .arg( + Arg::with_name("save") + .short("S") + .long("save") + .help("Save updated versions in the right object in the manifest"), + ), + ) + .subcommand( + SubCommand::with_name("publish") + .setting(AppSettings::Hidden) + .arg( + Arg::with_name("component") + .required(true) + .help("Component name to publish"), + ) + .about("Publish a release build to the default artifactory location"), + ) + .subcommand( + SubCommand::with_name("list-components") + .setting(AppSettings::Hidden) + .about("list components that can be used with lal build"), + ) + .subcommand( + SubCommand::with_name("list-supported-environments") + .setting(AppSettings::Hidden) + .about("list supported environments from the manifest"), + ) + .subcommand( + SubCommand::with_name("list-environments") + .setting(AppSettings::Hidden) + .about("list environments that can be used with lal build"), + ) + .subcommand( + SubCommand::with_name("list-configurations") + .setting(AppSettings::Hidden) + .arg( + Arg::with_name("component") + .required(true) + .help("Component name to look for in the manifest"), + ) + .about("list configurations for a given component"), + ) + .subcommand( + SubCommand::with_name("list-dependencies") + .setting(AppSettings::Hidden) + .arg( + Arg::with_name("core") + .short("c") + .long("core") + .help("Only list core dependencies"), + ) + .about("list dependencies from the manifest"), + ) + .subcommand( + SubCommand::with_name("channel") + .about("Manages channel configurations") + .subcommand( + SubCommand::with_name("set") + .about("Set a specific channel coordinate in the manifest file") + .arg( + Arg::with_name("channel") + .required(true) + .help("Name of the channel to use"), + ), + ) + .subcommand( + SubCommand::with_name("reset") + .about("Return to using the default channel in the manifest file"), + ) + .subcommand( + SubCommand::with_name("append") + .about("Append a subchannel to the current channel") + .arg( + Arg::with_name("subchannel") + .required(true) + .help("The subchannel to append"), + ), + ) + .subcommand( + SubCommand::with_name("testing") + .setting(AppSettings::Hidden) + .about("Creates a testing subchannel - for use in tooling only"), + ), + ); if cfg!(feature = "upgrade") { - app = app.subcommand(SubCommand::with_name("upgrade") - .about("Attempts to upgrade lal from artifactory")); + app = app.subcommand( + SubCommand::with_name("upgrade").about("Attempts to upgrade lal from artifactory"), + ); } let args = app.get_matches(); @@ -547,28 +891,32 @@ fn main() { // Allow lal configure without assumptions if let Some(a) = args.subcommand_matches("configure") { - result_exit("configure", - lal::configure(true, true, a.value_of("file").unwrap())); + result_exit( + "configure", + lal::configure(true, true, a.value_of("file").unwrap()), + ); } // Force config to exists before allowing remaining actions let config = Config::read() .map_err(|e| { error!("Configuration error: {}", e); - println!(""); + println!(); println!("If you just got upgraded use `lal configure `"); - println!("Site configs are found in {{install_prefix}}/share/lal/configs/ \ - and should auto-complete"); + println!( + "Site configs are found in {{install_prefix}}/share/lal/configs/ \ + and should auto-complete" + ); process::exit(1); }) .unwrap(); // Create a storage backend (something that implements storage/traits.rs) - let backend: Box = match &config.backend { - &BackendConfiguration::Artifactory(ref art_cfg) => { + let backend: Box = match config.backend { + BackendConfiguration::Artifactory(ref art_cfg) => { Box::new(ArtifactoryBackend::new(&art_cfg, &config.cache)) } - &BackendConfiguration::Local(ref local_cfg) => { + BackendConfiguration::Local(ref local_cfg) => { Box::new(LocalBackend::new(&local_cfg, &config.cache)) } }; @@ -577,14 +925,19 @@ fn main() { openssl_probe::init_ssl_cert_env_vars(); // Do upgrade checks or handle explicit `lal upgrade` here - #[cfg(feature = "upgrade")] handle_upgrade(&args, &config); + #[cfg(feature = "upgrade")] + handle_upgrade(&args, &config); // Allow lal init / clean without manifest existing in PWD if let Some(a) = args.subcommand_matches("init") { - result_exit("init", - lal::init(&config, - a.is_present("force"), - a.value_of("environment").unwrap())); + result_exit( + "init", + lal::init( + &config, + a.is_present("force"), + a.value_of("environment").unwrap(), + ), + ); } else if let Some(a) = args.subcommand_matches("clean") { let days = a.value_of("days").unwrap().parse().unwrap(); result_exit("clean", lal::clean(&config.cache, days)); @@ -614,7 +967,7 @@ fn main() { handle_manifest_agnostic_cmds(&args, &config, backend.deref(), explicit_env); // Force manifest to exist before allowing remaining actions - let manifest = Manifest::read() + let mut manifest = Manifest::read() .map_err(|e| { error!("Manifest error: {}", e); println!("Ensure manifest.json is valid json or run `lal init`"); @@ -623,7 +976,7 @@ fn main() { .unwrap(); // Subcommands that are environment agnostic - handle_environment_agnostic_cmds(&args, &manifest, backend.deref()); + handle_environment_agnostic_cmds(&args, &mut manifest, backend.deref()); // Force a valid container key configured in manifest and corr. value in config // NB: --env overrides sticky env overrides manifest.env @@ -637,12 +990,17 @@ fn main() { let container = handle_env_command(&args, &config, &env, &stickies); // Warn users who are using an unsupported environment - if !manifest.supportedEnvironments.clone().into_iter().any(|e| e == env) { + if manifest + .supportedEnvironments + .clone() + .into_iter() + .any(|e| e == env) + { let sub = args.subcommand_name().unwrap(); - warn!("Running {} command in unsupported {} environment", sub, env); + debug!("Running {} command in supported {} environment", sub, env); } else { let sub = args.subcommand_name().unwrap(); - debug!("Running {} command in supported {} environent", sub, env); + warn!("Running {} command in unsupported {} environment", sub, env); } // Main subcommands diff --git a/src/propagate.rs b/src/propagate.rs index 623401eb..bcb36895 100644 --- a/src/propagate.rs +++ b/src/propagate.rs @@ -1,80 +1,145 @@ +use super::{LalResult, Lockfile, Manifest}; use serde_json; use std::collections::BTreeSet; -use super::{LalResult, Manifest, Lockfile}; +use std::collections::HashMap; +/// Repo with channel +#[derive(Clone, Debug, Serialize)] +pub struct RepoWithChannel { + component: String, + channel: String, +} + +/// Possible dependency formats. +#[derive(Clone, Debug, Serialize)] +#[serde(untagged)] +pub enum Repo { + /// Name only + Short(String), + /// Name and channel + Long(RepoWithChannel), +} + +impl std::fmt::Display for Repo { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Repo::Short(s) => write!(f, "{}", s), + Repo::Long(c) => write!(f, "{}={}", c.component, c.channel), + } + } +} /// A single update of of a propagation -#[derive(Serialize)] +#[derive(Debug, Serialize)] pub struct SingleUpdate { /// Where to update dependencies - pub repo: String, + pub repo: Repo, /// Dependencies to update - pub dependencies: Vec, + pub dependencies: Vec, } /// A parallelizable update stage of a propagation -#[derive(Serialize, Default)] +#[derive(Debug, Serialize, Default)] pub struct UpdateStage { /// Updates to perform at this stage pub updates: Vec, } /// A set of sequential update steps that describe a propagation -#[derive(Serialize, Default)] +#[derive(Debug, Serialize, Default)] pub struct UpdateSequence { /// Update stages needed pub stages: Vec, } /// Compute the update sequence for a propagation -pub fn compute(lf: &Lockfile, component: &str) -> LalResult { +pub fn compute(lf: &Lockfile, components: &[String]) -> LalResult { // 1. collect the list of everything we want to build in between root and component - let all_required = lf.get_reverse_deps_transitively_for(component.into()); + let all_required = components + .iter() + .map(|c| lf.get_reverse_deps_transitively_for(c.to_owned())) + .fold(BTreeSet::::new(), |mut acc, r| { + for c in r { + acc.insert(c); + } + acc + }); let dependencies = lf.find_all_dependency_names(); // map String -> Set(names) + let channels = lf.find_all_channels(); // map String -> Set(Option(channel)) debug!("Needs updating: {:?}", all_required); debug!("Dependency table: {:?}", dependencies); + debug!("Channel table: {:?}", channels); // initialize mutables let mut result = UpdateSequence::default(); let mut remaining = all_required.clone(); // assume we already updated the component itself - let mut handled = vec![component.to_string()].into_iter().collect(); + let mut handled = components + .iter() + .map(ToOwned::to_owned) + .collect::>(); // create update stages while there is something left to update while !remaining.is_empty() { let mut stage = UpdateStage::default(); debug!("Remaining set: {:?}", remaining); - for dep in remaining.clone() { - debug!("Processing {}", dep); + for repo in remaining.clone() { + debug!("Processing {}", repo); // Consider transitive deps for dep, and check they are not in remaining - let deps_for_name = dependencies[&dep].clone(); - debug!("Deps for {} is {:?}", dep, deps_for_name); - let intersection = deps_for_name.intersection(&remaining).collect::>(); + let deps_for_name = dependencies[&repo].clone(); + debug!("Deps for {} is {:?}", repo, deps_for_name); + let intersection = deps_for_name + .intersection(&remaining) + .collect::>(); debug!("Intersection: {:?}", intersection); if intersection.is_empty() { // what to update is `handled` intersected with deps for this repo stage.updates.push(SingleUpdate { - repo: dep, - dependencies: deps_for_name - .intersection(&handled) - .cloned() - .collect(), - }); + repo: dep_to_repo(&repo, &channels), + dependencies: deps_for_name + .intersection(&handled) + .map(|d| dep_to_repo(d, &channels)) + .collect(), + }); } } // remove what we are doing in this stage from remaining for dep in &stage.updates { - remaining.remove(&dep.repo); - handled.insert(dep.repo.clone()); + let repo = match &dep.repo { + Repo::Short(s) => s, + Repo::Long(r) => &r.component, + }; + remaining.remove(repo); + handled.insert(repo.clone()); } result.stages.push(stage); } Ok(result) } +fn dep_to_repo(component: &str, channels: &HashMap>>) -> Repo { + let channels = channels.get(component); + let channel = match channels { + Some(channels) => { + let channels = channels.iter().filter_map(Clone::clone).collect::>(); + match channels.len() { + 0 => None, + 1 => Some(channels[0].clone()), + _ => panic!("Multiple channels found for {}: {:?}", component, channels), + } + } + _ => None, + }; + + let component = component.to_owned(); + match channel { + None => Repo::Short(component), + Some(channel) => Repo::Long(RepoWithChannel { component, channel }), + } +} /// Outputs the update path to the current manifest for a specific component /// @@ -83,26 +148,39 @@ pub fn compute(lf: &Lockfile, component: &str) -> LalResult { /// /// This will produce a set of sequential steps, each set itself being parallelizable. /// The resulting update steps can be performed in order to ensure `lal verify` is happy. -pub fn print(manifest: &Manifest, component: &str, json_output: bool) -> LalResult<()> { - debug!("Calculating update path for {}", component); +pub fn print(manifest: &Manifest, components: &[String], json_output: bool) -> LalResult<()> { + debug!("Calculating update path for components: {:?}", components); // TODO: allow taking a custom lockfile to be used outside a repo. - let lf = Lockfile::default().set_name(&manifest.name).populate_from_input()?; + let lf = Lockfile::default() + .set_name(&manifest.name) + .with_channel(manifest.channel.clone()) + .populate_from_input()?; - let result = compute(&lf, component)?; + let result = compute(&lf, &components)?; if json_output { let encoded = serde_json::to_string_pretty(&result)?; println!("{}", encoded); } else { - println!("Assuming {} has been updated:", component); + println!( + "Assuming the following components been updated: {:?}", + components + ); let mut i = 1; for stage in result.stages { println!("Stage {}:", i); for update in stage.updates { - println!("- update [{}] in {}", - update.dependencies.join(", "), - update.repo); + println!( + "- update [{}] in {}", + update + .dependencies + .iter() + .map(ToString::to_string) + .collect::>() + .join(", "), + update.repo + ); } i += 1; } diff --git a/src/publish.rs b/src/publish.rs index 7edfa031..6e4edc07 100644 --- a/src/publish.rs +++ b/src/publish.rs @@ -1,8 +1,9 @@ use std::path::Path; // Need both the struct and the trait -use storage::Backend; -use super::{LalResult, CliError, Lockfile}; +use super::{CliError, LalResult, Lockfile}; +use crate::channel::Channel; +use crate::storage::Backend; /// Publish a release build to the storage backend /// @@ -18,13 +19,11 @@ pub fn publish(name: &str, backend: &T) -> LalResult<()> { let lock = Lockfile::release_build()?; - let version = lock.version - .parse::() - .map_err(|e| { - error!("Release build not done --with-version=$BUILD_VERSION"); - debug!("Error: {}", e); - CliError::MissingReleaseBuild - })?; + let version = lock.version.parse::().map_err(|e| { + error!("Release build not done --with-version=$BUILD_VERSION"); + debug!("Error: {}", e); + CliError::MissingReleaseBuild + })?; if lock.sha.is_none() { warn!("Release build not done --with-sha=$(git rev-parse HEAD)"); @@ -33,8 +32,10 @@ pub fn publish(name: &str, backend: &T) -> LalResult<()> { // always publish to the environment in the lockfile let env = lock.environment; - info!("Publishing {}={} to {}", name, version, env); - backend.publish_artifact(name, version, &env)?; + let channel = Channel::from_option(&lock.channel); + + info!("Publishing {}={}/{} to {}", name, channel.version_string(), version, env); + backend.publish_artifact(name, version, channel, &env)?; Ok(()) } diff --git a/src/query.rs b/src/query.rs index c036ad00..a8f88320 100644 --- a/src/query.rs +++ b/src/query.rs @@ -1,26 +1,34 @@ use std::io::{self, Write}; -use storage::Backend; -use super::{LalResult, CliError}; +use super::{CliError, LalResult}; +use crate::channel::Channel; +use crate::storage::Backend; /// Prints a list of versions associated with a component -pub fn query(backend: &Backend, _env: Option<&str>, component: &str, last: bool) -> LalResult<()> { +pub fn query( + backend: &dyn Backend, + env: Option<&str>, + channel: Option<&str>, + component: &str, + last: bool, +) -> LalResult<()> { + let channel = Channel::from_option(&channel); if component.to_lowercase() != component { return Err(CliError::InvalidComponentName(component.into())); } - let env = match _env { + let env = match env { None => { - error!("query is no longer allowed without an explicit environment"); - return Err(CliError::EnvironmentUnspecified) - }, - Some(e) => e + error!("query is no longer allowed without an explicit environment. Specify an environment by passing the -e flag to lal"); + return Err(CliError::EnvironmentUnspecified); + } + Some(e) => e, }; if last { - let ver = backend.get_latest_version(component, env)?; + let ver = backend.get_latest_version(component, env, &channel)?; println!("{}", ver); } else { - let vers = backend.get_versions(component, env)?; + let vers = backend.get_versions(component, env, &channel)?; for v in vers { println!("{}", v); // needed because sigpipe handling is broken for stdout atm diff --git a/src/remove.rs b/src/remove.rs index a3e96429..f0f79ae8 100644 --- a/src/remove.rs +++ b/src/remove.rs @@ -16,7 +16,11 @@ pub fn remove(manifest: &Manifest, xs: Vec, save: bool, savedev: bool) - // remove entries in xs from manifest. if save || savedev { let mut mf = manifest.clone(); - let mut hmap = if save { mf.dependencies.clone() } else { mf.devDependencies.clone() }; + let mut hmap = if save { + mf.dependencies.clone() + } else { + mf.devDependencies.clone() + }; for component in xs.clone() { // We could perhaps allow people to just specify ANY dependency // and have a generic save flag, which we could infer from diff --git a/src/shell.rs b/src/shell.rs index 6ed38e3e..4fb77564 100644 --- a/src/shell.rs +++ b/src/shell.rs @@ -1,9 +1,9 @@ -use std::process::Command; use std::env; use std::path::Path; +use std::process::Command; use std::vec::Vec; -use super::{Config, Container, CliError, LalResult}; +use super::{CliError, Config, Container, LalResult}; /// Verifies that `id -u` and `id -g` are both 1000 /// @@ -11,23 +11,27 @@ use super::{Config, Container, CliError, LalResult}; /// so for builds to work with the default containers, user ids and group ids /// should match a defined linux setup of 1000:1000. fn permission_sanity_check() -> LalResult<()> { - let uid_output = Command::new("id").arg("-u").output()?; - let uid_str = String::from_utf8_lossy(&uid_output.stdout); - let uid = uid_str.trim().parse::().unwrap(); // trust `id -u` is sane - - let gid_output = Command::new("id").arg("-g").output()?; - let gid_str = String::from_utf8_lossy(&gid_output.stdout); - let gid = gid_str.trim().parse::().unwrap(); // trust `id -g` is sane - - if uid != 1000 || gid != 1000 { - return Err(CliError::DockerPermissionSafety(format!("UID and GID are not 1000:1000"), - uid, - gid)); + let user_id = get_id("-u")?; + let group_id = get_id("-g")?; + + if user_id != 1000 || group_id != 1000 { + return Err(CliError::DockerPermissionSafety( + "UID and GID are not 1000:1000".to_string(), + user_id, + group_id, + )); } Ok(()) } +fn get_id(flag: &str) -> LalResult { + let id_output = Command::new("id").arg(flag).output()?; + let id_str = String::from_utf8_lossy(&id_output.stdout); + // trust `id` is sane with sane flags + Ok(id_str.trim().parse::().unwrap()) +} + /// Gets the ID of a docker container /// /// Uses the `docker images` command to find the image ID of the specified @@ -37,18 +41,20 @@ fn permission_sanity_check() -> LalResult<()> { /// docker images returns no output. fn get_docker_image_id(container: &Container) -> LalResult { trace!("Using docker images to find ID of container {}", container); - let image_id_output = - Command::new("docker").arg("images").arg("-q").arg(container.to_string()).output()?; - let image_id_str: String = String::from_utf8_lossy(&image_id_output.stdout).trim().into(); - match image_id_str.len() { - 0 => { - trace!("Could not find ID"); - Err(CliError::DockerImageNotFound(container.to_string())) - } - _ => { - trace!("Found ID {}", image_id_str); - Ok(image_id_str.into()) - } + let image_id_output = Command::new("docker") + .arg("images") + .arg("-q") + .arg(container.to_string()) + .output()?; + let image_id_str: String = String::from_utf8_lossy(&image_id_output.stdout) + .trim() + .into(); + if image_id_str.is_empty() { + trace!("Could not find ID"); + Err(CliError::DockerImageNotFound(container.to_string())) + } else { + trace!("Found ID {}", image_id_str); + Ok(image_id_str) } } @@ -60,7 +66,10 @@ fn get_docker_image_id(container: &Container) -> LalResult { /// command status() call fails for a different reason. fn pull_docker_image(container: &Container) -> LalResult<()> { trace!("Pulling container {}", container); - let s = Command::new("docker").arg("pull").arg(container.to_string()).status()?; + let s = Command::new("docker") + .arg("pull") + .arg(container.to_string()) + .status()?; if !s.success() { trace!("Pull failed"); return Err(CliError::SubprocessFailure(s.code().unwrap_or(1001))); @@ -76,7 +85,7 @@ fn pull_docker_image(container: &Container) -> LalResult<()> { /// Returns Ok(()) if the command is successful, Err(CliError::SubprocessFailure) /// if `bash -c` fails or is interrupted by a signal, Err(CliError::Io) if the /// command status() call fails for a different reason. -fn build_docker_image(container: &Container, instructions: Vec) -> LalResult<()> { +fn build_docker_image(container: &Container, instructions: &[String]) -> LalResult<()> { trace!("Building docker image for {}", container); let instruction_strings = instructions.join("\\n"); trace!("Build instructions: \n{}", instruction_strings); @@ -84,9 +93,10 @@ fn build_docker_image(container: &Container, instructions: Vec) -> LalRe let instruction_strings = instruction_strings.replace("'", "'\\''"); let s = Command::new("bash") .arg("-c") - .arg(format!("echo -e '{}' | docker build --tag {} -", - instruction_strings, - container)) + .arg(format!( + "echo -e '{}' | docker build --tag {} -", + instruction_strings, container + )) .status()?; if !s.success() { trace!("Build failed"); @@ -119,11 +129,10 @@ fn fixup_docker_container(container: &Container, u: u32, g: u32) -> LalResult LalResult { - info!("Found container {}, image id is {}", modified_container, id); - } - Err(_) => { - let instructions: Vec = - vec![ - format!("FROM {}", container), - "USER root".into(), - format!("RUN groupmod -g {} lal && usermod -u {} lal", g, u), - "USER lal".into(), - ]; - info!("Attempting to build container {}...", modified_container); - build_docker_image(&modified_container, instructions)?; - } - }; + if let Ok(id) = get_docker_image_id(&modified_container) { + info!("Found container {}, image id is {}", modified_container, id); + } else { + let instructions: Vec = vec![ + format!("FROM {}", container), + "USER root".into(), + format!("RUN groupmod -g {} lal && usermod -u {} lal", g, u), + "USER lal".into(), + ]; + info!("Attempting to build container {}...", modified_container); + build_docker_image(&modified_container, &instructions)?; + } trace!("Fixup for user {}:{} succeeded", u, g); Ok(modified_container) } @@ -168,7 +173,6 @@ pub fn docker_run( flags: &DockerRunFlags, modes: &ShellModes, ) -> LalResult<()> { - let mut modified_container_option: Option = None; trace!("Performing docker permission sanity check"); @@ -177,10 +181,11 @@ pub fn docker_run( CliError::DockerPermissionSafety(_, u, g) => { if u == 0 { // Do not run as root - return Err(CliError::DockerPermissionSafety("Cannot run container as root user" - .into(), - u, - g)); + return Err(CliError::DockerPermissionSafety( + "Cannot run container as root user".into(), + u, + g, + )); } modified_container_option = Some(fixup_docker_container(container, u, g)?); } @@ -194,7 +199,7 @@ pub fn docker_run( let container = modified_container_option.as_ref().unwrap_or(container); trace!("Finding home and cwd"); - let home = env::home_dir().unwrap(); // crash if no $HOME + let home = dirs::home_dir().unwrap(); // crash if no $HOME let pwd = env::current_dir().unwrap(); // construct arguments vector @@ -202,10 +207,12 @@ pub fn docker_run( for mount in cfg.mounts.clone() { trace!(" - mounting {}", mount.src); args.push("-v".into()); - let mnt = format!("{}:{}{}", - mount.src, - mount.dest, - if mount.readonly { ":ro" } else { "" }); + let mnt = format!( + "{}:{}{}", + mount.src, + mount.dest, + if mount.readonly { ":ro" } else { "" } + ); args.push(mnt); } trace!(" - mounting {}", pwd.display()); @@ -220,7 +227,10 @@ pub fn docker_run( args.push("--env=DISPLAY".into()); args.push("-v".into()); // xauth also needed for `ssh -X` through `lal -X` - args.push(format!("{}/.Xauthority:/home/lal/.Xauthority:ro", home.display())); + args.push(format!( + "{}/.Xauthority:/home/lal/.Xauthority:ro", + home.display() + )); // QT compat args.push("--env=QT_X11_NO_MITSHM=1".into()); } @@ -232,6 +242,10 @@ pub fn docker_run( args.push(format!("--env={}", var)); } + for cap in modes.capabilities.clone() { + args.push(format!("--cap-add={}", cap)); + } + if flags.privileged { args.push("--privileged".into()) } @@ -265,7 +279,7 @@ pub fn docker_run( print!(" {}", arg); } } - println!(""); + println!(); } else { trace!("Entering docker"); let s = Command::new("docker").args(&args).status()?; @@ -288,10 +302,10 @@ pub struct ShellModes { pub host_networking: bool, /// Environment variables pub env_vars: Vec, + /// Additional capabilities + pub capabilities: Vec, } - - /// Mounts and enters `.` in an interactive bash shell using the configured container. /// /// If a command vector is given, this is called non-interactively instead of /bin/bash @@ -309,7 +323,7 @@ pub fn shell( let flags = DockerRunFlags { interactive: cmd.is_none() || cfg.interactive, - privileged: privileged, + privileged, }; let mut bash = vec![]; if let Some(cmdu) = cmd { @@ -328,7 +342,7 @@ pub fn script( cfg: &Config, container: &Container, name: &str, - args: Vec<&str>, + args: &[&str], modes: &ShellModes, privileged: bool, ) -> LalResult<()> { @@ -339,7 +353,7 @@ pub fn script( let flags = DockerRunFlags { interactive: cfg.interactive, - privileged: privileged, + privileged, }; // Simply run the script by adding on the arguments @@ -348,5 +362,5 @@ pub fn script( "-c".into(), format!("source {}; main {}", pth.display(), args.join(" ")), ]; - Ok(docker_run(cfg, container, cmd, &flags, modes)?) + docker_run(cfg, container, cmd, &flags, modes) } diff --git a/src/stash.rs b/src/stash.rs index 9bbf79bb..3875addd 100644 --- a/src/stash.rs +++ b/src/stash.rs @@ -1,8 +1,7 @@ use std::path::Path; -use storage::CachedBackend; -use super::{CliError, LalResult, Manifest, Lockfile}; - +use super::{CliError, LalResult, Lockfile, Manifest}; +use crate::storage::CachedBackend; /// Saves current build `./OUTPUT` to the local cache under a specific name /// diff --git a/src/status.rs b/src/status.rs index f82d5cce..dc3d4bfe 100644 --- a/src/status.rs +++ b/src/status.rs @@ -1,12 +1,26 @@ -use ansi_term::{Colour, ANSIString}; -use core::input; -use super::{Lockfile, CliError, LalResult, Manifest}; +use super::{CliError, LalResult, Lockfile, Manifest}; +use crate::core::input; +use ansi_term::{ANSIString, Colour}; -fn version_string(lf: Option<&Lockfile>, show_ver: bool, show_time: bool) -> ANSIString<'static> { +fn version_string(lf: Option<&Lockfile>, show_ver: bool, show_time: bool) -> ANSIString { if let Some(lock) = lf { - let ver_color = if lock.version.parse::().is_ok() { 12 } else { 11 }; - let verstr = Colour::Fixed(ver_color) - .paint(format!("({}-{})", lock.version, lock.environment.clone())); + let ver_color = if lock.version.parse::().is_ok() { + 12 + } else { + 11 + }; + let chstr = match &lock.channel { + // Don't print the default/empty path. + Some(ref path) if *path == "/" => String::new(), + None => String::new(), + Some(path) => format!("{}:", path), + }; + let verstr = Colour::Fixed(ver_color).paint(format!( + "({}{}-{})", + chstr, + lock.version, + lock.environment.clone() + )); let timestr = if let Some(ref time) = lock.built { Colour::Fixed(14).paint(format!("({})", time)) } else { @@ -30,7 +44,7 @@ fn status_recurse( dep: &str, lf: &Lockfile, n: usize, - parent_indent: Vec, + parent_indent: &[bool], show_ver: bool, show_time: bool, ) { @@ -46,17 +60,19 @@ fn status_recurse( res + (if ws_only { " " } else { "│ " }) }); - println!("│ {}{}─{} {} {}", - ws, - turn_char, - fork_char, - k, - version_string(Some(sublock), show_ver, show_time)); + println!( + "│ {}{}─{} {} {}", + ws, + turn_char, + fork_char, + k, + version_string(Some(sublock), show_ver, show_time) + ); - let mut next_indent = parent_indent.clone(); + let mut next_indent = parent_indent.to_owned(); next_indent.push(is_last); - status_recurse(k, sublock, n + 1, next_indent, show_ver, show_time); + status_recurse(k, sublock, n + 1, &next_indent, show_ver, show_time); } } @@ -94,8 +110,8 @@ pub fn status(manifest: &Manifest, full: bool, show_ver: bool, show_time: bool) }; // list children in --full mode // NB: missing deps will not be populatable - let has_children = full && !dep.missing && - !&lf.dependencies[&dep.name].dependencies.is_empty(); + let has_children = + full && !dep.missing && !&lf.dependencies[&dep.name].dependencies.is_empty(); let fork_char = if has_children { "┬" } else { "─" }; let is_last = i == len - 1; let turn_char = if is_last { "â””" } else { "├" }; @@ -106,12 +122,14 @@ pub fn status(manifest: &Manifest, full: bool, show_ver: bool, show_time: bool) println!("{}─{} {} {}", turn_char, fork_char, level1, ver_str); if has_children { - trace!("Attempting to get {} out of lockfile deps {:?}", - dep.name, - lf.dependencies); + trace!( + "Attempting to get {} out of lockfile deps {:?}", + dep.name, + lf.dependencies + ); // dep unwrap relies on populate_from_input try! reading all lockfiles earlier let sub_lock = &lf.dependencies[&dep.name]; - status_recurse(&dep.name, sub_lock, 1, vec![], show_ver, show_time); + status_recurse(&dep.name, sub_lock, 1, &[], show_ver, show_time); } } diff --git a/src/storage/artifactory.rs b/src/storage/artifactory.rs index 7a662814..68cb2435 100644 --- a/src/storage/artifactory.rs +++ b/src/storage/artifactory.rs @@ -1,23 +1,23 @@ #![allow(missing_docs)] -use std::vec::Vec; -use std::io::{Read, Write}; use std::fs::File; +use std::io::{Read, Write}; use std::path::{Path, PathBuf}; +use std::vec::Vec; #[cfg(feature = "upgrade")] use semver::Version; -use serde_json; -use sha1; -use hyper::{self, Client}; -use hyper::net::HttpsConnector; use hyper::header::{Authorization, Basic}; +use hyper::net::HttpsConnector; use hyper::status::StatusCode; +use hyper::{self, Client}; use hyper_native_tls::NativeTlsClient; +use serde_json; +use sha1; -use core::{CliError, LalResult}; - +use crate::channel::Channel; +use crate::core::{CliError, LalResult}; /// Artifactory credentials #[derive(Serialize, Deserialize, Clone)] @@ -41,9 +41,10 @@ pub struct ArtifactoryConfig { pub vgroup: String, /// Optional publish credentials pub credentials: Option, + /// Optional max number of upload retries + pub upload_retries: Option, } - // Need these to query for stored artifacts: // This query has tons of info, but we only care about the version // And the version is encoded in children.uri with leading slash @@ -61,7 +62,10 @@ fn hyper_req(url: &str) -> LalResult { let client = Client::with_connector(HttpsConnector::new(NativeTlsClient::new().unwrap())); let mut res = client.get(url).send()?; if res.status != hyper::Ok { - return Err(CliError::BackendFailure(format!("GET request with {}", res.status))); + return Err(CliError::BackendFailure(format!( + "GET request with {}", + res.status + ))); } let mut body = String::new(); res.read_to_string(&mut body)?; @@ -74,7 +78,10 @@ pub fn http_download_to_path(url: &str, save: &PathBuf) -> LalResult<()> { let client = Client::with_connector(HttpsConnector::new(NativeTlsClient::new().unwrap())); let mut res = client.get(url).send()?; if res.status != hyper::Ok { - return Err(CliError::BackendFailure(format!("GET request with {}", res.status))); + return Err(CliError::BackendFailure(format!( + "GET request with {}", + res.status + ))); } if cfg!(feature = "progress") { @@ -86,8 +93,10 @@ pub fn http_download_to_path(url: &str, save: &PathBuf) -> LalResult<()> { let mut buffer = [0; 1024 * 64]; let mut f = File::create(save)?; let pb = ProgressBar::new(total_size); - pb.set_style(ProgressStyle::default_bar() - .template("{bar:40.yellow/black} {bytes}/{total_bytes} ({eta})")); + pb.set_style( + ProgressStyle::default_bar() + .template("{bar:40.yellow/black} {bytes}/{total_bytes} ({eta})"), + ); while downloaded < total_size { let read = res.read(&mut buffer)?; @@ -106,7 +115,6 @@ pub fn http_download_to_path(url: &str, save: &PathBuf) -> LalResult<()> { Ok(()) } - /// Query the Artifactory storage api /// /// This will get, then parse all results as u32s, and return this list. @@ -114,16 +122,16 @@ pub fn http_download_to_path(url: &str, save: &PathBuf) -> LalResult<()> { fn get_storage_versions(uri: &str) -> LalResult> { debug!("GET {}", uri); - let resp = hyper_req(uri) - .map_err(|e| { - warn!("Failed to GET {}: {}", uri, e); - CliError::BackendFailure("No version information found on API".into()) - })?; + let resp = hyper_req(uri).map_err(|e| { + warn!("Failed to GET {}: {}", uri, e); + CliError::BackendFailure("No version information found on API".into()) + })?; trace!("Got body {}", resp); let res: ArtifactoryStorageResponse = serde_json::from_str(&resp)?; - let mut builds: Vec = res.children + let mut builds: Vec = res + .children .iter() .map(|r| r.uri.as_str()) .map(|r| r.trim_matches('/')) @@ -142,8 +150,6 @@ header! {(XCheckSumSha1, "X-Checksum-Sha1") => [String]} /// This is using a http basic auth PUT to artifactory using config credentials. fn upload_artifact(arti: &ArtifactoryConfig, uri: &str, f: &mut File) -> LalResult<()> { if let Some(creds) = arti.credentials.clone() { - let client = Client::new(); - let mut buffer: Vec = Vec::new(); f.read_to_end(&mut buffer)?; @@ -153,51 +159,87 @@ fn upload_artifact(arti: &ArtifactoryConfig, uri: &str, f: &mut File) -> LalResu sha.update(&buffer); let auth = Authorization(Basic { - username: creds.username, - password: Some(creds.password), - }); - - // upload the artifact - info!("PUT {}", full_uri); - let resp = client.put(&full_uri[..]).header(auth.clone()).body(&buffer[..]).send()?; - debug!("resp={:?}", resp); - let respstr = format!("{} from PUT {}", resp.status, full_uri); - if resp.status != StatusCode::Created { - return Err(CliError::UploadFailure(respstr)); - } - debug!("{}", respstr); - - // do another request to get the hash on artifactory - // jfrog api does not allow do do both at once - and this also creates the md5 (somehow) - // this creates ${full_uri}.sha1 and ${full_uri}.md5 (although we just gave it the sha..) - // This `respsha` can fail if engci-maven becomes inconsistent. NotFound has been seen. - // And that makes no sense because the above must have returned Created to get here.. - info!("PUT {} (X-Checksum-Sha1)", full_uri); - let respsha = client - .put(&full_uri[..]) - .header(XCheckSumDeploy("true".into())) - .header(XCheckSumSha1(sha.digest().to_string())) - .header(auth) - .send()?; - debug!("respsha={:?}", respsha); - let respshastr = format!("{} from PUT {} (X-Checksum-Sha1)", respsha.status, full_uri); - if respsha.status != StatusCode::Created { - return Err(CliError::UploadFailure(respshastr)); + username: creds.username, + password: Some(creds.password), + }); + + let client = Client::with_connector(HttpsConnector::new(NativeTlsClient::new().unwrap())); + + let mut remaining_retries = arti.upload_retries.unwrap_or(0); + loop { + match upload_artifact_attempt(&client, &auth, &full_uri, &buffer, &sha) { + Err(e) => { + if remaining_retries == 0 { + return Err(e); + } + warn!( + "Upload attempt failed! Remaining attempts: {}", + remaining_retries + ); + remaining_retries -= 1; + } + Ok(()) => return Ok(()), + } } - debug!("{}", respshastr); - - Ok(()) } else { Err(CliError::MissingBackendCredentials) } } +/// Make an attempt to upload an artifact +/// +/// Uses borrowed client, credentials, full_uri, buffer to upload and its sha +fn upload_artifact_attempt( + client: &Client, + auth: &Authorization, + full_uri: &str, + buffer: &[u8], + sha: &sha1::Sha1, +) -> LalResult<()> { + // attempt upload the artifact + info!("PUT {}", full_uri); + let resp = client + .put(&full_uri[..]) + .header(auth.clone()) + .body(&buffer[..]) + .send()?; + debug!("resp={:?}", resp); + let respstr = format!("{} from PUT {}", resp.status, full_uri); + if resp.status != StatusCode::Created { + return Err(CliError::UploadFailure(respstr)); + } + debug!("{}", respstr); + + // do another request to get the hash on artifactory + // jfrog api does not allow do do both at once - and this also creates the md5 (somehow) + // this creates ${full_uri}.sha1 and ${full_uri}.md5 (although we just gave it the sha..) + // This `respsha` can fail if engci-maven becomes inconsistent. NotFound has been seen. + // And that makes no sense because the above must have returned Created to get here.. + info!("PUT {} (X-Checksum-Sha1)", full_uri); + let respsha = client + .put(&full_uri[..]) + .header(XCheckSumDeploy("true".into())) + .header(XCheckSumSha1(sha.digest().to_string())) + .header(auth.clone()) + .send()?; + debug!("respsha={:?}", respsha); + let respshastr = format!("{} from PUT {} (X-Checksum-Sha1)", respsha.status, full_uri); + if respsha.status != StatusCode::Created { + return Err(CliError::UploadFailure(respshastr)); + } + debug!("{}", respshastr); + + Ok(()) +} + /// Get the maximal version number from the storage api fn get_storage_as_u32(uri: &str) -> LalResult { if let Some(&latest) = get_storage_versions(uri)?.iter().max() { Ok(latest) } else { - Err(CliError::BackendFailure("No version information found on API".into())) + Err(CliError::BackendFailure( + "No version information found on API".into(), + )) } } @@ -208,48 +250,63 @@ fn get_dependency_env_url( version: u32, env: &str, ) -> String { - let tar_url = format!("{}/{}/env/{}/{}/{}/{}.tar.gz", - art_cfg.slave, - art_cfg.vgroup, - env, - name, - version.to_string(), - name); + let tar_url = format!( + "{}/{}/env/{}/{}/{}/{}.tar.gz", + art_cfg.slave, + art_cfg.vgroup, + env, + name, + version.to_string(), + name + ); trace!("Inferring tarball location as {}", tar_url); tar_url } +fn get_api_storage_url( + art_cfg: &ArtifactoryConfig, + name: &str, + env: &str, + channel: &Channel, +) -> String { + format!( + "{}/api/storage/{}/{}env/{}/{}", + art_cfg.master, + art_cfg.release, + channel.http_string(), + env, + name + ) +} + fn get_dependency_url_latest( art_cfg: &ArtifactoryConfig, name: &str, env: &str, + channel: &Channel, ) -> LalResult { - let url = format!("{}/api/storage/{}/{}/{}/{}", - art_cfg.master, - art_cfg.release, - "env", - env, - name); + let url = get_api_storage_url(art_cfg, name, env, channel); let v = get_storage_as_u32(&url)?; debug!("Found latest version as {}", v); Ok(Component { - location: get_dependency_env_url(art_cfg, name, v, env), - version: v, - name: name.into(), - }) + location: get_dependency_env_url(art_cfg, name, v, env), + version: v, + name: name.into(), + channel: channel.clone(), + }) } // This queries the API for the default location // if a default exists, then all our current multi-builds must exist -fn get_latest_versions(art_cfg: &ArtifactoryConfig, name: &str, env: &str) -> LalResult> { - let url = format!("{}/api/storage/{}/{}/{}/{}", - art_cfg.master, - art_cfg.release, - "env", - env, - name); +fn get_latest_versions( + art_cfg: &ArtifactoryConfig, + name: &str, + env: &str, + channel: &Channel, +) -> LalResult> { + let url = get_api_storage_url(art_cfg, name, env, channel); get_storage_versions(&url) } @@ -260,15 +317,17 @@ fn get_tarball_uri( name: &str, version: Option, env: &str, + channel: &Channel, ) -> LalResult { if let Some(v) = version { Ok(Component { - location: get_dependency_env_url(art_cfg, name, v, env), - version: v, - name: name.into(), - }) + location: get_dependency_env_url(art_cfg, name, v, env), + version: v, + name: name.into(), + channel: channel.clone(), + }) } else { - get_dependency_url_latest(art_cfg, name, env) + get_dependency_url_latest(art_cfg, name, env, channel) } } @@ -292,15 +351,15 @@ pub fn get_latest_lal_version() -> LalResult { // canonical latest url let uri = "https://engci-maven-master.cisco.com/artifactory/api/storage/CME-release/lal"; debug!("GET {}", uri); - let resp = hyper_req(uri) - .map_err(|e| { - warn!("Failed to GET {}: {}", uri, e); - CliError::BackendFailure("No version information found on API".into()) - })?; + let resp = hyper_req(uri).map_err(|e| { + warn!("Failed to GET {}: {}", uri, e); + CliError::BackendFailure("No version information found on API".into()) + })?; trace!("Got body {}", resp); let res: ArtifactoryStorageResponse = serde_json::from_str(&resp)?; - let latest: Option = res.children + let latest: Option = res + .children .iter() .map(|r| r.uri.trim_matches('/').to_string()) .inspect(|v| trace!("Found lal version {}", v)) @@ -309,13 +368,17 @@ pub fn get_latest_lal_version() -> LalResult { if let Some(l) = latest { Ok(LatestLal { - version: l.clone(), - url: format!("https://engci-maven.cisco.com/artifactory/CME-group/lal/{}/lal.tar.gz", - l), - }) + version: l.clone(), + url: format!( + "https://engci-maven.cisco.com/artifactory/CME-group/lal/{}/lal.tar.gz", + l + ), + }) } else { warn!("Failed to parse version information from artifactory storage api for lal"); - Err(CliError::BackendFailure("No version information found on API".into())) + Err(CliError::BackendFailure( + "No version information found on API".into(), + )) } } @@ -332,7 +395,7 @@ pub struct ArtifactoryBackend { impl ArtifactoryBackend { pub fn new(cfg: &ArtifactoryConfig, cache: &str) -> Self { // TODO: create hyper clients in here rather than once per download - ArtifactoryBackend { + Self { config: cfg.clone(), cache: cache.into(), } @@ -344,12 +407,12 @@ impl ArtifactoryBackend { /// This is intended to be used by the caching trait `CachedBackend`, but for /// specific low-level use cases, these methods can be used directly. impl Backend for ArtifactoryBackend { - fn get_versions(&self, name: &str, loc: &str) -> LalResult> { - get_latest_versions(&self.config, name, loc) + fn get_versions(&self, name: &str, loc: &str, channel: &Channel) -> LalResult> { + get_latest_versions(&self.config, name, loc, channel) } - fn get_latest_version(&self, name: &str, loc: &str) -> LalResult { - let latest = get_dependency_url_latest(&self.config, name, loc)?; + fn get_latest_version(&self, name: &str, loc: &str, channel: &Channel) -> LalResult { + let latest = get_dependency_url_latest(&self.config, name, loc, channel)?; Ok(latest.version) } @@ -358,19 +421,26 @@ impl Backend for ArtifactoryBackend { name: &str, version: Option, loc: &str, + channel: &Channel, ) -> LalResult { - get_tarball_uri(&self.config, name, version, loc) + get_tarball_uri(&self.config, name, version, loc, channel) } - fn publish_artifact(&self, name: &str, version: u32, env: &str) -> LalResult<()> { + fn publish_artifact( + &self, + name: &str, + version: u32, + channel: Channel, + env: &str, + ) -> LalResult<()> { // this fn basically assumes all the sanity checks have been performed // files must exist and lockfile must be sensible let artdir = Path::new("./ARTIFACT"); let tarball = artdir.join(format!("{}.tar.gz", name)); let lockfile = artdir.join("lockfile.json"); - // uri prefix if specific env upload - let prefix = format!("env/{}/", env); + // URI prefix based on environment and channel + let prefix = format!("{}env/{}/", channel.http_string(), env); let tar_uri = format!("{}{}/{}/{}.tar.gz", prefix, name, version, name); let mut tarf = File::open(tarball)?; @@ -388,3 +458,21 @@ impl Backend for ArtifactoryBackend { http_download_to_path(url, dest) } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_uri() { + let input = + get_api_storage_url(&ArtifactoryConfig::default(), "a", "b", &Channel::default()); + let output = "/api/storage//env/b/a"; + assert_eq!(input, output); + + let input = + get_api_storage_url(&ArtifactoryConfig::default(), "a", "b", &Channel::new("c")); + let output = "/api/storage//ch/c/env/b/a"; + assert_eq!(input, output); + } +} diff --git a/src/storage/download.rs b/src/storage/download.rs index a1fc897f..5c269de1 100644 --- a/src/storage/download.rs +++ b/src/storage/download.rs @@ -1,16 +1,34 @@ use std::fs; use std::path::{Path, PathBuf}; -use storage::{Backend, CachedBackend, Component}; -use core::{CliError, LalResult, output}; +use crate::channel::Channel; +use crate::core::{output, CliError, LalResult}; +use crate::storage::{Backend, CachedBackend, Component}; -fn is_cached(backend: &T, name: &str, version: u32, env: &str) -> bool { - get_cache_dir(backend, name, version, env).is_dir() +fn is_cached( + backend: &T, + name: &str, + version: u32, + env: &str, + channel: &Channel, +) -> bool { + get_cache_dir(backend, name, version, env, channel).is_dir() } -fn get_cache_dir(backend: &T, name: &str, version: u32, env: &str) -> PathBuf { +fn get_cache_dir( + backend: &T, + name: &str, + version: u32, + env: &str, + channel: &Channel, +) -> PathBuf { let cache = backend.get_cache_dir(); - Path::new(&cache).join("environments").join(env).join(name).join(version.to_string()) + Path::new(&cache) + .join(channel.to_path()) + .join("environments") + .join(env) + .join(name) + .join(version.to_string()) } fn store_tarball( @@ -18,9 +36,10 @@ fn store_tarball( name: &str, version: u32, env: &str, + channel: &Channel, ) -> Result<(), CliError> { // 1. mkdir -p cacheDir/$name/$version - let destdir = get_cache_dir(backend, name, version, env); + let destdir = get_cache_dir(backend, name, version, env, channel); if !destdir.is_dir() { fs::create_dir_all(&destdir)?; } @@ -40,8 +59,8 @@ fn store_tarball( // helper for the unpack_ functions fn extract_tarball_to_input(tarname: PathBuf, component: &str) -> LalResult<()> { - use tar::Archive; use flate2::read::GzDecoder; + use tar::Archive; let extract_path = Path::new("./INPUT").join(component); let _ = fs::remove_dir_all(&extract_path); // remove current dir if exists @@ -87,12 +106,17 @@ where &self, name: &str, environments: Vec, + channel: &Channel, ) -> LalResult> { use std::collections::BTreeSet; let mut result = BTreeSet::new(); let mut first_pass = true; for e in environments { - let eres: BTreeSet<_> = self.get_versions(name, &e)?.into_iter().take(100).collect(); + let eres: BTreeSet<_> = self + .get_versions(name, &e, channel)? + .into_iter() + .take(100) + .collect(); info!("Last versions for {} in {} env is {:?}", name, e, eres); if first_pass { // if first pass, can't take intersection with something empty, start with first result @@ -112,24 +136,53 @@ where name: &str, version: Option, env: &str, + channel: &Channel, ) -> LalResult<(PathBuf, Component)> { trace!("Locate component {}", name); - let component = self.get_component_info(name, version, env)?; + let component = self.get_component_info(name, version, env, channel)?; - if !is_cached(self, &component.name, component.version, env) { - // download to PWD then move it to stash immediately - let local_tarball = Path::new(".").join(format!("{}.tar.gz", name)); - self.raw_fetch(&component.location, &local_tarball)?; - store_tarball(self, name, component.version, env)?; + if !is_cached(self, &component.name, component.version, env, channel) { + self.cache_published_component(&component, env)?; } - assert!(is_cached(self, &component.name, component.version, env), - "cached component"); + assert!( + is_cached(self, &component.name, component.version, env, channel), + "cached component" + ); trace!("Fetching {} from cache", name); - let tarname = get_cache_dir(self, &component.name, component.version, env) - .join(format!("{}.tar.gz", name)); - Ok((tarname, component)) + let base_name = + get_cache_dir(self, &component.name, component.version, env, channel).join(name); + + // Older versions of lal may have stored the artefacts without the gz extension. Check for this and fix it. + // This is just a rename as the files were gzipped, however this was not reflected in their name. + let compressed_name = base_name.with_extension("tar.gz"); + let uncompressed_name = base_name.with_extension("tar"); + if uncompressed_name.exists() { + assert!(!compressed_name.exists()); + fs::rename(uncompressed_name, &compressed_name)?; + } + + if !(compressed_name.exists()) { + // The cache has somehow become corrupted, try to update it. + self.cache_published_component(&component, env)?; + assert!(compressed_name.exists()); + } + + Ok((compressed_name, component)) + } + + fn cache_published_component(&self, component: &Component, env: &str) -> LalResult<()> { + // Download to PWD then move it to stash immediately + let local_tarball = Path::new(".").join(format!("{}.tar.gz", component.name)); + self.raw_fetch(&component.location, &local_tarball)?; + store_tarball( + self, + &component.name, + component.version, + env, + &component.channel, + ) } // basic functionality for `fetch`/`update` @@ -138,12 +191,16 @@ where name: &str, version: Option, env: &str, + channel: &Channel, ) -> LalResult { - let (tarname, component) = self.retrieve_published_component(name, version, env)?; - - debug!("Unpacking tarball {} for {}", - tarname.to_str().unwrap(), - component.name); + let (tarname, component) = + self.retrieve_published_component(name, version, env, channel)?; + + debug!( + "Unpacking tarball {} for {}", + tarname.to_str().unwrap(), + component.name + ); extract_tarball_to_input(tarname, name)?; Ok(component) @@ -165,6 +222,7 @@ where .join(code) .join(format!("{}.tar.gz", name)); if !tarpath.is_file() { + debug!("Could not find {:?}", tarpath); return Err(CliError::MissingStashArtifact(format!("{}/{}", name, code))); } Ok(tarpath) @@ -172,7 +230,10 @@ where // helper for `stash` fn stash_output(&self, name: &str, code: &str) -> LalResult<()> { - let destdir = Path::new(&self.get_cache_dir()).join("stash").join(name).join(code); + let destdir = Path::new(&self.get_cache_dir()) + .join("stash") + .join(name) + .join(code); debug!("Creating {:?}", destdir); fs::create_dir_all(&destdir)?; diff --git a/src/storage/local.rs b/src/storage/local.rs index fff8931c..2f8ae17a 100644 --- a/src/storage/local.rs +++ b/src/storage/local.rs @@ -1,12 +1,12 @@ #![allow(missing_docs)] use std::fs; +use std::path::{Path, PathBuf}; use std::str::FromStr; use std::vec::Vec; -use std::path::{Path, PathBuf}; - -use core::{CliError, LalResult, config_dir, ensure_dir_exists_fresh}; +use crate::channel::Channel; +use crate::core::{config_dir, ensure_dir_exists_fresh, CliError, LalResult}; /// LocalBackend configuration options (currently none) #[derive(Serialize, Deserialize, Clone, Default)] @@ -24,7 +24,7 @@ pub struct LocalBackend { impl LocalBackend { pub fn new(cfg: &LocalConfig, cache: &str) -> Self { - LocalBackend { + Self { config: cfg.clone(), cache: cache.into(), } @@ -36,8 +36,14 @@ impl LocalBackend { /// This is intended to be used by the caching trait `CachedBackend`, but for /// specific low-level use cases, these methods can be used directly. impl Backend for LocalBackend { - fn get_versions(&self, name: &str, loc: &str) -> LalResult> { - let tar_dir = format!("{}/environments/{}/{}/", self.cache, loc, name); + fn get_versions(&self, name: &str, loc: &str, channel: &Channel) -> LalResult> { + let tar_dir = format!( + "{}{}/environments/{}/{}/", + self.cache, + channel.fs_string(), + loc, + name + ); let dentries = fs::read_dir(config_dir().join(tar_dir)); let mut versions = vec![]; for entry in dentries? { @@ -51,11 +57,13 @@ impl Backend for LocalBackend { Ok(versions) } - fn get_latest_version(&self, name: &str, loc: &str) -> LalResult { - if let Some(&last) = self.get_versions(name, loc)?.last() { + fn get_latest_version(&self, name: &str, loc: &str, channel: &Channel) -> LalResult { + if let Some(&last) = self.get_versions(name, loc, channel)?.last() { return Ok(last); } - Err(CliError::BackendFailure("No versions found on local storage".into())) + Err(CliError::BackendFailure( + "No versions found on local storage".into(), + )) } fn get_component_info( @@ -63,23 +71,39 @@ impl Backend for LocalBackend { name: &str, version: Option, loc: &str, + channel: &Channel, ) -> LalResult { info!("get_component_info: {} {:?} {}", name, version, loc); let v = if let Some(ver) = version { ver } else { - self.get_latest_version(name, loc)? + self.get_latest_version(name, loc, channel)? }; - let loc = format!("{}/environments/{}/{}/{}/{}.tar.gz", self.cache, loc, name, v, name); + let loc = format!( + "{}{}/environments/{}/{}/{}/{}.tar.gz", + self.cache, + channel.fs_string(), + loc, + name, + v, + name + ); Ok(Component { name: name.into(), version: v, location: loc, + channel: channel.clone(), }) } - fn publish_artifact(&self, name: &str, version: u32, env: &str) -> LalResult<()> { + fn publish_artifact( + &self, + name: &str, + version: u32, + channel: Channel, + env: &str, + ) -> LalResult<()> { // this fn basically assumes all the sanity checks have been performed // files must exist and lockfile must be sensible let artifactdir = Path::new("./ARTIFACT"); @@ -87,9 +111,17 @@ impl Backend for LocalBackend { let lockfile = artifactdir.join("lockfile.json"); // prefix with environment - let tar_dir = format!("{}/environments/{}/{}/{}/", self.cache, env, name, version); - let tar_path = format!("{}/environments/{}/{}/{}/{}.tar.gz", self.cache, env, name, version, name); - let lock_path = format!("{}/environments/{}/{}/{}/lockfile.json", self.cache, env, name, version); + let tar_dir = format!( + "{}{}/environments/{}/{}/{}/", + self.cache, + channel.fs_string(), + env, + name, + version + ); + debug!("Using dir {}", tar_dir); + let tar_path = format!("{}/{}.tar.gz", tar_dir, name); + let lock_path = format!("{}/lockfile.json", tar_dir); if let Some(full_tar_dir) = config_dir().join(tar_dir).to_str() { ensure_dir_exists_fresh(full_tar_dir)?; diff --git a/src/storage/mod.rs b/src/storage/mod.rs index 1e328c32..a5cb77ef 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -1,16 +1,16 @@ -pub use self::traits::{BackendConfiguration, Backend, CachedBackend, Component}; +pub use self::traits::{Backend, BackendConfiguration, CachedBackend, Component}; -pub use self::artifactory::{ArtifactoryConfig, Credentials, ArtifactoryBackend}; -pub use self::local::{LocalConfig, LocalBackend}; +pub use self::artifactory::{ArtifactoryBackend, ArtifactoryConfig, Credentials}; +pub use self::local::{LocalBackend, LocalConfig}; // Some special exports for lal upgrade - canonical releases are on artifactory atm #[cfg(feature = "upgrade")] -pub use self::artifactory::{LatestLal, get_latest_lal_version, http_download_to_path}; +pub use self::artifactory::{get_latest_lal_version, http_download_to_path, LatestLal}; -mod traits; mod artifactory; -mod local; mod download; +mod local; +mod traits; #[cfg(feature = "progress")] mod progress; diff --git a/src/storage/progress.rs b/src/storage/progress.rs index 531f34ec..dcf64411 100644 --- a/src/storage/progress.rs +++ b/src/storage/progress.rs @@ -33,13 +33,15 @@ pub struct ProgressReader { } */ impl ProgressReader { - pub fn new(mut rdr: R) -> io::Result> { + pub fn new(mut rdr: R) -> io::Result { let len = rdr.seek(SeekFrom::End(0))?; rdr.seek(SeekFrom::Start(0))?; let pb = ProgressBar::new(len); - pb.set_style(ProgressStyle::default_bar() - .template("{bar:40.green/black} {bytes}/{total_bytes} ({eta})")); - Ok(ProgressReader { rdr, pb }) + pb.set_style( + ProgressStyle::default_bar() + .template("{bar:40.green/black} {bytes}/{total_bytes} ({eta})"), + ); + Ok(Self { rdr, pb }) } } diff --git a/src/storage/traits.rs b/src/storage/traits.rs index be9c76da..f8a60fa9 100644 --- a/src/storage/traits.rs +++ b/src/storage/traits.rs @@ -1,7 +1,8 @@ use std::path::PathBuf; -use core::LalResult; use super::{ArtifactoryConfig, LocalConfig}; +use crate::channel::Channel; +use crate::core::LalResult; /// An enum struct for the currently configured `Backend` /// @@ -23,7 +24,6 @@ impl Default for BackendConfiguration { fn default() -> Self { BackendConfiguration::Artifactory(ArtifactoryConfig::default()) } } - /// The basic definition of a component as it exists online /// /// A component may have many build artifacts from many environments. @@ -32,6 +32,8 @@ pub struct Component { pub name: String, /// Version number pub version: u32, + /// Channel path + pub channel: Channel, /// The raw location of the component at the specified version number /// /// No restriction on how this information is encoded, but it must work with `raw_fetch` @@ -45,19 +47,31 @@ pub struct Component { /// We do rely on there being a basic API that can implement this trait though. pub trait Backend { /// Get a list of versions for a component in descending order - fn get_versions(&self, name: &str, loc: &str) -> LalResult>; + fn get_versions(&self, name: &str, loc: &str, channel: &Channel) -> LalResult>; /// Get the latest version of a component - fn get_latest_version(&self, name: &str, loc: &str) -> LalResult; + fn get_latest_version(&self, name: &str, loc: &str, channel: &Channel) -> LalResult; /// Get the version and location information of a component /// /// If no version is given, figure out what latest is - fn get_component_info(&self, name: &str, ver: Option, loc: &str) -> LalResult; + fn get_component_info( + &self, + name: &str, + ver: Option, + loc: &str, + channel: &Channel, + ) -> LalResult; /// Publish a release build's ARTIFACT to a specific location /// /// This will publish everything inside the ARTIFACT dir created by `lal build -r` - fn publish_artifact(&self, name: &str, version: u32, env: &str) -> LalResult<()>; + fn publish_artifact( + &self, + name: &str, + version: u32, + channel: Channel, + env: &str, + ) -> LalResult<()>; /// Raw fetch of location to a destination /// @@ -79,6 +93,7 @@ pub trait CachedBackend { &self, name: &str, environments: Vec, + channel: &Channel, ) -> LalResult>; /// Retrieve the location to a cached published component (downloading if necessary) @@ -87,6 +102,7 @@ pub trait CachedBackend { name: &str, version: Option, env: &str, + channel: &Channel, ) -> LalResult<(PathBuf, Component)>; /// Retrieve the location to a stashed component @@ -98,6 +114,7 @@ pub trait CachedBackend { name: &str, version: Option, env: &str, + channel: &Channel, ) -> LalResult; /// Retrieve and unpack a stashed component to INPUT @@ -105,4 +122,7 @@ pub trait CachedBackend { /// Add a stashed component from a folder fn stash_output(&self, name: &str, code: &str) -> LalResult<()>; + + /// Pull a component into the cache + fn cache_published_component(&self, component: &Component, env: &str) -> LalResult<()>; } diff --git a/src/update.rs b/src/update.rs index e3840e2b..61b77022 100644 --- a/src/update.rs +++ b/src/update.rs @@ -1,5 +1,6 @@ -use storage::CachedBackend; -use super::{LalResult, Manifest, CliError}; +use super::{CliError, Coordinates, LalResult, Manifest, TwoDCoordinates}; +use crate::channel::{parse_coords, Channel}; +use crate::storage::CachedBackend; /// Update specific dependencies outside the manifest /// @@ -12,61 +13,112 @@ use super::{LalResult, Manifest, CliError}; pub fn update( manifest: &Manifest, backend: &T, - components: Vec, + components: &[String], save: bool, savedev: bool, env: &str, ) -> LalResult<()> { debug!("Update specific deps: {:?}", components); + // Verify component names. + components + .iter() + .map(|c| c.split('=').collect::>()[0]) + .try_for_each(|c| { + if c.to_lowercase() == c { + Ok(()) + } else { + Err(CliError::InvalidComponentName(c.to_string())) + } + })?; + let mut error = None; let mut updated = Vec::with_capacity(components.len()); - for comp in &components { + let own_channel = Channel::from_option(&manifest.channel); + for comp in components { info!("Fetch {} {}", env, comp); - if comp.contains('=') { - let pair: Vec<&str> = comp.split('=').collect(); - if let Ok(n) = pair[1].parse::() { - if pair[0].to_lowercase() != pair[0] { - return Err(CliError::InvalidComponentName(pair[0].into())); - } - // standard fetch with an integer version - match backend.unpack_published_component(pair[0], Some(n), env) { - Ok(c) => updated.push(c), - Err(e) => { - warn!("Failed to update {} ({})", pair[0], e); - error = Some(e); - } + let comp_vec = comp.split('=').collect::>(); + let comp = comp_vec[0]; + + if comp_vec.len() == 1 { + // fetch without a specific version (latest) + + // First, identify the current channel. + let coords = if let Some(coords) = manifest.dependencies.get(comp) { + Some(coords) + } else if let Some(coords) = manifest.devDependencies.get(comp) { + Some(coords) + } else { + None + }; + let current_channel = if let Some(coords) = coords { + match coords { + Coordinates::OneD(_) => None, + Coordinates::TwoD(v) => Some(&v.channel), } } else { - // fetch from stash - this does not go into `updated` it it succeeds - // because we wont and cannot save stashed versions in the manifest - let _ = backend.unpack_stashed_component(pair[0], pair[1]).map_err(|e| { - warn!("Failed to update {} from stash ({})", pair[0], e); - error = Some(e); - }); - } - } else { - if &comp.to_lowercase() != comp { - return Err(CliError::InvalidComponentName(comp.clone())); - } - // fetch without a specific version (latest) + None + }; + let current_channel = Channel::from_option(¤t_channel); + debug!("Current channel: {}", current_channel); - // First, since this potentially goes in the manifest + // Second, since this potentially goes in the manifest // make sure the version is found for all supported environments: - let ver = backend - .get_latest_supported_versions(comp, manifest.supportedEnvironments.clone())? - .into_iter() - .max() - .ok_or(CliError::NoIntersectedVersion(comp.clone()))?; - info!("Fetch {} {}={}", env, comp, ver); + let ver = *backend + .get_latest_supported_versions( + comp, + manifest.supportedEnvironments.clone(), + ¤t_channel, + )? + .last() + .ok_or_else(|| CliError::NoIntersectedVersion(comp.to_string()))?; + info!("Fetch {} {}={}{}", env, comp, current_channel.version_string(), ver); - match backend.unpack_published_component(comp, Some(ver), env) { + match backend.unpack_published_component(comp, Some(ver), env, ¤t_channel) { Ok(c) => updated.push(c), Err(e) => { warn!("Failed to update {} ({})", &comp, e); error = Some(e); } } + } else { + // Put the original `=` signs back in place. + let coords = comp_vec.iter().skip(1).cloned().collect::>().join("="); + let (version, channel) = parse_coords(&coords); + + if version.is_some() || channel.is_some() { + // We have at least one of a version or a channel. + let channel = Channel::from_option(&channel); + + // Verify that the channel is valid. + if let Err(e) = channel.verify() { + warn!("Invalid channel {}", channel); + error = Some(e); + continue; + } + if let Err(e) = own_channel.contains(&channel) { + warn!("Failed to update {} ({})", &comp, e); + error = Some(e); + continue; + } + + match backend.unpack_published_component(comp, version, env, &channel) { + Ok(c) => updated.push(c), + Err(e) => { + warn!("Failed to update {} ({})", comp, e); + error = Some(e); + } + } + } else { + // fetch from stash - this does not go into `updated` it it succeeds + // because we won't and cannot save stashed versions in the manifest + let _ = backend + .unpack_stashed_component(comp, &coords) + .map_err(|e| { + warn!("Failed to update {} from stash ({})", comp, e); + error = Some(e); + }); + } } } if let Some(e) = error { @@ -77,21 +129,41 @@ pub fn update( if save || savedev { let mut mf = manifest.clone(); // find reference to correct list - let mut hmap = if save { mf.dependencies.clone() } else { mf.devDependencies.clone() }; + let mut hmap = if save { + mf.dependencies.clone() + } else { + mf.devDependencies.clone() + }; for c in &updated { - debug!("Successfully updated {} at version {}", &c.name, c.version); + debug!( + "Successfully updated {} on channel {} to version {}", + c.name, c.channel, c.version + ); + let new_coords = Coordinates::TwoD(TwoDCoordinates { + version: c.version, + channel: c.channel.to_string(), + }); if hmap.contains_key(&c.name) { let val = hmap.get_mut(&c.name).unwrap(); - if c.version < *val { - warn!("Downgrading {} from {} to {}", c.name, *val, c.version); - } else if c.version > *val { - info!("Upgrading {} from {} to {}", c.name, *val, c.version); + let (version, channel) = match val { + Coordinates::OneD(v) => (*v, None), + Coordinates::TwoD(v) => (v.version, Some(&v.channel)), + }; + // Only print information about changes in versions if the channel has + // remained constant. Otherwise the version numbers cannot be compared. + let channel = Channel::from_option(&channel); + if c.channel != channel { + info!("Changing from channel {} to channel {}", channel, c.channel) + } else if c.version < version { + warn!("Downgrading {} from {} to {}", c.name, version, c.version); + } else if c.version > version { + info!("Upgrading {} from {} to {}", c.name, version, c.version); } else { info!("Maintaining {} at version {}", c.name, c.version); } - *val = c.version; + *val = new_coords; } else { - hmap.insert(c.name.clone(), c.version); + hmap.insert(c.name.clone(), new_coords); } } if save { @@ -121,5 +193,5 @@ pub fn update_all( } else { manifest.dependencies.keys().cloned().collect() }; - update(manifest, backend, deps, save && !dev, save && dev, env) + update(manifest, backend, &deps, save && !dev, save && dev, env) } diff --git a/src/upgrade.rs b/src/upgrade.rs index 0a3874db..379a612d 100644 --- a/src/upgrade.rs +++ b/src/upgrade.rs @@ -10,12 +10,12 @@ use semver::Version; use std::env; -use std::path::{Path, PathBuf}; use std::fs; +use std::path::{Path, PathBuf}; use std::process::Command; -use super::{LalResult, CliError}; -use super::{http_download_to_path, get_latest_lal_version, LatestLal}; +use super::{get_latest_lal_version, http_download_to_path, LatestLal}; +use super::{CliError, LalResult}; struct ExeInfo { /// Whether ldd things its a dynamic executable @@ -39,24 +39,28 @@ fn identify_exe() -> LalResult { let pthstr: String = pth.to_str().unwrap().into(); let prefix = if pthstr.contains("/bin/") { let v: Vec<&str> = pthstr.split("/bin/").collect(); - if v.len() == 2 { Some(Path::new(v[0]).to_owned()) } else { None } + if v.len() == 2 { + Some(Path::new(v[0]).to_owned()) + } else { + None + } } else { None }; Ok(ExeInfo { - dynamic: is_dynamic, - debug: pthstr.contains("debug"), // cheap check for compiled versions - path: pthstr, - prefix: prefix, - version: Version::parse(env!("CARGO_PKG_VERSION")).unwrap(), - }) + dynamic: is_dynamic, + debug: pthstr.contains("debug"), // cheap check for compiled versions + path: pthstr, + prefix, + version: Version::parse(env!("CARGO_PKG_VERSION")).unwrap(), + }) } // basic tarball extractor // smaller than the INPUT extractor uses because it doesn't clear out anything fn extract_tarball(input: PathBuf, output: &PathBuf) -> LalResult<()> { - use tar::Archive; use flate2::read::GzDecoder; + use tar::Archive; let data = fs::File::open(input)?; let decompressed = GzDecoder::new(data)?; // decoder reads data @@ -116,8 +120,8 @@ fn upgrade_exe(latest: &LatestLal, exe: &ExeInfo) -> LalResult<()> { http_download_to_path(&latest.url, &tar_dest)?; info!("Backing up {} to {}", exe.path, old_file.display()); fs::rename(&exe.path, &old_file)?; // need to undo this if we fail - // NB: DO NOT INSERT CALLS THAT CAN FAIL HERE BEFORE THE OVERWRITE - // 3. force dump lal tarball into exe.prefix - rollback if it failed + // NB: DO NOT INSERT CALLS THAT CAN FAIL HERE BEFORE THE OVERWRITE + // 3. force dump lal tarball into exe.prefix - rollback if it failed info!("Unpacking new version of lal into {}", prefix.display()); match overwrite_exe(latest, exe) { // NB: This call takes a small amount of time - and can be aborted :/ @@ -135,7 +139,6 @@ fn upgrade_exe(latest: &LatestLal, exe: &ExeInfo) -> LalResult<()> { Ok(()) // we did it! } - /// Check for and possibly upgrade lal when using musl releases /// /// This will query for the latest version, and upgrade in the one possible case. @@ -149,21 +152,24 @@ pub fn upgrade(silent: bool) -> LalResult { // New version found - always full output now info!("A new version of lal is available: {}", latest.version); info!("You are running {} at {}", exe.version, exe.path); - println!(""); + println!(); if exe.dynamic { info!("Your version is built from source - please run (in source checkout):"); let build_flag = if exe.debug { "" } else { "--release" }; - info!("rustup update stable && git pull && cargo build {}", - build_flag); + info!( + "rustup update stable && git pull && cargo build {}", + build_flag + ); } else if exe.prefix.is_some() { // install lal in the prefix it's normally in info!("Upgrading..."); upgrade_exe(&latest, &exe)?; - info!("lal upgraded successfully to {} at {}", - latest.version, - exe.path); - println!(""); + info!( + "lal upgraded successfully to {} at {}", + latest.version, exe.path + ); + println!(); } else { // static, but no good guess of where to install - let user decide: info!("Your version is prebuilt but installed weirdly - please run:"); diff --git a/src/verify.rs b/src/verify.rs index 23a06a22..2bb35c9d 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -1,5 +1,20 @@ -use super::{Lockfile, Manifest, LalResult}; -use input; +use super::{LalResult, Lockfile, Manifest}; +use crate::input; + +bitflags! { + /// Flags to indicate variants on the verification process. + #[derive(Default)] + pub struct Flags: u8 { + /// A simple verify was added to aid the workflow of stashed components. + /// Users can use `lal verify --simple` or `lal build -s` aka. `--simple-verify`, + /// instead of having to use `lal build --force` when just using stashed components. + /// This avoids problems with different environments going undetected. + const SIMPLE = 0b01; + /// Indicates whether verification should allow testing channels, which are only to be + /// used by automated systems. + const TESTING = 0b10; + } +} /// Verifies that `./INPUT` satisfies all strictness conditions. /// @@ -13,14 +28,9 @@ use input; /// This function is meant to be a helper for when we want official builds, but also /// a way to tell developers that they are using things that differ from what jenkins /// would use. -/// -/// A simple verify was added to aid the workflow of stashed components. -/// Users can use `lal verify --simple` or `lal build -s` aka. `--simple-verify`, -/// instead of having to use `lal build --force` when just using stashed components. -/// This avoids problems with different environments going undetected. -pub fn verify(m: &Manifest, env: &str, simple: bool) -> LalResult<()> { +pub fn verify(m: &Manifest, env: &str, flags: Flags) -> LalResult<()> { // 1. Verify that the manifest is sane - m.verify()?; + m.verify(flags)?; // 2. dependencies in `INPUT` match `manifest.json`. if m.dependencies.is_empty() && !input::present() { @@ -31,9 +41,12 @@ pub fn verify(m: &Manifest, env: &str, simple: bool) -> LalResult<()> { input::verify_dependencies_present(m)?; // get data for big verify steps - let lf = Lockfile::default().populate_from_input()?; + let lf = Lockfile::default() + .with_channel(m.channel.clone()) + .populate_from_input()?; // 3. verify the root level dependencies match the manifest + let simple = flags.contains(Flags::SIMPLE); if !simple { input::verify_global_versions(&lf, m)?; } @@ -46,6 +59,11 @@ pub fn verify(m: &Manifest, env: &str, simple: bool) -> LalResult<()> { // 5. verify all components are built in the same environment input::verify_environment_consistency(&lf, env)?; + // 6. the channel hierarchy is valid + if !simple { + input::verify_global_channels(&lf)?; + } + info!("Dependencies fully verified"); Ok(()) } diff --git a/tests/chan-base/BUILD b/tests/chan-base/BUILD new file mode 100755 index 00000000..af785372 --- /dev/null +++ b/tests/chan-base/BUILD @@ -0,0 +1,2 @@ +#!/bin/bash +echo "pretend build" diff --git a/tests/chan-base/INPUT/chan-leaf/lockfile.json b/tests/chan-base/INPUT/chan-leaf/lockfile.json new file mode 100644 index 00000000..141ac7e6 --- /dev/null +++ b/tests/chan-base/INPUT/chan-leaf/lockfile.json @@ -0,0 +1,16 @@ +{ + "name": "chan-leaf", + "config": "release", + "container": { + "name": "clux/lal-alpine", + "tag": "3.6" + }, + "environment": "alpine", + "defaultEnv": "alpine", + "sha": null, + "version": "1", + "channel": "/leaf", + "tool": "3.8.1", + "built": "2019-03-15 13:36:49", + "dependencies": {} +} diff --git a/tests/chan-base/manifest.json b/tests/chan-base/manifest.json new file mode 100644 index 00000000..7484f683 --- /dev/null +++ b/tests/chan-base/manifest.json @@ -0,0 +1,20 @@ +{ + "name": "chan-base", + "channel": "/base", + "environment": "alpine", + "supportedEnvironments": [ + "alpine" + ], + "components": { + "chan-base": { + "defaultConfig": "release", + "configurations": [ + "release" + ] + } + }, + "dependencies": { + "chan-leaf": {"version":1, "channel": "/leaf"} + }, + "devDependencies": {} +} diff --git a/tests/chan-base2/BUILD b/tests/chan-base2/BUILD new file mode 100755 index 00000000..af785372 --- /dev/null +++ b/tests/chan-base2/BUILD @@ -0,0 +1,2 @@ +#!/bin/bash +echo "pretend build" diff --git a/tests/chan-base2/INPUT/chan-leaf/lockfile.json b/tests/chan-base2/INPUT/chan-leaf/lockfile.json new file mode 100644 index 00000000..141ac7e6 --- /dev/null +++ b/tests/chan-base2/INPUT/chan-leaf/lockfile.json @@ -0,0 +1,16 @@ +{ + "name": "chan-leaf", + "config": "release", + "container": { + "name": "clux/lal-alpine", + "tag": "3.6" + }, + "environment": "alpine", + "defaultEnv": "alpine", + "sha": null, + "version": "1", + "channel": "/leaf", + "tool": "3.8.1", + "built": "2019-03-15 13:36:49", + "dependencies": {} +} diff --git a/tests/chan-base2/manifest.json b/tests/chan-base2/manifest.json new file mode 100644 index 00000000..1e4cd5d6 --- /dev/null +++ b/tests/chan-base2/manifest.json @@ -0,0 +1,20 @@ +{ + "name": "chan-base2", + "channel": "/leaf", + "environment": "alpine", + "supportedEnvironments": [ + "alpine" + ], + "components": { + "chan-base2": { + "defaultConfig": "release", + "configurations": [ + "release" + ] + } + }, + "dependencies": { + "chan-leaf": {"version":1, "channel": "/leaf"} + }, + "devDependencies": {} +} diff --git a/tests/chan-leaf/BUILD b/tests/chan-leaf/BUILD new file mode 100755 index 00000000..af785372 --- /dev/null +++ b/tests/chan-leaf/BUILD @@ -0,0 +1,2 @@ +#!/bin/bash +echo "pretend build" diff --git a/tests/chan-leaf/manifest.json b/tests/chan-leaf/manifest.json new file mode 100644 index 00000000..b4b28f95 --- /dev/null +++ b/tests/chan-leaf/manifest.json @@ -0,0 +1,18 @@ +{ + "name": "chan-leaf", + "channel": "/leaf", + "environment": "alpine", + "supportedEnvironments": [ + "alpine" + ], + "components": { + "chan-leaf": { + "defaultConfig": "release", + "configurations": [ + "release" + ] + } + }, + "dependencies": {}, + "devDependencies": {} +} diff --git a/tests/chan-test/BUILD b/tests/chan-test/BUILD new file mode 100755 index 00000000..af785372 --- /dev/null +++ b/tests/chan-test/BUILD @@ -0,0 +1,2 @@ +#!/bin/bash +echo "pretend build" diff --git a/tests/chan-test/manifest.json b/tests/chan-test/manifest.json new file mode 100644 index 00000000..7d93f7ca --- /dev/null +++ b/tests/chan-test/manifest.json @@ -0,0 +1,18 @@ +{ + "name": "chan-test", + "channel": "/testing", + "environment": "alpine", + "supportedEnvironments": [ + "alpine" + ], + "components": { + "chan-test": { + "defaultConfig": "release", + "configurations": [ + "release" + ] + } + }, + "dependencies": {}, + "devDependencies": {} +} diff --git a/tests/helloworld/manifest.json b/tests/helloworld/manifest.json index b095d58e..f0c51ac3 100644 --- a/tests/helloworld/manifest.json +++ b/tests/helloworld/manifest.json @@ -1,5 +1,6 @@ { "name": "hello", + "channel": null, "environment": "alpine", "supportedEnvironments": [ "alpine" @@ -13,7 +14,10 @@ } }, "dependencies": { - "heylib": 1 + "heylib": { + "channel": "/", + "version": 1 + } }, "devDependencies": {} } diff --git a/tests/testmain.rs b/tests/testmain.rs index cf88d730..de342160 100644 --- a/tests/testmain.rs +++ b/tests/testmain.rs @@ -1,15 +1,11 @@ -extern crate lal; - #[macro_use] extern crate log; -extern crate loggerv; -extern crate walkdir; use std::env; -use std::path::Path; use std::fs::{self, File}; -use std::process::Command; use std::io::prelude::*; +use std::path::Path; +use std::process::Command; use walkdir::WalkDir; use loggerv::init_with_verbosity; @@ -19,28 +15,20 @@ use lal::*; mod chk { use std::fmt::Display; - use std::process; // TODO: don't need to move T into here, but since they are joined.. pub fn is_ok(x: Result, name: &str) { let _ = x.map_err(|e| { - println!("Bail out! {} failed with '{}'", name, e); - process::exit(1); + panic!(format!("Bail out! {} failed with '{}'", name, e)); }); } } -// fn assert_err(x: LalResult, name: &str) { -// let _ = x.map(|v| { -// println!("Bail out! {} unexpected ok: {}", name, v); -// process::exit(1); -// }); -// fn init_ssl() { use std::env; env::set_var("SSL_CERT_FILE", "/etc/ssl/certs/ca-certificates.crt"); } -fn main() { +pub fn main() { init_ssl(); // print debug output and greater from lal during tests init_with_verbosity(2).unwrap(); @@ -67,7 +55,6 @@ fn main() { let testdir = fs::canonicalize(Path::new("..").join("tests")).unwrap(); - // Test basic build functionality with heylib component let heylibdir = testdir.join("heylib"); assert!(env::set_current_dir(heylibdir).is_ok()); @@ -162,6 +149,33 @@ fn main() { check_propagation("prop-leaf"); info!("ok check_propagation prop-leaf -> prop-base"); + + check_multiple_propagation(); + info!("ok check_propagation prop-mid-1 + prop-mid-2 -> prop-base"); + + // Verify channel dependency + let chanleaf = testdir.join("chan-leaf"); + assert!(env::set_current_dir(&chanleaf).is_ok()); + fetch_release_build_and_publish(&backend); + info!("ok fetch_release_build_and_publish chan-leaf"); + + let chanbase1 = testdir.join("chan-base"); + assert!(env::set_current_dir(&chanbase1).is_ok()); + verify_channel_mismatch(&backend); + info!("ok verify_channel_mismatch chan-base"); + + let chanbase2 = testdir.join("chan-base2"); + assert!(env::set_current_dir(&chanbase2).is_ok()); + fetch_release_build_and_publish(&backend); + info!("ok fetch_release_build_and_publish chan-base2"); + + // Verify testing channel verification + let chanleaf = testdir.join("chan-test"); + assert!(env::set_current_dir(&chanleaf).is_ok()); + verify_testing_channel_failure(&backend); + info!("ok verify_channel_mismatch chan-test"); + fetch_release_test_build_and_publish(&backend); + info!("ok fetch_release_build_and_publish chan-test"); } fn kill_laldir() { @@ -265,11 +279,9 @@ fn configure_yes() -> LocalBackend { let cfgu = cfg.unwrap(); - match &cfgu.backend { - &BackendConfiguration::Local(ref local_cfg) => { - LocalBackend::new(&local_cfg, &cfgu.cache) - } - _ => unreachable!() // demo.json uses local backend + match cfgu.backend { + BackendConfiguration::Local(ref local_cfg) => LocalBackend::new(&local_cfg, &cfgu.cache), + _ => unreachable!(), // demo.json uses local backend } } @@ -307,7 +319,7 @@ fn has_config_and_manifest() { chk::is_ok(Manifest::read(), "could read manifest"); // There is no INPUT yet, but we have no dependencies, so this should work: - let r = lal::verify(&manifest.unwrap(), "xenial".into(), false); + let r = lal::verify::verify(&manifest.unwrap(), "xenial", lal::verify::Flags::default()); chk::is_ok(r, "could verify after install"); } @@ -316,22 +328,26 @@ fn shell_echo() { let cfg = Config::read().unwrap(); let container = cfg.get_container("alpine".into()).unwrap(); let modes = ShellModes::default(); - let r = lal::docker_run(&cfg, - &container, - vec!["echo".to_string(), "# echo from docker".to_string()], - &DockerRunFlags::default(), - &modes); + let r = lal::docker_run( + &cfg, + &container, + vec!["echo".to_string(), "# echo from docker".to_string()], + &DockerRunFlags::default(), + &modes, + ); assert!(r.is_ok(), "shell echoed"); } fn shell_permissions() { let cfg = Config::read().unwrap(); let container = cfg.get_container("alpine".into()).unwrap(); let modes = ShellModes::default(); - let r = lal::docker_run(&cfg, - &container, - vec!["touch".to_string(), "README.md".to_string()], - &DockerRunFlags::default(), - &modes); + let r = lal::docker_run( + &cfg, + &container, + vec!["touch".to_string(), "README.md".to_string()], + &DockerRunFlags::default(), + &modes, + ); assert!(r.is_ok(), "could touch files in container"); } @@ -344,19 +360,19 @@ fn build_and_stash_update_self(backend: &T) { let mut bopts = BuildOptions { name: Some("heylib".into()), configuration: Some("release".into()), - container: container, + container, release: true, version: None, sha: None, force: false, - simple_verify: false, + verify_flags: lal::verify::Flags::default(), }; let modes = ShellModes::default(); // basic build works - all deps are global at right env - let r = lal::build(&cfg, &mf, &bopts, "alpine".into(), modes.clone()); + let r = lal::build(&cfg, &mf, &bopts, "alpine", modes.clone()); if let Err(e) = r { println!("error from build: {:?}", e); - assert!(false, "could perform an alpine build"); + unreachable!("could perform an alpine build"); } // lal stash blah @@ -364,44 +380,47 @@ fn build_and_stash_update_self(backend: &T) { assert!(rs.is_ok(), "could stash lal build artifact"); // lal update heylib=blah - let ru = lal::update(&mf, - backend, - vec!["heylib=blah".to_string()], - false, - false, - "garbage"); // env not relevant for stash + let ru = lal::update( + &mf, + backend, + &["heylib=blah".to_string()], + false, + false, + "garbage", + ); // env not relevant for stash chk::is_ok(ru, "could update heylib from stash"); // basic build won't work now without simple verify - let r1 = lal::build(&cfg, &mf, &bopts, "alpine".into(), modes.clone()); + let r1 = lal::build(&cfg, &mf, &bopts, "alpine", modes.clone()); assert!(r1.is_err(), "could not verify a new alpine build"); if let Err(CliError::NonGlobalDependencies(nonglob)) = r1 { assert_eq!(nonglob, "heylib"); } else { println!("actual r1 was {:?}", r1); - assert!(false); + unreachable!(); } - bopts.simple_verify = true; - let r2 = lal::build(&cfg, &mf, &bopts, "alpine".into(), modes.clone()); + bopts.verify_flags = lal::verify::Flags::SIMPLE; + let r2 = lal::build(&cfg, &mf, &bopts, "alpine", modes.clone()); assert!(r2.is_ok(), "can build with stashed deps with simple verify"); - // force will also work - even with stashed deps from wrong env - let renv = lal::build(&cfg, &mf, &bopts, "xenial".into(), modes.clone()); - assert!(renv.is_err(), - "cannot build with simple verify when wrong env"); + let renv = lal::build(&cfg, &mf, &bopts, "xenial", modes.clone()); + assert!( + renv.is_err(), + "cannot build with simple verify when wrong env" + ); if let Err(CliError::EnvironmentMismatch(_, compenv)) = renv { assert_eq!(compenv, "alpine"); // expected complaints about xenial env } else { println!("actual renv was {:?}", renv); - assert!(false); + unreachable!(); } // settings that reflect lal build -f - bopts.simple_verify = false; + bopts.verify_flags = lal::verify::Flags::default(); bopts.force = true; - let renv2 = lal::build(&cfg, &mf, &bopts, "xenial".into(), modes.clone()); + let renv2 = lal::build(&cfg, &mf, &bopts, "xenial", modes.clone()); assert!(renv2.is_ok(), "could force build in different env"); // additionally do a build with printonly @@ -410,13 +429,13 @@ fn build_and_stash_update_self(backend: &T) { x11_forwarding: true, host_networking: true, env_vars: vec![], + capabilities: vec![], }; - let printbuild = lal::build(&cfg, &mf, &bopts, "alpine".into(), all_modes); + let printbuild = lal::build(&cfg, &mf, &bopts, "alpine", all_modes); // TODO: verify output! assert!(printbuild.is_ok(), "saw docker run print with X11 mounts"); } - fn fetch_release_build_and_publish(backend: &T) { let mf = Manifest::read().unwrap(); let cfg = Config::read().unwrap(); @@ -429,21 +448,79 @@ fn fetch_release_build_and_publish(backend: &T) { let bopts = BuildOptions { name: None, configuration: Some("release".into()), - container: container, + container, + release: true, + version: Some("1".into()), // want to publish version 1 for later + sha: None, + force: false, + verify_flags: lal::verify::Flags::default(), + }; + let modes = ShellModes::default(); + let r = lal::build(&cfg, &mf, &bopts, "alpine", modes.clone()); + assert!(r.is_ok(), "could build in release"); + + let rp = lal::publish(&mf.name, backend); + assert!(rp.is_ok(), "could publish"); +} + +fn fetch_release_test_build_and_publish(backend: &T) { + let mf = Manifest::read().unwrap(); + let cfg = Config::read().unwrap(); + let container = cfg.get_container("alpine".into()).unwrap(); + + let rcore = lal::fetch(&mf, backend, true, "alpine"); + assert!(rcore.is_ok(), "install core succeeded"); + + // we'll try with various build options further down with various deps + let bopts = BuildOptions { + name: None, + configuration: Some("release".into()), + container, release: true, version: Some("1".into()), // want to publish version 1 for later sha: None, force: false, - simple_verify: false, + verify_flags: lal::verify::Flags::TESTING, }; let modes = ShellModes::default(); - let r = lal::build(&cfg, &mf, &bopts, "alpine".into(), modes.clone()); + let r = lal::build(&cfg, &mf, &bopts, "alpine", modes.clone()); assert!(r.is_ok(), "could build in release"); let rp = lal::publish(&mf.name, backend); assert!(rp.is_ok(), "could publish"); } +fn verify_channel_mismatch(backend: &T) { + let mf = Manifest::read().unwrap(); + + let rcore = lal::fetch(&mf, backend, true, "alpine"); + assert!(rcore.is_err(), "expected channel mismtach"); +} + +fn verify_testing_channel_failure(backend: &T) { + let mf = Manifest::read().unwrap(); + let cfg = Config::read().unwrap(); + let container = cfg.get_container("alpine".into()).unwrap(); + + let rcore = lal::fetch(&mf, backend, true, "alpine"); + assert!(rcore.is_ok(), "install core succeeded"); + + // we'll try with various build options further down with various deps + let bopts = BuildOptions { + name: None, + configuration: Some("release".into()), + container, + release: true, + version: Some("1".into()), // want to publish version 1 for later + sha: None, + force: false, + verify_flags: lal::verify::Flags::default(), + }; + let modes = ShellModes::default(); + let r = lal::build(&cfg, &mf, &bopts, "alpine", modes.clone()); + assert!(r.is_err(), "expected channel mismatch"); +} + fn no_publish_non_release_builds(backend: &T) { let mf = Manifest::read().unwrap(); let cfg = Config::read().unwrap(); @@ -458,15 +535,15 @@ fn no_publish_non_release_builds(backend: &T) { let mut bopts = BuildOptions { name: None, configuration: Some("release".into()), - container: container, - release: false, // missing releaes bad + container, + release: false, // missing releaes bad version: Some("2".into()), // but have version sha: None, force: false, - simple_verify: false, + verify_flags: lal::verify::Flags::default(), }; let modes = ShellModes::default(); - let r = lal::build(&cfg, &mf, &bopts, "alpine".into(), modes.clone()); + let r = lal::build(&cfg, &mf, &bopts, "alpine", modes.clone()); assert!(r.is_ok(), "could build without non-release"); let rp = lal::publish(&mf.name, backend); @@ -475,12 +552,11 @@ fn no_publish_non_release_builds(backend: &T) { bopts.version = None; // missing version bad bopts.release = true; // but at least in release mode now - let rb2 = lal::build(&cfg, &mf, &bopts, "alpine".into(), modes.clone()); + let rb2 = lal::build(&cfg, &mf, &bopts, "alpine", modes.clone()); assert!(rb2.is_ok(), "could build in without version"); let rp2 = lal::publish(&mf.name, backend); assert!(rp2.is_err(), "could not publish without version set"); - } // add dependencies to test tree // NB: this currently shouldn't do anything as all deps are accounted for @@ -489,12 +565,14 @@ fn update_save(backend: &T) { let mf1 = Manifest::read().unwrap(); // update heylib --save - let ri = lal::update(&mf1, - backend, - vec!["heylib".to_string()], - true, - false, - "alpine"); + let ri = lal::update( + &mf1, + backend, + &["heylib".to_string()], + true, + false, + "alpine", + ); chk::is_ok(ri, "could update heylib and save"); // main deps (and re-read manifest to avoid overwriting devedps) @@ -503,7 +581,7 @@ fn update_save(backend: &T) { "heylib".to_string(), // TODO: more deps ]; - let ri = lal::update(&mf2, backend, updates, true, false, "alpine"); + let ri = lal::update(&mf2, backend, &updates, true, false, "alpine"); chk::is_ok(ri, "could update and save"); // verify update-all --save @@ -523,20 +601,24 @@ fn verify_checks(backend: &T) { let rcore = lal::fetch(&mf, backend, true, "alpine"); assert!(rcore.is_ok(), "install core succeeded"); - let r = lal::verify(&mf, "alpine".into(), false); + let r = lal::verify::verify(&mf, "alpine", lal::verify::Flags::default()); assert!(r.is_ok(), "could verify after install"); - let renv1 = lal::verify(&mf, "xenial".into(), false); + let renv1 = lal::verify::verify(&mf, "xenial", lal::verify::Flags::default()); assert!(renv1.is_err(), "could not verify with wrong env"); - let renv2 = lal::verify(&mf, "xenial".into(), true); - assert!(renv2.is_err(), - "could not verify with wrong env - even with simple"); - - let heylib = Path::new(&env::current_dir().unwrap()).join("INPUT").join("heylib"); + let renv2 = lal::verify::verify(&mf, "xenial", lal::verify::Flags::SIMPLE); + assert!( + renv2.is_err(), + "could not verify with wrong env - even with simple" + ); + + let heylib = Path::new(&env::current_dir().unwrap()) + .join("INPUT") + .join("heylib"); // clean folders and verify it fails fs::remove_dir_all(&heylib).unwrap(); - let r2 = lal::verify(&mf, "alpine".into(), false); + let r2 = lal::verify::verify(&mf, "alpine", lal::verify::Flags::default()); assert!(r2.is_err(), "verify failed after fiddling"); // fetch --core, resyncs with core deps (removes devDeps and other extraneous) @@ -557,52 +639,109 @@ fn verify_checks(backend: &T) { assert!(rall.is_ok(), "install all succeeded"); //assert!(gtest.is_dir(), "gtest is otherwise installed again"); - let r3 = lal::verify(&mf, "alpine", false); + let r3 = lal::verify::verify(&mf, "alpine", lal::verify::Flags::default()); assert!(r3.is_ok(), "verify ok again"); } fn run_scripts() { { - Command::new("mkdir").arg("-p").arg(".lal/scripts").output().unwrap(); + Command::new("mkdir") + .arg("-p") + .arg(".lal/scripts") + .output() + .unwrap(); let mut f = File::create("./.lal/scripts/subroutine").unwrap(); - write!(f, "main() {{ echo hi $1 $2 ;}}\n").unwrap(); - Command::new("chmod").arg("+x").arg(".lal/scripts/subroutine").output().unwrap(); + writeln!(f, "main() {{ echo hi $1 $2 ;}}").unwrap(); + Command::new("chmod") + .arg("+x") + .arg(".lal/scripts/subroutine") + .output() + .unwrap(); } let cfg = Config::read().unwrap(); let container = cfg.get_container("alpine".into()).unwrap(); let modes = ShellModes::default(); - let r = lal::script(&cfg, - &container, - "subroutine", - vec!["there", "mr"], - &modes, - false); + let r = lal::script( + &cfg, + &container, + "subroutine", + &["there", "mr"], + &modes, + false, + ); assert!(r.is_ok(), "could run subroutine script"); } +fn assert_dep(dependencies: &[lal::propagate::Repo], expected: &[&str]) { + for (dep, exp) in dependencies.iter().zip(expected) { + assert_repo(dep, exp); + } +} + +fn assert_repo(repo: &lal::propagate::Repo, expected: &str) { + assert_eq!(repo.to_string(), expected); +} + fn check_propagation(leaf: &str) { let mf = Manifest::read().unwrap(); + let leaf = vec![leaf.to_owned()]; - let lf = Lockfile::default().set_name(&mf.name).populate_from_input().unwrap(); - if let Ok(res) = lal::propagate::compute(&lf, leaf) { + let lf = Lockfile::default() + .set_name(&mf.name) + .populate_from_input() + .unwrap(); + if let Ok(res) = lal::propagate::compute(&lf, &leaf) { assert_eq!(res.stages.len(), 2); // first stage assert_eq!(res.stages[0].updates.len(), 2); // must update both mid points - assert_eq!(res.stages[0].updates[0].dependencies, vec!["prop-leaf"]); - assert_eq!(res.stages[0].updates[1].dependencies, vec!["prop-leaf"]); - assert_eq!(res.stages[0].updates[0].repo, "prop-mid-1"); - assert_eq!(res.stages[0].updates[1].repo, "prop-mid-2"); + assert_dep(&res.stages[0].updates[0].dependencies, &["prop-leaf"]); + assert_dep(&res.stages[0].updates[1].dependencies, &["prop-leaf"]); + assert_repo(&res.stages[0].updates[1].repo, "prop-mid-2"); // second stage assert_eq!(res.stages[1].updates.len(), 1); // must update base - assert_eq!(res.stages[1].updates[0].dependencies, vec!["prop-mid-1", "prop-mid-2"]); - assert_eq!(res.stages[1].updates[0].repo, "prop-base"); + assert_dep( + &res.stages[1].updates[0].dependencies, + &["prop-mid-1", "prop-mid-2"], + ); + assert_repo(&res.stages[1].updates[0].repo, "prop-base"); } else { - assert!(false, "could propagate leaf to {}", mf.name); + unreachable!("could propagate leaf to {}", mf.name); } - let rpj = lal::propagate::print(&mf, leaf, true); + let rpj = lal::propagate::print(&mf, &leaf, true); assert!(rpj.is_ok(), "could print propagate json to stdout"); - let rp = lal::propagate::print(&mf, leaf, false); + let rp = lal::propagate::print(&mf, &leaf, false); + assert!(rp.is_ok(), "could print propagate to stdout"); + + // print tree for extra coverage of bigger trees + let rs = lal::status(&mf, true, true, true); + assert!(rs.is_ok(), "could print status of propagation root"); +} + +fn check_multiple_propagation() { + let mf = Manifest::read().unwrap(); + let leaf = vec!["prop-mid-1".to_owned(), "prop-mid-2".to_owned()]; + + let lf = Lockfile::default() + .set_name(&mf.name) + .populate_from_input() + .unwrap(); + if let Ok(res) = lal::propagate::compute(&lf, &leaf) { + assert_eq!(res.stages.len(), 1); + // first stage + assert_eq!(res.stages[0].updates.len(), 1); // must update both mid points + assert_dep( + &res.stages[0].updates[0].dependencies, + &["prop-mid-1", "prop-mid-2"], + ); + assert_repo(&res.stages[0].updates[0].repo, "prop-base"); + } else { + unreachable!("could propagate leaf to {}", mf.name); + } + + let rpj = lal::propagate::print(&mf, &leaf, true); + assert!(rpj.is_ok(), "could print propagate json to stdout"); + let rp = lal::propagate::print(&mf, &leaf, false); assert!(rp.is_ok(), "could print propagate to stdout"); // print tree for extra coverage of bigger trees @@ -619,14 +758,6 @@ fn status_on_experimentals() { assert!(r.is_err(), "status should complain at experimental deps"); } -#[cfg(feature = "upgrade")] -fn upgrade_does_not_fail() { - let uc = lal::upgrade(true); - assert!(uc.is_ok(), "could perform upgrade check"); - let upgraded = uc.unwrap(); - assert!(!upgraded, "we never have upgrades in the tip source tree"); -} - fn clean_check() { let cfg = Config::read().unwrap(); let r = lal::clean(&cfg.cache, 1); @@ -637,7 +768,7 @@ fn clean_check() { .min_depth(3) .max_depth(3) .into_iter() - .filter_map(|e| e.ok()) + .filter_map(Result::ok) .filter(|e| e.path().is_dir()); let first = dirs.next(); @@ -652,7 +783,7 @@ fn clean_check() { .min_depth(3) .max_depth(3) .into_iter() - .filter_map(|e| e.ok()) + .filter_map(Result::ok) .filter(|e| e.path().is_dir()); let first2 = dirs2.next(); @@ -680,10 +811,9 @@ fn export_check(backend: &T) { } fn query_check(backend: &T) { - let r = lal::query(backend, Some("alpine"), "hello", false); + let r = lal::query(backend, Some("alpine"), None, "hello", false); assert!(r.is_ok(), "could query for hello"); - let rl = lal::query(backend, Some("alpine"), "hello", true); + let rl = lal::query(backend, Some("alpine"), None, "hello", true); assert!(rl.is_ok(), "could query latest for hello"); - }