diff --git a/.github/workflows/autorebase.yml b/.github/workflows/autorebase.yml new file mode 100644 index 000000000..5ba890638 --- /dev/null +++ b/.github/workflows/autorebase.yml @@ -0,0 +1,32 @@ +on: + # Run on every push on every branch + push: + branches-ignore: + # Ignore branches automatically created by github-rebase + - rebase-pull-request** + - cherry-pick-rebase-pull-request** + # Run when pull requests get labeled + pull_request: + types: [labeled] + +jobs: + auto-rebase: + name: AutoRebase + runs-on: ubuntu-latest + steps: + # We can't use the built-in secrets.GITHUB_TOKEN yet because of this limitation: + # https://github.community/t5/GitHub-Actions/Triggering-a-new-workflow-from-another-workflow/td-p/31676 + # In the meantime, use a token granting write access on the repo: + # - a GitHub App token + # See https://github.com/marketplace/actions/github-app-token. + - name: GitHub App token + id: token-generator + uses: tibdex/github-app-token@v1.0.2 + with: + app_id: ${{ secrets.TOKEN_GEN_APP_ID }} + private_key: ${{ secrets.TOKEN_GEN_PRIVATE_KEY }} + + - name: Auto Rebase + uses: Label305/AutoRebase@v0.1 + with: + github_token: ${{ steps.token-generator.outputs.token }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 64518abd4..000000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: ci -on: [push, pull_request] -jobs: - build: - name: ${{ matrix.kind }} ${{ matrix.os }} - runs-on: ${{ matrix.os }} - timeout-minutes: 60 - strategy: - matrix: - os: [macOS-latest, windows-2019, ubuntu-16.04] - kind: ['test_debug'] - steps: - - name: Clone repository - uses: actions/checkout@v1 - with: - # Use depth > 1, because sometimes we need to rebuild master and if - # other commits have landed it will become impossible to rebuild if - # the checkout is too shallow. - fetch-depth: 5 - submodules: true - - - name: Install rust - uses: hecrj/setup-rust-action@v1 - with: - rust-version: "1.41.0" - - - name: Install clippy and rustfmt - run: | - rustup component add clippy - rustup component add rustfmt - - - name: check formatting - run: cargo fmt -- --check - - - name: build and test - run: cargo test --locked --all-targets - - # TODO - # - name: clippy - # run: cargo clippy --locked --all-target diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 000000000..fe36415fb --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,94 @@ +# .gitlab-ci.yml +# +# sccache + + +stages: + - check + - test + - deploy + +variables: + GIT_STRATEGY: fetch + GIT_DEPTH: 100 + CARGO_INCREMENTAL: 0 + # this var is changed to "-:staging" when the CI image gets rebuilt + # read more https://github.com/paritytech/scripts/pull/244 + CI_IMAGE: "paritytech/sccache-ci-ubuntu:staging" # temporary override + +workflow: + rules: + - if: $CI_COMMIT_TAG + - if: $CI_COMMIT_BRANCH + +.docker-env: &docker-env + image: "${CI_IMAGE}" + before_script: + - which gcc && gcc --version + - which clang && clang --version + - rustc +stable --version + - rustc +nightly --version + - sccache -s + retry: + max: 2 + when: + - runner_system_failure + - unknown_failure + - api_failure + interruptible: true + tags: + - linux-docker + rules: + - if: $CI_PIPELINE_SOURCE == "web" + - if: $CI_PIPELINE_SOURCE == "schedule" + - if: $CI_COMMIT_REF_NAME == "master" + - if: $CI_COMMIT_REF_NAME == "tags" + - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs + - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 + +.collect-artifacts: &collect-artifacts + artifacts: + name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}" + when: on_success + expire_in: 7 days + paths: + - artifacts/ + +#### stage: check + +fmt: + <<: *docker-env + stage: check + script: + - cargo fmt -- --check + allow_failure: true + +clippy: + <<: *docker-env + stage: check + script: + - cargo clippy --all-targets + allow_failure: true + +#### stage: test + +nightly-test: + <<: *docker-env + stage: test + variables: + EXTRA_FEATURES: "unstable" + script: + - cargo +nightly build --verbose --features="${EXTRA_FEATURES}" || exit 1 + - RUST_BACKTRACE=1 cargo +nightly test --workspace --verbose --features="${EXTRA_FEATURES}" + +stable-test: + stage: test + <<: *docker-env + <<: *collect-artifacts + script: + - cargo +stable build --verbose + - RUST_BACKTRACE=1 cargo +stable test --workspace --verbose + - cargo +stable build --release --features="dist-client,dist-server" + # collect artifacts + - mkdir -p ./artifacts/sccache/ + - mv ./target/release/sccache ./artifacts/sccache/. diff --git a/Cargo.lock b/Cargo.lock index dbfefc162..ff1181812 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,6 +15,60 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" +[[package]] +name = "aead" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc95d1bdb8e6666b2b217308eeeb09f2d6728d104be3e31916cc74d15420331" +dependencies = [ + "generic-array 0.14.4", +] + +[[package]] +name = "aes" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7001367fde4c768a19d1029f0a8be5abd9308e1119846d5bd9ad26297b8faf5" +dependencies = [ + "aes-soft", + "aesni", + "block-cipher", +] + +[[package]] +name = "aes-gcm" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f5007801316299f922a6198d1d09a0bae95786815d066d5880d13f7c45ead1" +dependencies = [ + "aead", + "aes", + "block-cipher", + "ghash", + "subtle 2.3.0", +] + +[[package]] +name = "aes-soft" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4925647ee64e5056cf231608957ce7c81e12d6d6e316b9ce1404778cc1d35fa7" +dependencies = [ + "block-cipher", + "byteorder", + "opaque-debug 0.2.3", +] + +[[package]] +name = "aesni" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050d39b0b7688b3a3254394c3e30a9d66c41dcf9b05b0e2dbdc623f6505d264" +dependencies = [ + "block-cipher", + "opaque-debug 0.2.3", +] + [[package]] name = "aho-corasick" version = "0.7.10" @@ -88,6 +142,23 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "assert_matches" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" + +[[package]] +name = "async-trait" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b246867b8b3b6ae56035f1eb1ed557c1d8eae97f0d53696138a50fa0e3a3b8c0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atty" version = "0.2.14" @@ -118,12 +189,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0df2f85c8a2abbe3b7d7e748052fdd9b76a0458fdeb16ad4223f5eca78c7c130" dependencies = [ "addr2line", - "cfg-if", + "cfg-if 0.1.10", "libc", "object", "rustc-demangle", ] +[[package]] +name = "base-x" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" + [[package]] name = "base64" version = "0.9.3" @@ -150,15 +227,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" [[package]] -name = "bincode" -version = "0.8.0" +name = "base64" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e103c8b299b28a9c6990458b7013dc4a8356a9b854c51b9883241f5866fac36e" -dependencies = [ - "byteorder", - "num-traits 0.1.43", - "serde", -] +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "bincode" @@ -170,6 +248,12 @@ dependencies = [ "serde", ] +[[package]] +name = "bit-vec" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0dc55f2d8a1a85650ac47858bb001b4c0dd73d79e3c455a842925e68d29cd3" + [[package]] name = "bitflags" version = "1.2.1" @@ -196,33 +280,37 @@ dependencies = [ "arrayref", "arrayvec", "cc", - "cfg-if", + "cfg-if 0.1.10", "constant_time_eq", - "crypto-mac", - "digest", + "crypto-mac 0.7.0", + "digest 0.8.1", ] [[package]] name = "block-buffer" -version = "0.7.3" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ "block-padding", - "byte-tools", - "byteorder", - "generic-array", + "generic-array 0.14.4", ] [[package]] -name = "block-padding" -version = "0.1.5" +name = "block-cipher" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +checksum = "fa136449e765dc7faa244561ccae839c394048667929af599b5d931ebe7b7f10" dependencies = [ - "byte-tools", + "generic-array 0.14.4", ] +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + [[package]] name = "boxfnonce" version = "0.1.1" @@ -246,10 +334,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8" [[package]] -name = "byte-tools" -version = "0.3.1" +name = "bumpalo" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" +checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" [[package]] name = "byteorder" @@ -274,17 +362,11 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" -[[package]] -name = "case" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88b166b48e29667f5443df64df3c61dc07dc2b1a0b0d231800e07f09a33ecc1" - [[package]] name = "cc" -version = "1.0.54" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311" +checksum = "ad9c6140b5a2c7db40ea56eb1821245e5362b44385c05b76288b1a599934ac87" dependencies = [ "jobserver", ] @@ -295,6 +377,12 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "chrono" version = "0.4.11" @@ -302,8 +390,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" dependencies = [ "num-integer", - "num-traits 0.2.11", - "time", + "num-traits", + "serde", + "time 0.1.43", ] [[package]] @@ -356,9 +445,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d6364d028778d0d98b6014fa5882da377cd10d3492b7734d266a428e9b1fca" dependencies = [ "log 0.4.8", - "md5", + "md5 0.3.8", ] +[[package]] +name = "const_fn" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c478836e029dcef17fb47c89023448c64f781a046e0300e257ad8225ae59afab" + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -371,7 +466,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "888604f00b3db336d2af898ec3c1d5d0ddf5e6d462220f2ededc33a87ac4bbd5" dependencies = [ - "time", + "time 0.1.43", "url 1.7.2", ] @@ -388,7 +483,7 @@ dependencies = [ "publicsuffix", "serde", "serde_json", - "time", + "time 0.1.43", "try_from", "url 1.7.2", ] @@ -415,13 +510,19 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384f8c53175c890920b6e0127b730709d2a173ca6c4dfdc81618ac9b46f648fe" +[[package]] +name = "cpuid-bool" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" + [[package]] name = "crc32fast" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", ] [[package]] @@ -442,7 +543,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" dependencies = [ "autocfg 1.0.0", - "cfg-if", + "cfg-if 0.1.10", "crossbeam-utils 0.7.2", "lazy_static", "maybe-uninit", @@ -465,24 +566,18 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "crossbeam-utils 0.7.2", "maybe-uninit", ] -[[package]] -name = "crossbeam-utils" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d453a17e8bd2b913fa38e8b9cf04bcdbb5be790aa294f2389661d72036015" - [[package]] name = "crossbeam-utils" version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "lazy_static", ] @@ -493,7 +588,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" dependencies = [ "autocfg 1.0.0", - "cfg-if", + "cfg-if 0.1.10", + "lazy_static", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec91540d98355f690a86367e566ecad2e9e579f230230eb7c21398372be73ea5" +dependencies = [ + "autocfg 1.0.0", + "cfg-if 1.0.0", + "const_fn", "lazy_static", ] @@ -503,29 +610,56 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" dependencies = [ - "generic-array", - "subtle", + "generic-array 0.12.3", + "subtle 1.0.0", ] [[package]] -name = "daemonize" -version = "0.4.1" +name = "crypto-mac" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70c24513e34f53b640819f0ac9f705b673fcf4006d7aab8778bee72ebfc89815" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" dependencies = [ - "boxfnonce", - "libc", + "generic-array 0.14.4", + "subtle 2.3.0", ] [[package]] -name = "derive-error" -version = "0.0.3" +name = "crypto-mac" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4857fd85a0c34b3c3297875b747c1e02e06b6a0ea32dd892d8192b9ce0813ea6" +dependencies = [ + "generic-array 0.14.4", + "subtle 2.3.0", +] + +[[package]] +name = "ct-logs" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3686f5fa27dbc1d76c751300376e167c5a43387f44bb451fd1c24776e49113" +dependencies = [ + "sct", +] + +[[package]] +name = "ct-logs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c8e13110a84b6315df212c045be706af261fd364791cad863285439ebba672e" +dependencies = [ + "sct", +] + +[[package]] +name = "daemonize" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "629f1bb3abce791912ca85a24676fff54464f7deb122906adabc90fb96e876d3" +checksum = "70c24513e34f53b640819f0ac9f705b673fcf4006d7aab8778bee72ebfc89815" dependencies = [ - "case", - "quote 0.3.15", - "syn 0.11.11", + "boxfnonce", + "libc", ] [[package]] @@ -540,16 +674,24 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" dependencies = [ - "generic-array", + "generic-array 0.12.3", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array 0.14.4", ] [[package]] name = "directories" -version = "2.0.2" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "551a778172a450d7fc12e629ca3b0428d00f6afa9a43da1b630d54604e97371c" +checksum = "f8fed639d60b58d0f53498ab13d26f621fd77569cc6edb031f4cc36a2ad9da0f" dependencies = [ - "cfg-if", "dirs-sys", ] @@ -564,6 +706,16 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "dirs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" +dependencies = [ + "cfg-if 0.1.10", + "dirs-sys", +] + [[package]] name = "dirs-sys" version = "0.3.5" @@ -575,6 +727,12 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "discard" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" + [[package]] name = "doc-comment" version = "0.3.3" @@ -599,14 +757,14 @@ version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8ac63f94732332f44fe654443c46f6375d1939684c17b0afb6cb56b0456e171" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", ] [[package]] name = "env_logger" -version = "0.5.13" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15b0a4d2e39f8420210be8b27eeda28029729e2fd4291019455016c348240c38" +checksum = "f26ecb66b4bdca6c1409b40fb255eefc2bd4f6d135dab3c3124f80ffa2a9661e" dependencies = [ "atty", "humantime", @@ -641,24 +799,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" dependencies = [ "proc-macro2", - "quote 1.0.7", - "syn 1.0.31", + "quote", + "syn", "synstructure", ] -[[package]] -name = "fake-simd" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" - [[package]] name = "filetime" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "affc17579b132fc2461adf7c575cc6e8b134ebca52c51f5411388965227dc695" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "libc", "redox_syscall", "winapi 0.3.8", @@ -670,7 +822,7 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cfff41391129e0a856d6d822600b8d71179d46879e310417eb9c762eb178b42" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "crc32fast", "libc", "libz-sys", @@ -683,7 +835,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da62c4f1b81918835a8c6a484a397775fff5953fe83529afd51b05f5c6a6617d" dependencies = [ - "num-traits 0.2.11", + "num-traits", ] [[package]] @@ -785,7 +937,6 @@ dependencies = [ "futures-core", "futures-task", "futures-util", - "num_cpus", ] [[package]] @@ -794,6 +945,16 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789" +[[package]] +name = "futures-locks" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c4e684ddb2d8a4db5ca8a02b35156da129674ba4412b6f528698d58c594954" +dependencies = [ + "futures 0.3.5", + "tokio 0.2.21", +] + [[package]] name = "futures-macro" version = "0.3.5" @@ -802,8 +963,8 @@ checksum = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39" dependencies = [ "proc-macro-hack", "proc-macro2", - "quote 1.0.7", - "syn 1.0.31", + "quote", + "syn", ] [[package]] @@ -835,7 +996,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr 2.3.3", - "pin-project", + "pin-project 0.4.20", "pin-utils", "proc-macro-hack", "proc-macro-nested", @@ -851,6 +1012,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check 0.9.2", +] + [[package]] name = "getopts" version = "0.2.21" @@ -866,11 +1037,20 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "libc", "wasi", ] +[[package]] +name = "ghash" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6e27f0689a6e15944bdce7e45425efb87eaa8ab0c6e87f11d0987a9133e2531" +dependencies = [ + "polyval", +] + [[package]] name = "gimli" version = "0.21.0" @@ -893,7 +1073,7 @@ dependencies = [ "bytes 0.4.12", "fnv", "futures 0.1.29", - "http", + "http 0.1.21", "indexmap", "log 0.4.8", "slab", @@ -901,6 +1081,26 @@ dependencies = [ "tokio-io", ] +[[package]] +name = "h2" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e4728fd124914ad25e99e3d15a9361a879f6620f63cb56bbb08f95abb97a535" +dependencies = [ + "bytes 0.5.4", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.1", + "indexmap", + "slab", + "tokio 0.2.21", + "tokio-util 0.3.1", + "tracing", + "tracing-futures", +] + [[package]] name = "hermit-abi" version = "0.1.13" @@ -910,14 +1110,30 @@ dependencies = [ "libc", ] +[[package]] +name = "hex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" + [[package]] name = "hmac" -version = "0.7.1" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" +dependencies = [ + "crypto-mac 0.8.0", + "digest 0.9.0", +] + +[[package]] +name = "hmac" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695" +checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15" dependencies = [ - "crypto-mac", - "digest", + "crypto-mac 0.10.0", + "digest 0.9.0", ] [[package]] @@ -931,6 +1147,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9" +dependencies = [ + "bytes 0.5.4", + "fnv", + "itoa", +] + [[package]] name = "http-body" version = "0.1.0" @@ -939,24 +1166,37 @@ checksum = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d" dependencies = [ "bytes 0.4.12", "futures 0.1.29", - "http", + "http 0.1.21", "tokio-buf", ] +[[package]] +name = "http-body" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" +dependencies = [ + "bytes 0.5.4", + "http 0.2.1", +] + [[package]] name = "httparse" version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" +[[package]] +name = "httpdate" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" + [[package]] name = "humantime" -version = "1.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" -dependencies = [ - "quick-error", -] +checksum = "3c1ad908cc71012b7bea4d0c53ba96a8cba9962f048fa68d143376143d863b7a" [[package]] name = "hyper" @@ -967,16 +1207,16 @@ dependencies = [ "bytes 0.4.12", "futures 0.1.29", "futures-cpupool", - "h2", - "http", - "http-body", + "h2 0.1.26", + "http 0.1.21", + "http-body 0.1.0", "httparse", "iovec", "itoa", "log 0.4.8", "net2", "rustc_version", - "time", + "time 0.1.43", "tokio 0.1.22", "tokio-buf", "tokio-executor", @@ -985,37 +1225,110 @@ dependencies = [ "tokio-tcp", "tokio-threadpool", "tokio-timer", - "want", + "want 0.2.0", ] [[package]] -name = "hyper-tls" -version = "0.3.2" +name = "hyper" +version = "0.13.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a800d6aa50af4b5850b2b0f659625ce9504df908e9733b635720483be26174f" +checksum = "f6ad767baac13b44d4529fcf58ba2cd0995e36e7b435bc5b039de6f47e880dbf" dependencies = [ - "bytes 0.4.12", - "futures 0.1.29", - "hyper", - "native-tls", - "tokio-io", + "bytes 0.5.4", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.2.7", + "http 0.2.1", + "http-body 0.3.1", + "httparse", + "httpdate", + "itoa", + "pin-project 1.0.2", + "socket2", + "tokio 0.2.21", + "tower-service", + "tracing", + "want 0.3.0", ] [[package]] -name = "hyperx" -version = "0.12.0" +name = "hyper-rustls" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78e2d2253d7a17929560fc3adf48c48fc924c94fa4507e037a60e6bc55c0eda6" +checksum = "ac965ea399ec3a25ac7d13b8affd4b8f39325cca00858ddf5eb29b79e6b14b08" dependencies = [ - "base64 0.9.3", + "bytes 0.5.4", + "ct-logs 0.6.0", + "futures-util", + "hyper 0.13.9", + "log 0.4.8", + "rustls 0.17.0", + "rustls-native-certs 0.3.0", + "tokio 0.2.21", + "tokio-rustls 0.13.1", + "webpki", +] + +[[package]] +name = "hyper-rustls" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37743cc83e8ee85eacfce90f2f4102030d9ff0a95244098d781e9bee4a90abb6" +dependencies = [ + "bytes 0.5.4", + "ct-logs 0.7.0", + "futures-util", + "hyper 0.13.9", + "log 0.4.8", + "rustls 0.18.1", + "rustls-native-certs 0.4.0", + "tokio 0.2.21", + "tokio-rustls 0.14.1", + "webpki", +] + +[[package]] +name = "hyper-tls" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a800d6aa50af4b5850b2b0f659625ce9504df908e9733b635720483be26174f" +dependencies = [ + "bytes 0.4.12", + "futures 0.1.29", + "hyper 0.12.35", + "native-tls", + "tokio-io", +] + +[[package]] +name = "hyper-tls" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d979acc56dcb5b8dddba3917601745e877576475aa046df3226eabdecef78eed" +dependencies = [ + "bytes 0.5.4", + "hyper 0.13.9", + "native-tls", + "tokio 0.2.21", + "tokio-tls", +] + +[[package]] +name = "hyperx" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a94cbc2c6f63028e5736ca4e811ae36d3990059c384cbe68298c66728a9776" +dependencies = [ + "base64 0.10.1", "bytes 0.4.12", - "http", + "http 0.1.21", "httparse", "language-tags", "log 0.4.8", "mime 0.3.16", "percent-encoding 1.0.1", - "time", + "time 0.1.43", "unicase 2.6.0", ] @@ -1059,6 +1372,12 @@ dependencies = [ "libc", ] +[[package]] +name = "ipnet" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135" + [[package]] name = "itertools" version = "0.9.0" @@ -1068,6 +1387,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "0.4.5" @@ -1083,21 +1411,35 @@ dependencies = [ "libc", ] +[[package]] +name = "js-sys" +version = "0.3.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca059e81d9486668f12d455a4ea6daa600bd408134cd17e3d3fb5a32d1f016f8" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "jsonwebtoken" -version = "6.0.1" +version = "7.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a81d1812d731546d2614737bee92aa071d37e9afa1409bc374da9e5e70e70b22" +checksum = "afabcc15e437a6484fc4f12d0fd63068fe457bf93f1c148d3d9649c60b103f32" dependencies = [ - "base64 0.10.1", - "chrono", + "base64 0.12.3", + "pem", "ring", "serde", - "serde_derive", "serde_json", - "untrusted", + "simple_asn1 0.4.1", ] +[[package]] +name = "keccak" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" + [[package]] name = "kernel32-sys" version = "0.2.2" @@ -1119,6 +1461,9 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] [[package]] name = "libc" @@ -1126,6 +1471,12 @@ version = "0.2.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" +[[package]] +name = "libm" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" + [[package]] name = "libmount" version = "0.1.15" @@ -1190,7 +1541,7 @@ version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", ] [[package]] @@ -1218,13 +1569,13 @@ checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" [[package]] name = "md-5" -version = "0.8.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18af3dcaf2b0219366cdb4e2af65a6101457b415c3d1a5c71dd9c2b7c77b9c8" +checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15" dependencies = [ "block-buffer", - "digest", - "opaque-debug", + "digest 0.9.0", + "opaque-debug 0.3.0", ] [[package]] @@ -1233,6 +1584,12 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79c56d6a0b07f9e19282511c83fc5b086364cbae4ba8c7d5f190c3d9b0425a48" +[[package]] +name = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + [[package]] name = "memcached-rs" version = "0.4.2" @@ -1324,7 +1681,7 @@ version = "0.6.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "fuchsia-zircon", "fuchsia-zircon-sys", "iovec", @@ -1412,8 +1769,8 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "security-framework", - "security-framework-sys", + "security-framework 0.4.4", + "security-framework-sys 0.4.3", "tempfile", ] @@ -1423,7 +1780,7 @@ version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "libc", "winapi 0.3.8", ] @@ -1436,7 +1793,7 @@ checksum = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce" dependencies = [ "bitflags", "cc", - "cfg-if", + "cfg-if 0.1.10", "libc", "void", ] @@ -1449,7 +1806,7 @@ checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" dependencies = [ "bitflags", "cc", - "cfg-if", + "cfg-if 0.1.10", "libc", "void", ] @@ -1460,6 +1817,47 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg 1.0.0", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e9a41747ae4633fce5adffb4d2e81ffc5e89593cb19917f8fb2cc5ff76507bf" +dependencies = [ + "autocfg 1.0.0", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d03c330f9f7a2c19e3c0b42698e48141d0809c78cd9b6219f85bd7d7e892aa" +dependencies = [ + "autocfg 0.1.7", + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.7.3", + "serde", + "smallvec 1.4.0", + "zeroize", +] + [[package]] name = "num-integer" version = "0.1.42" @@ -1467,16 +1865,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" dependencies = [ "autocfg 1.0.0", - "num-traits 0.2.11", + "num-traits", ] [[package]] -name = "num-traits" -version = "0.1.43" +name = "num-iter" +version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" +checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" dependencies = [ - "num-traits 0.2.11", + "autocfg 1.0.0", + "num-integer", + "num-traits", ] [[package]] @@ -1504,7 +1904,7 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbf9993e59c894e3c08aa1c2712914e9e6bf1fcbfc6bef283e2183df345a4fee" dependencies = [ - "num-traits 0.2.11", + "num-traits", ] [[package]] @@ -1513,6 +1913,15 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cbca9424c482ee628fa549d9c812e2cd22f1180b9222c9200fdfa6eb31aecb2" +[[package]] +name = "oid" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "293d5f18898078ea69ba1c84f3688d1f2b6744df8211da36197153157cee7055" +dependencies = [ + "serde", +] + [[package]] name = "once_cell" version = "1.4.0" @@ -1525,6 +1934,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "openssl" version = "0.10.29" @@ -1532,7 +1947,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cee6d85f4cb4c4f59a6a85d5b68a233d280c82e29e822913b9c8b129fbf20bdd" dependencies = [ "bitflags", - "cfg-if", + "cfg-if 0.1.10", "foreign-types", "lazy_static", "libc", @@ -1575,7 +1990,7 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "cloudabi", "libc", "redox_syscall", @@ -1584,6 +1999,17 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "pem" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59698ea79df9bf77104aefd39cc3ec990cb9693fb59c3b0a70ddf2646fdffb4b" +dependencies = [ + "base64 0.12.3", + "once_cell", + "regex", +] + [[package]] name = "percent-encoding" version = "1.0.1" @@ -1635,13 +2061,83 @@ dependencies = [ "unicase 1.4.2", ] +[[package]] +name = "picky" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d4848a165e4500f6b442901e9471181bd5a3cd1427e5186dde06d4f963feec" +dependencies = [ + "aes-gcm", + "base64 0.12.3", + "digest 0.9.0", + "http 0.2.1", + "num-bigint-dig", + "oid", + "picky-asn1", + "picky-asn1-der", + "picky-asn1-x509", + "rand 0.7.3", + "rsa", + "serde", + "serde_json", + "sha-1", + "sha2", + "sha3", + "thiserror", +] + +[[package]] +name = "picky-asn1" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0718a593406db1ad8be482278f79215a0901e978925462159d8598cacb004ea" +dependencies = [ + "oid", + "serde", + "serde_bytes", +] + +[[package]] +name = "picky-asn1-der" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233e556fc14cd42f38290ecd53f23a9fe047df2837d3d7494d27872b40a64bca" +dependencies = [ + "picky-asn1", + "serde", + "serde_bytes", +] + +[[package]] +name = "picky-asn1-x509" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8501e799b4c18bac0a6e74672126b1826df41178dcf076eec9ddefd93edcb11" +dependencies = [ + "base64 0.12.3", + "num-bigint-dig", + "oid", + "picky-asn1", + "picky-asn1-der", + "serde", +] + [[package]] name = "pin-project" version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e75373ff9037d112bb19bc61333a06a159eaeb217660dcfbea7d88e1db823919" dependencies = [ - "pin-project-internal", + "pin-project-internal 0.4.20", +] + +[[package]] +name = "pin-project" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ccc2237c2c489783abd8c4c80e5450fc0e98644555b1364da68cc29aa151ca7" +dependencies = [ + "pin-project-internal 1.0.2", ] [[package]] @@ -1651,8 +2147,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10b4b44893d3c370407a1d6a5cfde7c41ae0478e31c516c85f67eb3adc51be6d" dependencies = [ "proc-macro2", - "quote 1.0.7", - "syn 1.0.31", + "quote", + "syn", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8e8d2bf0b23038a4424865103a4df472855692821aab4e4f5c3312d461d9e5f" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1679,6 +2186,16 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b18befed8bc2b61abc79a457295e7e838417326da1586050b919414073977f19" +[[package]] +name = "polyval" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5884790f1ce3553ad55fec37b5aaac5882e0e845a2612df744d6c85c9bf046c" +dependencies = [ + "cfg-if 0.1.10", + "universal-hash", +] + [[package]] name = "ppv-lite86" version = "0.2.8" @@ -1728,11 +2245,11 @@ checksum = "0afe1bd463b9e9ed51d0e0f0b50b6b146aec855c56fd182bb242388710a9b6de" [[package]] name = "proc-macro2" -version = "1.0.18" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" dependencies = [ - "unicode-xid 0.2.0", + "unicode-xid", ] [[package]] @@ -1763,12 +2280,6 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" -[[package]] -name = "quote" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" - [[package]] name = "quote" version = "1.0.7" @@ -1801,19 +2312,6 @@ dependencies = [ "winapi 0.3.8", ] -[[package]] -name = "rand" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9" -dependencies = [ - "cloudabi", - "fuchsia-cprng", - "libc", - "rand_core 0.3.1", - "winapi 0.3.8", -] - [[package]] name = "rand" version = "0.6.5" @@ -1986,7 +2484,7 @@ dependencies = [ "pin-project-lite", "sha1", "tokio 0.2.21", - "tokio-util", + "tokio-util 0.2.0", "url 2.1.1", ] @@ -2047,25 +2545,65 @@ dependencies = [ "encoding_rs", "flate2", "futures 0.1.29", - "http", - "hyper", - "hyper-tls", + "http 0.1.21", + "hyper 0.12.35", + "hyper-tls 0.3.2", "log 0.4.8", "mime 0.3.16", "mime_guess 2.0.3", "native-tls", "serde", "serde_json", - "serde_urlencoded", - "time", + "serde_urlencoded 0.5.5", + "time 0.1.43", "tokio 0.1.22", "tokio-executor", "tokio-io", "tokio-threadpool", "tokio-timer", "url 1.7.2", - "uuid", - "winreg", + "uuid 0.7.4", + "winreg 0.6.2", +] + +[[package]] +name = "reqwest" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9eaa17ac5d7b838b7503d118fa16ad88f440498bf9ffe5424e621f93190d61e" +dependencies = [ + "base64 0.12.3", + "bytes 0.5.4", + "encoding_rs", + "futures-core", + "futures-util", + "http 0.2.1", + "http-body 0.3.1", + "hyper 0.13.9", + "hyper-rustls 0.21.0", + "hyper-tls 0.4.3", + "ipnet", + "js-sys", + "lazy_static", + "log 0.4.8", + "mime 0.3.16", + "mime_guess 2.0.3", + "native-tls", + "percent-encoding 2.1.0", + "pin-project-lite", + "rustls 0.18.1", + "serde", + "serde_json", + "serde_urlencoded 0.6.1", + "tokio 0.2.21", + "tokio-rustls 0.14.1", + "tokio-tls", + "url 2.1.1", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "winreg 0.7.0", ] [[package]] @@ -2079,15 +2617,16 @@ dependencies = [ [[package]] name = "ring" -version = "0.14.6" +version = "0.16.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "426bc186e3e95cac1e4a4be125a4aca7e84c2d616ffc02244eef36e2a60a093c" +checksum = "952cd6b98c85bbc30efa1ba5783b8abf12fec8b3287ffa52605b9432313e34e4" dependencies = [ "cc", - "lazy_static", "libc", + "once_cell", "spin", - "untrusted", + "untrusted 0.7.1", + "web-sys", "winapi 0.3.8", ] @@ -2109,44 +2648,241 @@ dependencies = [ "sha1", "term", "threadpool", - "time", + "time 0.1.43", "tiny_http", "url 1.7.2", ] [[package]] -name = "rust-argon2" -version = "0.7.0" +name = "rsa" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017" +checksum = "3648b669b10afeab18972c105e284a7b953a669b0be3514c27f9b17acab2f9cd" dependencies = [ - "base64 0.11.0", - "blake2b_simd", - "constant_time_eq", - "crossbeam-utils 0.7.2", + "byteorder", + "digest 0.9.0", + "lazy_static", + "num-bigint-dig", + "num-integer", + "num-iter", + "num-traits", + "pem", + "rand 0.7.3", + "sha2", + "simple_asn1 0.4.1", + "subtle 2.3.0", + "thiserror", + "zeroize", ] [[package]] -name = "rustc-demangle" -version = "0.1.16" +name = "rsa-der" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" +checksum = "1170c86c683547fa781a0e39e6e281ebaedd4515be8a806022984f427ea3d44d" +dependencies = [ + "simple_asn1 0.4.1", +] [[package]] -name = "rustc_version" -version = "0.2.3" +name = "rsa-export" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +checksum = "a29a64b407c67f1f7a605538dc0975d40f5f1479fc1b04f7568c78120993f7f7" dependencies = [ - "semver", + "num-bigint-dig", + "num-integer", + "pem", + "rsa", + "simple_asn1 0.5.0", ] [[package]] -name = "ryu" -version = "1.0.5" +name = "rsa-pem" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" - +checksum = "3ee7d87640dab9972e4d05503aad4c30a107ca50912d10596d44f8555b7da4ce" +dependencies = [ + "bit-vec", + "log 0.4.8", + "num-bigint 0.2.6", + "num-bigint-dig", + "num-traits", + "pem", + "rsa", + "thiserror", + "yasna", +] + +[[package]] +name = "rusoto_core" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e977941ee0658df96fca7291ecc6fc9a754600b21ad84b959eb1dbbc9d5abcc7" +dependencies = [ + "async-trait", + "base64 0.12.3", + "bytes 0.5.4", + "crc32fast", + "futures 0.3.5", + "http 0.2.1", + "hyper 0.13.9", + "hyper-rustls 0.20.0", + "lazy_static", + "log 0.4.8", + "md5 0.7.0", + "percent-encoding 2.1.0", + "pin-project 0.4.20", + "rusoto_credential", + "rusoto_signature", + "rustc_version", + "serde", + "serde_json", + "tokio 0.2.21", + "xml-rs", +] + +[[package]] +name = "rusoto_credential" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac05563f83489b19b4d413607a30821ab08bbd9007d14fa05618da3ef09d8b" +dependencies = [ + "async-trait", + "chrono", + "dirs 2.0.2", + "futures 0.3.5", + "hyper 0.13.9", + "pin-project 0.4.20", + "regex", + "serde", + "serde_json", + "shlex", + "tokio 0.2.21", + "zeroize", +] + +[[package]] +name = "rusoto_s3" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1146e37a7c1df56471ea67825fe09bbbd37984b5f6e201d8b2e0be4ee15643d8" +dependencies = [ + "async-trait", + "bytes 0.5.4", + "futures 0.3.5", + "rusoto_core", + "xml-rs", +] + +[[package]] +name = "rusoto_signature" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a740a88dde8ded81b6f2cff9cd5e054a5a2e38a38397260f7acdd2c85d17dd" +dependencies = [ + "base64 0.12.3", + "bytes 0.5.4", + "futures 0.3.5", + "hex", + "hmac 0.8.1", + "http 0.2.1", + "hyper 0.13.9", + "log 0.4.8", + "md5 0.7.0", + "percent-encoding 2.1.0", + "pin-project 0.4.20", + "rusoto_credential", + "rustc_version", + "serde", + "sha2", + "time 0.2.23", + "tokio 0.2.21", +] + +[[package]] +name = "rust-argon2" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017" +dependencies = [ + "base64 0.11.0", + "blake2b_simd", + "constant_time_eq", + "crossbeam-utils 0.7.2", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "rustls" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0d4a31f5d68413404705d6982529b0e11a9aacd4839d1d6222ee3b8cb4015e1" +dependencies = [ + "base64 0.11.0", + "log 0.4.8", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d1126dcf58e93cee7d098dbda643b5f92ed724f1f6a63007c1116eed6700c81" +dependencies = [ + "base64 0.12.3", + "log 0.4.8", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls-native-certs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75ffeb84a6bd9d014713119542ce415db3a3e4748f0bfce1e1416cd224a23a5" +dependencies = [ + "openssl-probe", + "rustls 0.17.0", + "schannel", + "security-framework 0.4.4", +] + +[[package]] +name = "rustls-native-certs" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "629d439a7672da82dd955498445e496ee2096fe2117b9f796558a43fdb9e59b8" +dependencies = [ + "openssl-probe", + "rustls 0.18.1", + "schannel", + "security-framework 1.0.0", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + [[package]] name = "safemem" version = "0.2.0" @@ -2175,28 +2911,32 @@ dependencies = [ "anyhow", "ar", "assert_cmd", + "assert_matches", + "async-trait", "atty", - "base64 0.11.0", - "bincode 1.2.1", + "base64 0.13.0", + "bincode", "blake3", "byteorder", + "bytes 0.5.4", "cc", "chrono", "clap", "counted-array", - "crossbeam-utils 0.5.0", + "crossbeam-utils 0.8.0", "daemonize", "directories", "env_logger", "filetime", "flate2", - "futures 0.1.29", "futures 0.3.5", - "hmac", - "http", - "hyper", + "futures-locks", + "hmac 0.10.1", + "http 0.2.1", + "hyper 0.13.9", + "hyper-rustls 0.21.0", "hyperx", - "itertools", + "itertools 0.10.0", "jobserver", "jsonwebtoken", "lazy_static", @@ -2210,15 +2950,24 @@ dependencies = [ "nix 0.17.0", "num_cpus", "number_prefix", + "oid", "openssl", + "picky", + "picky-asn1-x509", "predicates", - "rand 0.5.6", + "rand 0.7.3", "redis", "regex", - "reqwest", + "reqwest 0.10.8", "retry", "ring", "rouille", + "rsa", + "rsa-der", + "rsa-export", + "rsa-pem", + "rusoto_core", + "rusoto_s3", "selenium-rs", "serde", "serde_derive", @@ -2229,21 +2978,23 @@ dependencies = [ "syslog", "tar", "tempfile", - "time", + "thiserror", + "time 0.1.43", + "tokio 0.2.21", "tokio-compat", "tokio-io", "tokio-named-pipes", "tokio-process", "tokio-reactor", - "tokio-serde-bincode", + "tokio-serde", "tokio-tcp", "tokio-timer", - "tokio-uds", + "tokio-util 0.3.1", "toml", "tower", - "untrusted", + "untrusted 0.6.2", "url 1.7.2", - "uuid", + "uuid 0.8.1", "version-compare", "void", "walkdir", @@ -2269,6 +3020,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "sct" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c" +dependencies = [ + "ring", + "untrusted 0.7.1", +] + [[package]] name = "security-framework" version = "0.4.4" @@ -2279,7 +3040,20 @@ dependencies = [ "core-foundation", "core-foundation-sys", "libc", - "security-framework-sys", + "security-framework-sys 0.4.3", +] + +[[package]] +name = "security-framework" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad502866817f0575705bd7be36e2b2535cc33262d493aa733a2ec862baa2bc2b" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys 1.0.0", ] [[package]] @@ -2292,13 +3066,23 @@ dependencies = [ "libc", ] +[[package]] +name = "security-framework-sys" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51ceb04988b17b6d1dcd555390fa822ca5637b4a14e1f5099f13d351bed4d6c7" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "selenium-rs" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01173ad274e14fafa534a5e660d950ca1939ccebd3955df987b7df7e4e301108" dependencies = [ - "reqwest", + "reqwest 0.9.24", "serde", "serde_derive", "serde_json", @@ -2329,6 +3113,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.111" @@ -2336,8 +3129,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f2c3ac8e6ca1e9c80b8be1023940162bf81ae3cffbb1809474152f2ce1eb250" dependencies = [ "proc-macro2", - "quote 1.0.7", - "syn 1.0.31", + "quote", + "syn", ] [[package]] @@ -2363,16 +3156,29 @@ dependencies = [ "url 1.7.2", ] +[[package]] +name = "serde_urlencoded" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97" +dependencies = [ + "dtoa", + "itoa", + "serde", + "url 2.1.1", +] + [[package]] name = "sha-1" -version = "0.8.2" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +checksum = "ce3cdf1b5e620a498ee6f2a171885ac7e22f0e12089ec4b3d22b84921792507c" dependencies = [ "block-buffer", - "digest", - "fake-simd", - "opaque-debug", + "cfg-if 1.0.0", + "cpuid-bool", + "digest 0.9.0", + "opaque-debug 0.3.0", ] [[package]] @@ -2383,16 +3189,35 @@ checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" [[package]] name = "sha2" -version = "0.8.2" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" +checksum = "6e7aab86fe2149bad8c507606bdb3f4ef5e7b2380eb92350f56122cca72a42a8" dependencies = [ "block-buffer", - "digest", - "fake-simd", - "opaque-debug", + "cfg-if 1.0.0", + "cpuid-bool", + "digest 0.9.0", + "opaque-debug 0.3.0", ] +[[package]] +name = "sha3" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" +dependencies = [ + "block-buffer", + "digest 0.9.0", + "keccak", + "opaque-debug 0.3.0", +] + +[[package]] +name = "shlex" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" + [[package]] name = "signal-hook-registry" version = "1.2.0" @@ -2403,6 +3228,28 @@ dependencies = [ "libc", ] +[[package]] +name = "simple_asn1" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692ca13de57ce0613a363c8c2f1de925adebc81b04c923ac60c5488bb44abe4b" +dependencies = [ + "chrono", + "num-bigint 0.2.6", + "num-traits", +] + +[[package]] +name = "simple_asn1" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39465bdea3e86aa6f95f69d1b7e3010634fdeda0bc4b6c9124cbcd7419873065" +dependencies = [ + "chrono", + "num-bigint 0.3.1", + "num-traits", +] + [[package]] name = "siphasher" version = "0.2.3" @@ -2446,7 +3293,7 @@ version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "libc", "redox_syscall", "winapi 0.3.8", @@ -2458,6 +3305,64 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "standback" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf906c8b8fc3f6ecd1046e01da1d8ddec83e48c8b08b84dcc02b585a6bedf5a8" +dependencies = [ + "version_check 0.9.2", +] + +[[package]] +name = "stdweb" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" +dependencies = [ + "discard", + "rustc_version", + "stdweb-derive", + "stdweb-internal-macros", + "stdweb-internal-runtime", + "wasm-bindgen", +] + +[[package]] +name = "stdweb-derive" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "serde_derive", + "syn", +] + +[[package]] +name = "stdweb-internal-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" +dependencies = [ + "base-x", + "proc-macro2", + "quote", + "serde", + "serde_derive", + "serde_json", + "sha1", + "syn", +] + +[[package]] +name = "stdweb-internal-runtime" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" + [[package]] name = "string" version = "0.2.1" @@ -2489,34 +3394,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" [[package]] -name = "syn" -version = "0.11.11" +name = "subtle" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" -dependencies = [ - "quote 0.3.15", - "synom", - "unicode-xid 0.0.4", -] +checksum = "343f3f510c2915908f155e94f17220b19ccfacf2a64a2a5d8004f2c3e311e7fd" [[package]] name = "syn" -version = "1.0.31" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5304cfdf27365b7585c25d4af91b35016ed21ef88f17ced89c7093b43dba8b6" +checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac" dependencies = [ "proc-macro2", - "quote 1.0.7", - "unicode-xid 0.2.0", -] - -[[package]] -name = "synom" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" -dependencies = [ - "unicode-xid 0.0.4", + "quote", + "unicode-xid", ] [[package]] @@ -2526,9 +3417,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" dependencies = [ "proc-macro2", - "quote 1.0.7", - "syn 1.0.31", - "unicode-xid 0.2.0", + "quote", + "syn", + "unicode-xid", ] [[package]] @@ -2540,7 +3431,7 @@ dependencies = [ "error-chain", "libc", "log 0.4.8", - "time", + "time 0.1.43", ] [[package]] @@ -2571,7 +3462,7 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "libc", "rand 0.7.3", "redox_syscall", @@ -2586,7 +3477,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42" dependencies = [ "byteorder", - "dirs", + "dirs 1.0.5", "winapi 0.3.8", ] @@ -2624,8 +3515,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "893582086c2f98cde18f906265a65b5030a074b1046c674ae898be6519a7f479" dependencies = [ "proc-macro2", - "quote 1.0.7", - "syn 1.0.31", + "quote", + "syn", ] [[package]] @@ -2656,6 +3547,44 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "time" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcdaeea317915d59b2b4cd3b5efcd156c309108664277793f5351700c02ce98b" +dependencies = [ + "const_fn", + "libc", + "standback", + "stdweb", + "time-macros", + "version_check 0.9.2", + "winapi 0.3.8", +] + +[[package]] +name = "time-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" +dependencies = [ + "proc-macro-hack", + "time-macros-impl", +] + +[[package]] +name = "time-macros-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5c3be1edfad6027c69f5491cf4cb310d1a71ecd6af742788c6ff8bced86b8fa" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "standback", + "syn", +] + [[package]] name = "tiny_http" version = "0.6.2" @@ -2707,10 +3636,14 @@ dependencies = [ "libc", "memchr 2.3.3", "mio", + "mio-named-pipes", "mio-uds", "num_cpus", "pin-project-lite", + "signal-hook-registry", "slab", + "tokio-macros", + "winapi 0.3.8", ] [[package]] @@ -2794,6 +3727,17 @@ dependencies = [ "log 0.4.8", ] +[[package]] +name = "tokio-macros" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e44da00bfc73a25f814cd8d7e57a68a5c31b74b3152a0a1d1f590c97ed06265a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tokio-named-pipes" version = "0.1.0" @@ -2846,27 +3790,38 @@ dependencies = [ ] [[package]] -name = "tokio-serde" -version = "0.1.0" +name = "tokio-rustls" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "894168193c4f80862a2244ff953b69145a9961a9efba39500e0970b083d0649c" +checksum = "15cb62a0d2770787abc96e99c1cd98fcf17f94959f3af63ca85bdfb203f051b4" dependencies = [ - "bytes 0.4.12", - "futures 0.1.29", + "futures-core", + "rustls 0.17.0", + "tokio 0.2.21", + "webpki", ] [[package]] -name = "tokio-serde-bincode" -version = "0.1.1" +name = "tokio-rustls" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e35c8d60a5e87cfb30dd562a309e56f8a6d36617b0a76c87f04d5466607ca8" +checksum = "e12831b255bcfa39dc0436b01e19fea231a37db570686c06ee72c423479f889a" dependencies = [ - "bincode 0.8.0", - "bytes 0.4.12", - "derive-error", - "futures 0.1.29", - "serde", - "tokio-serde", + "futures-core", + "rustls 0.18.1", + "tokio 0.2.21", + "webpki", +] + +[[package]] +name = "tokio-serde" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebdd897b01021779294eb09bb3b52b6e11b0747f9f7e333a84bef532b656de99" +dependencies = [ + "bytes 0.5.4", + "futures 0.3.5", + "pin-project 0.4.20", ] [[package]] @@ -2939,6 +3894,16 @@ dependencies = [ "tokio-executor", ] +[[package]] +name = "tokio-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a70f4fcd7b3b24fb194f837560168208f669ca8cb70d0c4b862944452396343" +dependencies = [ + "native-tls", + "tokio 0.2.21", +] + [[package]] name = "tokio-udp" version = "0.1.6" @@ -2986,6 +3951,20 @@ dependencies = [ "tokio 0.2.21", ] +[[package]] +name = "tokio-util" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" +dependencies = [ + "bytes 0.5.4", + "futures-core", + "futures-sink", + "log 0.4.8", + "pin-project-lite", + "tokio 0.2.21", +] + [[package]] name = "toml" version = "0.5.6" @@ -2997,11 +3976,11 @@ dependencies = [ [[package]] name = "tower" -version = "0.1.1" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc72f33b6a72c75c9df0037afce313018bae845f0ec7fdb9201b8768427a917f" +checksum = "fd3169017c090b7a28fce80abaad0ab4f5566423677c9331bb320af7e49cfe62" dependencies = [ - "futures 0.1.29", + "futures-core", "tower-buffer", "tower-discover", "tower-layer", @@ -3015,13 +3994,13 @@ dependencies = [ [[package]] name = "tower-buffer" -version = "0.1.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c7b83e1ccf5b23dd109dd6ae2c07b8e2beec7a51a21f29da2dba576317370e0" +checksum = "c4887dc2a65d464c8b9b66e0e4d51c2fd6cf5b3373afc72805b0a60bce00446a" dependencies = [ - "futures 0.1.29", - "tokio-executor", - "tokio-sync", + "futures-core", + "pin-project 0.4.20", + "tokio 0.2.21", "tower-layer", "tower-service", "tracing", @@ -3029,91 +4008,101 @@ dependencies = [ [[package]] name = "tower-discover" -version = "0.1.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73a7632286f78164d65d18fd0e570307acde9362489aa5c8c53e6315cc2bde47" +checksum = "0f6b5000c3c54d269cc695dff28136bb33d08cbf1df2c48129e143ab65bf3c2a" dependencies = [ - "futures 0.1.29", + "futures-core", + "pin-project 0.4.20", "tower-service", ] [[package]] name = "tower-layer" -version = "0.1.0" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a35d656f2638b288b33495d1053ea74c40dc05ec0b92084dd71ca5566c4ed1dc" + +[[package]] +name = "tower-limit" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ddf07e10c07dcc8f41da6de036dc66def1a85b70eb8a385159e3908bb258328" +checksum = "92c3040c5dbed68abffaa0d4517ac1a454cd741044f33ab0eefab6b8d1361404" dependencies = [ - "futures 0.1.29", + "futures-core", + "pin-project 0.4.20", + "tokio 0.2.21", + "tower-layer", + "tower-load", "tower-service", ] [[package]] -name = "tower-limit" -version = "0.1.3" +name = "tower-load" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c21ba835a08fd54b63cd91ae0548a7b6e2a91075147dfa3dc8e1a940c1b6f18f" +checksum = "8cc79fc3afd07492b7966d7efa7c6c50f8ed58d768a6075dd7ae6591c5d2017b" dependencies = [ - "futures 0.1.29", - "tokio-sync", - "tokio-timer", - "tower-layer", + "futures-core", + "log 0.4.8", + "pin-project 0.4.20", + "tokio 0.2.21", + "tower-discover", "tower-service", - "tracing", ] [[package]] name = "tower-load-shed" -version = "0.1.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04fbaf5bfb63d84204db87b9b2aeec61549613f2bbb8706dcc36f5f3ea8cd769" +checksum = "9f021e23900173dc315feb4b6922510dae3e79c689b74c089112066c11f0ae4e" dependencies = [ - "futures 0.1.29", + "futures-core", + "pin-project 0.4.20", "tower-layer", "tower-service", ] [[package]] name = "tower-retry" -version = "0.1.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09e80588125061f276ed2a7b0939988b411e570a2dbb2965b1382ef4f71036f7" +checksum = "e6727956aaa2f8957d4d9232b308fe8e4e65d99db30f42b225646e86c9b6a952" dependencies = [ - "futures 0.1.29", - "tokio-timer", + "futures-core", + "pin-project 0.4.20", + "tokio 0.2.21", "tower-layer", "tower-service", ] [[package]] name = "tower-service" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc0c98637d23732f8de6dfd16494c9f1559c3b9e20b4a46462c8f9b9e827bfa" -dependencies = [ - "futures 0.1.29", -] +checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860" [[package]] name = "tower-timeout" -version = "0.1.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c06bbc2fbd056f810940a8c6f0cc194557d36da3c22999a755a7a6612447da9" +checksum = "127b8924b357be938823eaaec0608c482d40add25609481027b96198b2e4b31e" dependencies = [ - "futures 0.1.29", - "tokio-timer", + "pin-project 0.4.20", + "tokio 0.2.21", "tower-layer", "tower-service", ] [[package]] name = "tower-util" -version = "0.1.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4792342fac093db5d2558655055a89a04ca909663467a4310c7739d9f8b64698" +checksum = "d1093c19826d33807c72511e68f73b4a0469a3f22c2bd5f7d5212178b4b89674" dependencies = [ - "futures 0.1.29", - "tokio-io", - "tower-layer", + "futures-core", + "futures-util", + "pin-project 0.4.20", "tower-service", ] @@ -3123,7 +4112,7 @@ version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a41f40ed0e162c911ac6fcb53ecdc8134c46905fdbbae8c50add462a538b495f" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "log 0.4.8", "tracing-attributes", "tracing-core", @@ -3136,8 +4125,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99bbad0de3fd923c9c3232ead88510b783e5a4d16a6154adffa3d53308de984c" dependencies = [ "proc-macro2", - "quote 1.0.7", - "syn 1.0.31", + "quote", + "syn", ] [[package]] @@ -3149,6 +4138,16 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "tracing-futures" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab7bb6f14721aa00656086e9335d363c5c8747bae02ebe32ea2c7dece5689b4c" +dependencies = [ + "pin-project 0.4.20", + "tracing", +] + [[package]] name = "treeline" version = "0.1.0" @@ -3167,7 +4166,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "283d3b89e1368717881a9d51dad843cc435380d8109c9e47d38780a324698d8b" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", ] [[package]] @@ -3229,15 +4228,19 @@ checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" [[package]] name = "unicode-xid" -version = "0.0.4" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" +checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" [[package]] -name = "unicode-xid" -version = "0.2.0" +name = "universal-hash" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402" +dependencies = [ + "generic-array 0.14.4", + "subtle 2.3.0", +] [[package]] name = "unix_socket" @@ -3245,7 +4248,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6aa2700417c405c38f5e6902d699345241c28c0b7ade4abaad71e35a87eb1564" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "libc", ] @@ -3264,6 +4267,12 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cd1f4b4e96b46aeb8d4855db4a7a9bd96eeeb5c6a1ab54593328761642ce2f" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "url" version = "1.7.2" @@ -3301,6 +4310,15 @@ dependencies = [ "rand 0.6.5", ] +[[package]] +name = "uuid" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" +dependencies = [ + "rand 0.7.3", +] + [[package]] name = "vcpkg" version = "0.2.10" @@ -3315,9 +4333,9 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version-compare" -version = "0.0.10" +version = "0.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d63556a25bae6ea31b52e640d7c41d1ab27faba4ccb600013837a3d0b3994ca1" +checksum = "1c18c859eead79d8b95d09e4678566e8d70105c4e7b251f707a03df32442661b" [[package]] name = "version_check" @@ -3377,12 +4395,119 @@ dependencies = [ "try-lock", ] +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log 0.4.8", + "try-lock", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +[[package]] +name = "wasm-bindgen" +version = "0.2.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac64ead5ea5f05873d7c12b545865ca2b8d28adfc50a49b84770a3a97265d42" +dependencies = [ + "cfg-if 0.1.10", + "serde", + "serde_json", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f22b422e2a757c35a73774860af8e112bff612ce6cb604224e8e47641a9e4f68" +dependencies = [ + "bumpalo", + "lazy_static", + "log 0.4.8", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7866cab0aa01de1edf8b5d7936938a7e397ee50ce24119aef3e1eaa3b6171da" +dependencies = [ + "cfg-if 0.1.10", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b13312a745c08c469f0b292dd2fcd6411dba5f7160f593da6ef69b64e407038" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f249f06ef7ee334cc3b8ff031bfc11ec99d00f34d86da7498396dc1e3b1498fe" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307" + +[[package]] +name = "web-sys" +version = "0.3.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bf6ef87ad7ae8008e15a355ce696bed26012b7caa21605188cfd8214ab51e2d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab146130f5f790d45f82aeeb09e55a256573373ec64409fc19a6fb82fb1032ae" +dependencies = [ + "ring", + "untrusted 0.7.1", +] + +[[package]] +name = "webpki-roots" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8eff4b7516a57307f9349c64bf34caa34b940b66fed4b2fb3136cb7386e5739" +dependencies = [ + "webpki", +] + [[package]] name = "which" version = "4.0.0" @@ -3445,6 +4570,15 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "winreg" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" +dependencies = [ + "winapi 0.3.8", +] + [[package]] name = "ws2_32-sys" version = "0.2.1" @@ -3464,6 +4598,43 @@ dependencies = [ "libc", ] +[[package]] +name = "xml-rs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b07db065a5cf61a7e4ba64f29e67db906fb1787316516c4e6e5ff0fea1efcd8a" + +[[package]] +name = "yasna" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de7bff972b4f2a06c85f6d8454b09df153af7e3a4ec2aac81db1b105b684ddb" +dependencies = [ + "bit-vec", + "num-bigint 0.2.6", +] + +[[package]] +name = "zeroize" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f33972566adbd2d3588b0491eb94b98b43695c4ef897903470ede4f3f5a28a" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f369ddb18862aba61aa49bf31e74d29f0f162dec753063200e1dc084345d16" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "zip" version = "0.5.5" @@ -3477,18 +4648,18 @@ dependencies = [ [[package]] name = "zstd" -version = "0.5.2+zstd.1.4.5" +version = "0.6.0+zstd.1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644352b10ce7f333d6e0af85bd4f5322dc449416dc1211c6308e95bca8923db4" +checksum = "d4e44664feba7f2f1a9f300c1f6157f2d1bfc3c15c6f3cf4beabf3f5abe9c237" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "2.0.4+zstd.1.4.5" +version = "3.0.0+zstd.1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7113c0c9aed2c55181f2d9f5b0a36e7d2c0183b11c058ab40b35987479efe4d7" +checksum = "d9447afcd795693ad59918c7bbffe42fdd6e467d708f3537e3dc14dc598c573f" dependencies = [ "libc", "zstd-sys", @@ -3496,12 +4667,12 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "1.4.16+zstd.1.4.5" +version = "1.4.19+zstd.1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c442965efc45353be5a9b9969c9b0872fff6828c7e06d118dda2cb2d0bb11d5a" +checksum = "ec24a9273d24437afb8e71b16f3d9a5d569193cccdb7896213b59f552f387674" dependencies = [ "cc", "glob", - "itertools", + "itertools 0.9.0", "libc", ] diff --git a/Cargo.toml b/Cargo.toml index 534ad0889..7208dd6ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,19 +1,15 @@ [package] name = "sccache" version = "0.2.14-alpha.0" -authors = ["Ted Mielczarek ", "Alex Crichton "] +authors = ["Bernhard Schuster ", "Ted Mielczarek ", "Alex Crichton "] license = "Apache-2.0" description = "Sccache is a ccache-like tool. It is used as a compiler wrapper and avoids compilation when possible, storing a cache in a remote storage using the S3 API." -repository = "https://github.com/mozilla/sccache/" +repository = "https://github.com/paritytech/sccache/" readme = "README.md" categories = ["command-line-utilities", "development-tools::build-utils"] -keywords = ["ccache"] +keywords = ["ccache", "compile", "cache", "sccache"] edition = "2018" -[badges] -travis-ci = { repository = "mozilla/sccache" } -appveyor = { repository = "mozilla/sccache" } - [[bin]] name = "sccache" @@ -24,76 +20,100 @@ required-features = ["dist-server"] [dependencies] anyhow = "1.0" ar = { version = "0.8", optional = true } -atty = "0.2.6" -base64 = "0.11.0" +async-trait = "0.1" +atty = "^0.2.6" +base64 = { version = "0.13.0", features = ["std"] } bincode = "1" blake3 = "0.3" byteorder = "1.0" +bytes = "0.5" chrono = { version = "0.4", optional = true } -clap = "2.23.0" +clap = "2.33" counted-array = "0.1" -directories = "2" -env_logger = "0.5" +directories = "3" +env_logger = "0.8" filetime = "0.2" flate2 = { version = "1.0", optional = true, default-features = false, features = ["rust_backend"] } -futures = "0.1.11" -futures_03 = { package = "futures", version = "0.3", features = ["compat", "thread-pool"] } -hmac = { version = "0.7", optional = true } -http = "0.1" -hyper = { version = "0.12", optional = true } -hyperx = { version = "0.12", optional = true } +futures = "0.3" +futures-locks = "0.6" + +hmac = { version = "0.10", optional = true } +http = "^0.2.1" +hyper = { version = "0.13", optional = true } +hyperx = { version = "0.13", optional = true } +hyper-rustls = { version = "0.21", optional = true } jobserver = "0.1" -jsonwebtoken = { version = "6.0.1", optional = true } -lazy_static = "1.0.0" -libc = "0.2.10" +jsonwebtoken = { version = "7", optional = true } +lazy_static = "1.4" +libc = "^0.2.10" local-encoding = "0.2.0" log = "0.4" lru-disk-cache = { path = "lru-disk-cache", version = "0.4.0" } -md-5 = { version = "0.8", optional = true } +md-5 = { version = "0.9", optional = true } memcached-rs = { version = "0.4" , optional = true } -num_cpus = "1.0" -number_prefix = "0.2.5" -openssl = { version = "0.10", optional = true } -rand = "0.5" +num_cpus = "1.13" +number_prefix = "0.2" +# avoid duplicate dependency by sticking to 0.1 for now +oid = "0.1" +# x509 certificate generation +picky = "^6.1.1" +picky-asn1-x509 = "0.4" +rand = "0.7" redis = { version = "0.15.0", optional = true } regex = "1" -reqwest = { version = "0.9.11", optional = true } +reqwest = { version = "0.10", features = ["rustls-tls", "json", "blocking"], optional = true } retry = "0.4.0" -ring = { version = "0.14.6", optional = true } -sha-1 = { version = "0.8", optional = true } -sha2 = { version = "0.8", optional = true } +rsa = "0.3" +# exports pkcs#1 +rsa-export = "0.2" +# both are pkcs8 only +rsa-der = "0.2" +rsa-pem = "0.2" +ring = { version = "0.16.15", features = ["std"], optional = true } +rusoto_core = { version = "0.45.0", default_features=false, features = ["rustls"], optional = true } +rusoto_s3 = { version = "0.45.0", default_features=false, features = ["rustls"], optional = true } +sha-1 = { version = "0.9", optional = true } +sha2 = { version = "0.9", optional = true } serde = "1.0" serde_derive = "1.0" serde_json = "1.0" strip-ansi-escapes = "0.1" tar = "0.4" tempfile = "3" +# while generally anyhow is sufficient, it's not `Clone` +# which is necessary for some trait objects +thiserror = "1" time = "0.1.35" +tokio = { version = "0.2", features = ["io-util", "time", "uds", "tcp", "process"] } tokio-compat = "0.1" tokio-io = "0.1" tokio-process = "0.2" -tokio-serde-bincode = "0.1" -tower = "0.1" +tokio-serde = "0.6" +tokio-util = { version = "0.3", features = ["codec"] } +tower = "0.3" tokio-tcp = "0.1" tokio-timer = "0.2" toml = "0.5" untrusted = { version = "0.6.0", optional = true } url = { version = "1.0", optional = true } -uuid = { version = "0.7", features = ["v4"] } +uuid = { version = "0.8", features = ["v4"] } walkdir = "2" # by default which pulls in an outdated failure version which = { version = "4", default-features = false } zip = { version = "0.5", default-features = false, features = ["deflate"] } -zstd = { version = "0.5" } +zstd = { version = "0.6" } # dist-server only -crossbeam-utils = { version = "0.5", optional = true } +crossbeam-utils = { version = "0.8", optional = true } libmount = { version = "0.1.10", optional = true } nix = { version = "0.17.0", optional = true } rouille = { version = "2.2", optional = true, default-features = false, features = ["ssl"] } syslog = { version = "5", optional = true } void = { version = "1", optional = true } -version-compare = { version = "0.0.10", optional = true } +version-compare = { version = "0.0.11", optional = true } + +# test only +openssl = { version = "0.10", optional = true } [patch.crates-io] # Waiting for #151 to make it into a release @@ -101,15 +121,15 @@ tiny_http = { git = "https://github.com/tiny-http/tiny-http.git", rev = "619680d [dev-dependencies] assert_cmd = "1" +assert_matches = "1" cc = "1.0" chrono = "0.4" -itertools = "0.9" +itertools = "0.10" predicates = "1" selenium-rs = "0.1" [target.'cfg(unix)'.dependencies] daemonize = "0.4" -tokio-uds = "0.2" [target.'cfg(windows)'.dependencies] tokio-named-pipes = "0.1" @@ -124,22 +144,28 @@ features = [ ] [features] -default = ["dist-client", "s3"] -all = ["dist-client", "redis", "s3", "memcached", "gcs", "azure"] -azure = ["chrono", "hyper", "hyperx", "url", "hmac", "md-5", "sha2"] -s3 = ["chrono", "hyper", "hyperx", "reqwest", "simple-s3", "hmac", "sha-1"] +default = ["redis", "s3", "memcached", "gcs", "azure", "dist-client", "dist-server"] +# legacy compat, do not use +all = ["redis", "s3", "memcached", "gcs", "azure", "dist-client", "dist-server"] + +azure = ["chrono", "hyper", "hyperx", "reqwest", "url", "hmac", "md-5", "sha2"] +s3 = ["chrono", "hyper", "hyper-rustls", "hyperx", "reqwest", "rusoto_core", "rusoto_s3", "hmac", "sha-1"] simple-s3 = [] -gcs = ["chrono", "hyper", "hyperx", "reqwest", "ring", "untrusted", "url"] +gcs = ["chrono", "hyper", "hyperx", "reqwest", "ring", "untrusted", "url", "sha2"] memcached = ["memcached-rs"] + native-zlib = ["zip/deflate-zlib"] # Enable features that require unstable features of Nightly Rust. unstable = [] + # Enables distributed support in the sccache client dist-client = ["ar", "flate2", "hyper", "hyperx", "reqwest", "url", "sha2"] # Enables the sccache-dist binary -dist-server = ["crossbeam-utils", "jsonwebtoken", "flate2", "libmount", "nix", "openssl", "reqwest", "rouille", "syslog", "void", "version-compare"] +dist-server = ["chrono", "crossbeam-utils", "hyperx", "jsonwebtoken", "flate2", "libmount", "nix", "reqwest", "rouille", "sha2", "syslog", "void", "version-compare"] # Enables dist tests with external requirements dist-tests = ["dist-client", "dist-server"] +# Run JWK token crypto against openssl ref impl +vs_openssl = ["openssl", "dist-server"] [workspace] exclude = ["tests/test-crate"] diff --git a/README.md b/README.md index 0ca70329b..967c96886 100644 --- a/README.md +++ b/README.md @@ -125,38 +125,22 @@ cargo build --release [--features=all|s3|redis|gcs|memcached|azure] By default, `sccache` supports a local disk cache and S3. Use the `--features` flag to build `sccache` with support for other storage options. Refer the [Cargo Documentation](http://doc.crates.io/manifest.html#the-features-section) for details on how to select features with Cargo. -### Building portable binaries - -When building with the `gcs` feature, `sccache` will depend on OpenSSL, which can be an annoyance if you want to distribute portable binaries. It is possible to statically link against OpenSSL using the steps below before building with `cargo`. #### Linux -You will need to download and build OpenSSL with `-fPIC` in order to statically link against it. - -```bash -./config -fPIC --prefix=/usr/local --openssldir=/usr/local/ssl -make -make install -export OPENSSL_LIB_DIR=/usr/local/lib -export OPENSSL_INCLUDE_DIR=/usr/local/include -export OPENSSL_STATIC=yes -``` +No native dependencies. Build with `cargo` and use `ldd` to check that the resulting binary does not depend on OpenSSL anymore. #### macOS -Just setting the below environment variable will enable static linking. - -```bash -export OPENSSL_STATIC=yes -``` +No native dependencies. Build with `cargo` and use `otool -L` to check that the resulting binary does not depend on OpenSSL anymore. #### Windows -On Windows it is fairly straightforward to just ship the required `libcrypto` and `libssl` DLLs with `sccache.exe`, but the binary might also depend on a few MSVC CRT DLLs that are not available on older Windows versions. +On Windows the binary might also depend on a few MSVC CRT DLLs that are not available on older Windows versions. It is possible to statically link against the CRT using a `.cargo/config` file with the following contents. @@ -167,18 +151,6 @@ rustflags = ["-Ctarget-feature=+crt-static"] Build with `cargo` and use `dumpbin /dependents` to check that the resulting binary does not depend on MSVC CRT DLLs anymore. -In order to statically link against both the CRT and OpenSSL, you will need to either build OpenSSL static libraries (with a statically linked CRT) yourself or get a pre-built distribution that provides these. - -Then you can set environment variables which get picked up by the `openssl-sys` crate. - -See the following example for using pre-built libraries from [Shining Light Productions](https://slproweb.com/products/Win32OpenSSL.html), assuming an installation in `C:\OpenSSL-Win64`: - -``` -set OPENSSL_LIB_DIR=C:\OpenSSL-Win64\lib\VC\static -set OPENSSL_INCLUDE_DIR=C:\OpenSSL-Win64\include -set OPENSSL_LIBS=libcrypto64MT:libssl64MT -``` - --- Storage Options @@ -192,13 +164,14 @@ The default cache size is 10 gigabytes. To change this, set `SCCACHE_CACHE_SIZE` ### S3 If you want to use S3 storage for the sccache cache, you need to set the `SCCACHE_BUCKET` environment variable to the name of the S3 bucket to use. -You can use `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` to set the S3 credentials. Alternately, you can set `AWS_IAM_CREDENTIALS_URL` to a URL that returns credentials in the format supported by the [EC2 metadata service](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#instance-metadata-security-credentials), and credentials will be fetched from that location as needed. In the absence of either of these options, credentials for the instance's IAM role will be fetched from the EC2 metadata service directly. +You can use `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` to set the S3 credentials. Other supported methods are listed in Rusoto's [ChainProvider](https://rusoto.github.io/rusoto/rusoto_credential/struct.ChainProvider.html). To connect to a public bucket anonymously (read only mode), the environment variable `SCCACHE_S3_PUBLIC` must be set to true, to prevent the default behavior of rusoto's [DefaultCredentialsProvider](https://rusoto.github.io/rusoto/rusoto_credential/struct.DefaultCredentialsProvider.html), which is to pass the error of ChainProvider. -If you need to override the default endpoint you can set `SCCACHE_ENDPOINT`. To connect to a minio storage for example you can set `SCCACHE_ENDPOINT=:`. If your endpoint requires TLS, set `SCCACHE_S3_USE_SSL=true`. +You can set the region of your bucket with one of the environment variables `AWS_DEFAULT_REGION`, `AWS_REGION` or `SCCACHE_REGION`. +If you need to override the default endpoint you can set `SCCACHE_ENDPOINT`. To connect to a minio storage for example you can set `SCCACHE_ENDPOINT=:`. +Optionally, the endpoint can start with `http://` or `https://` to force the protocol. By default, HTTPS will be used. You can also define a prefix that will be prepended to the keys of all cache objects created and read within the S3 bucket, effectively creating a scope. To do that use the `SCCACHE_S3_KEY_PREFIX` environment variable. This can be useful when sharing a bucket with another application. - ### Redis Set `SCCACHE_REDIS` to a [Redis](https://redis.io/) url in format `redis://[:@][:port][/]` to store the cache in a Redis instance. Redis can be configured as a LRU (least recently used) cache with a fixed maximum cache size. Set `maxmemory` and `maxmemory-policy` according to the [Redis documentation](https://redis.io/topics/lru-cache). The `allkeys-lru` policy which discards the *least recently accessed or modified* key fits well for the sccache use case. diff --git a/docs/Configuration.md b/docs/Configuration.md new file mode 100644 index 000000000..52bb0a638 --- /dev/null +++ b/docs/Configuration.md @@ -0,0 +1,98 @@ +# Available Configuration Options + +## file + +```toml +[dist] +# where to find the scheduler +scheduler_url = "http://1.2.3.4:10600" +# a set of prepackaged toolchains +toolchains = [] +# the maximum size of the toolchain cache in bytes +toolchain_cache_size = 5368709120 +cache_dir = "/home/user/.cache/sccache-dist-client" + +[dist.auth] +type = "token" +token = "secrettoken" + + +#[cache.azure] +# does not work as it appears + +[cache.disk] +dir = "/tmp/.cache/sccache" +size = 7516192768 # 7 GiBytes + +[cache.gcs] +# optional url +url = "..." +rw_mode = "READ_ONLY" +# rw_mode = "READ_WRITE" +cred_path = "/psst/secret/cred" +bucket = "bucket" + +[cache.memcached] +url = "..." + +[cache.redis] +url = "redis://user:passwd@1.2.3.4:6379/1" + +[cache.s3] +bucket = "name" +endpoint = "s3-us-east-1.amazonaws.com" +use_ssl = true +``` + +## env + +Whatever is set by a file based configuration, it is overruled by the env +configuration variables + +### misc + +* `SCCACHE_ALLOW_CORE_DUMPS` to enable core dumps by the server +* `SCCACHE_CONF` configuration file path +* `SCCACHE_CACHED_CONF` +* `SCCACHE_IDLE_TIMEOUT` how long the local daemon process waits for more client requests before exiting +* `SCCACHE_STARTUP_NOTIFY` specify a path to a socket which will be used for server completion notification +* `SCCACHE_MAX_FRAME_LENGTH` how much data can be transfered between client and server +* `SCCACHE_NO_DAEMON` set to `1` to disable putting the server to the background + +### cache configs + +#### disk + +* `SCCACHE_DIR` local on disk artifact cache directory +* `SCCACHE_CACHE_SIZE` maximum size of the local on disk cache i.e. `10G` + +#### s3 compatible + +* `SCCACHE_BUCKET` s3 bucket to be used +* `SCCACHE_ENDPOINT` s3 endpoint +* `SCCACHE_REGION` s3 region +* `SCCACHE_S3_USE_SSL` s3 endpoint requires TLS, set this to `true` + +The endpoint used then becomes `${SCCACHE_BUCKET}.s3-{SCCACHE_REGION}.amazonaws.com`. +If `SCCACHE_REGION` is undefined, it will default to `us-east-1`. + +#### redis + +* `SCCACHE_REDIS` full redis url, including auth and access token/passwd + +The full url appears then as `redis://user:passwd@1.2.3.4:6379/1`. + +#### memcached + +* `SCCACHE_MEMCACHED` memcached url + +#### gcs + +* `SCCACHE_GCS_BUCKET` +* `SCCACHE_GCS_CREDENTIALS_URL` +* `SCCACHE_GCS_KEY_PATH` +* `SCCACHE_GCS_RW_MODE` + +#### azure + +* `SCCACHE_AZURE_CONNECTION_STRING` diff --git a/lru-disk-cache/src/lib.rs b/lru-disk-cache/src/lib.rs index 399597aec..08487aec3 100644 --- a/lru-disk-cache/src/lib.rs +++ b/lru-disk-cache/src/lib.rs @@ -79,7 +79,7 @@ pub enum Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.to_string()) + write!(f, "{}", self) } } @@ -228,7 +228,7 @@ impl LruDiskCache { None => fs::metadata(path)?.len(), }; self.add_file(AddFile::RelPath(rel_path), size) - .or_else(|e| { + .map_err(|e| { error!( "Failed to insert file `{}`: {}", rel_path.to_string_lossy(), @@ -236,7 +236,7 @@ impl LruDiskCache { ); fs::remove_file(&self.rel_to_abs_path(rel_path)) .expect("Failed to remove file we just created!"); - Err(e) + e }) } diff --git a/src/azure/blobstore.rs b/src/azure/blobstore.rs index 9e647c220..c17560a43 100644 --- a/src/azure/blobstore.rs +++ b/src/azure/blobstore.rs @@ -1,3 +1,4 @@ +// Copyright 2020 Bernhard // Copyright 2018 Benjamin Bader // Copyright 2016 Mozilla Foundation // @@ -14,17 +15,14 @@ // limitations under the License. use crate::azure::credentials::*; -use futures::{Future, Stream}; -use hmac::{Hmac, Mac}; -use hyper::header::HeaderValue; -use hyper::Method; +use hmac::{Hmac, Mac, NewMac}; use hyperx::header; use md5::{Digest, Md5}; -use reqwest::r#async::{Client, Request}; +use reqwest::Url; +use reqwest::{header::HeaderValue, Client, Method, Request}; use sha2::Sha256; use std::fmt; use std::str::FromStr; -use url::Url; use crate::errors::*; use crate::util::HeadersExt; @@ -33,20 +31,20 @@ const BLOB_API_VERSION: &str = "2017-04-17"; fn hmac(data: &[u8], secret: &[u8]) -> Vec { let mut hmac = Hmac::::new_varkey(secret).expect("HMAC can take key of any size"); - hmac.input(data); - hmac.result().code().iter().copied().collect::>() + hmac.update(data); + hmac.finalize().into_bytes().as_slice().to_vec() } fn signature(to_sign: &str, secret: &str) -> String { let decoded_secret = base64::decode_config(secret.as_bytes(), base64::STANDARD).unwrap(); let sig = hmac(to_sign.as_bytes(), &decoded_secret); - base64::encode_config::>(&sig, base64::STANDARD) + base64::encode_config(&sig, base64::STANDARD) } fn md5(data: &[u8]) -> String { let mut digest = Md5::new(); - digest.input(data); - base64::encode_config(&digest.result(), base64::STANDARD) + digest.update(data); + base64::encode_config(&digest.finalize(), base64::STANDARD) } pub struct BlobContainer { @@ -72,7 +70,7 @@ impl BlobContainer { }) } - pub fn get(&self, key: &str, creds: &AzureCredentials) -> SFuture> { + pub async fn get(&self, key: &str, creds: &AzureCredentials) -> Result> { let url_string = format!("{}{}", self.url, key); let uri = Url::from_str(&url_string).unwrap(); let date = time::now_utc().rfc822().to_string(); @@ -89,10 +87,7 @@ impl BlobContainer { creds, ); - let uri_copy = uri.clone(); - let uri_second_copy = uri.clone(); - - let mut request = Request::new(Method::GET, uri); + let mut request = Request::new(Method::GET, uri.clone()); request.headers_mut().insert( "x-ms-date", HeaderValue::from_str(&date).expect("Date is an invalid header value"), @@ -107,46 +102,34 @@ impl BlobContainer { ); } - Box::new( - self.client - .execute(request) - .fwith_context(move || format!("failed GET: {}", uri_copy)) - .and_then(|res| { - if res.status().is_success() { - let content_length = res - .headers() - .get_hyperx::() - .map(|header::ContentLength(len)| len); - Ok((res.into_body(), content_length)) - } else { - Err(BadHttpStatusError(res.status()).into()) - } - }) - .and_then(|(body, content_length)| { - body.fold(Vec::new(), |mut body, chunk| { - body.extend_from_slice(&chunk); - Ok::<_, reqwest::Error>(body) - }) - .fcontext("failed to read HTTP body") - .and_then(move |bytes| { - if let Some(len) = content_length { - if len != bytes.len() as u64 { - bail!(format!( - "Bad HTTP body size read: {}, expected {}", - bytes.len(), - len - )); - } else { - info!("Read {} bytes from {}", bytes.len(), uri_second_copy); - } - } - Ok(bytes) - }) - }), - ) + let res = self + .client + .execute(request) + .await + .with_context(|| format!("failed GET: {}", &uri))?; + + let (bytes, content_length) = if res.status().is_success() { + let content_length = res.content_length(); + (res.bytes().await?, content_length) + } else { + return Err(BadHttpStatusError(res.status()).into()); + }; + + if let Some(len) = content_length { + if len != bytes.len() as u64 { + bail!(format!( + "Bad HTTP body size read: {}, expected {}", + bytes.len(), + len + )); + } else { + info!("Read {} bytes from {}", bytes.len(), &uri); + } + } + Ok(bytes.into_iter().collect()) } - pub fn put(&self, key: &str, content: Vec, creds: &AzureCredentials) -> SFuture<()> { + pub async fn put(&self, key: &str, content: Vec, creds: &AzureCredentials) -> Result<()> { let url_string = format!("{}{}", self.url, key); let uri = Url::from_str(&url_string).unwrap(); let date = time::now_utc().rfc822().to_string(); @@ -204,7 +187,7 @@ impl BlobContainer { *request.body_mut() = Some(content.into()); - Box::new(self.client.execute(request).then(|result| match result { + match self.client.execute(request).await { Ok(res) => { if res.status().is_success() { trace!("PUT succeeded"); @@ -218,7 +201,7 @@ impl BlobContainer { trace!("PUT failed with error: {:?}", e); Err(e.into()) } - })) + } } } @@ -271,7 +254,7 @@ fn compute_auth_header( fn canonicalize_resource(uri: &Url, account_name: &str) -> String { let mut canonical_resource = String::new(); - canonical_resource.push_str("/"); + canonical_resource.push('/'); canonical_resource.push_str(account_name); canonical_resource.push_str(uri.path()); @@ -283,7 +266,7 @@ fn canonicalize_resource(uri: &Url, account_name: &str) -> String { #[cfg(test)] mod test { use super::*; - use tokio_compat::runtime::current_thread::Runtime; + use tokio::runtime::Runtime; #[test] fn test_signing() { diff --git a/src/azure/credentials.rs b/src/azure/credentials.rs index 7b66d877a..5338f2e0b 100644 --- a/src/azure/credentials.rs +++ b/src/azure/credentials.rs @@ -42,7 +42,7 @@ impl AzureCredentials { AzureCredentials { blob_endpoint: endpoint, account_name: account_name.to_owned(), - account_key: account_key.to_owned(), + account_key, container_name, } } diff --git a/src/bin/sccache-dist/build.rs b/src/bin/sccache-dist/build.rs index 87f86d60b..69f4fc639 100644 --- a/src/bin/sccache-dist/build.rs +++ b/src/bin/sccache-dist/build.rs @@ -267,7 +267,7 @@ impl OverlayBuilder { crossbeam_utils::thread::scope(|scope| { scope - .spawn(|| { + .spawn(|_| { // Now mounted filesystems will be automatically unmounted when this thread dies // (and tmpfs filesystems will be completely destroyed) nix::sched::unshare(nix::sched::CloneFlags::CLONE_NEWNS) @@ -369,7 +369,7 @@ impl OverlayBuilder { .arg(cwd); for (k, v) in env_vars { - if k.contains("=") { + if k.contains('=') { warn!("Skipping environment variable: {:?}", k); continue; } @@ -416,6 +416,7 @@ impl OverlayBuilder { .join() .unwrap_or_else(|_e| Err(anyhow!("Build thread exited unsuccessfully"))) }) + .unwrap_or_else(|e| Err(anyhow!("Error joining build thread: {:?}", e))) } // Failing during cleanup is pretty unexpected, but we can still return the successful compile @@ -510,7 +511,7 @@ impl DockerBuilder { .args(&["ps", "-a", "--format", "{{.ID}} {{.Image}}"]) .check_stdout_trim() .context("Unable to list all Docker containers")?; - if containers != "" { + if !containers.is_empty() { let mut containers_to_rm = vec![]; for line in containers.split(|c| c == '\n') { let mut iter = line.splitn(2, ' '); @@ -540,7 +541,7 @@ impl DockerBuilder { .args(&["images", "--format", "{{.ID}} {{.Repository}}"]) .check_stdout_trim() .context("Failed to list all docker images")?; - if images != "" { + if !images.is_empty() { let mut images_to_rm = vec![]; for line in images.split(|c| c == '\n') { let mut iter = line.splitn(2, ' '); @@ -608,7 +609,7 @@ impl DockerBuilder { .context("Failed to run kill on all processes in container")?; let diff = docker_diff(&cid)?; - if diff != "" { + if !diff.is_empty() { let mut lastpath = None; for line in diff.split(|c| c == '\n') { let mut iter = line.splitn(2, ' '); @@ -640,7 +641,7 @@ impl DockerBuilder { continue; } } - lastpath = Some(changepath.clone()); + lastpath = Some(changepath); if let Err(e) = Command::new("docker") .args(&["exec", &cid, "/busybox", "rm", "-rf", changepath]) .check_run() @@ -652,7 +653,7 @@ impl DockerBuilder { let newdiff = docker_diff(&cid)?; // See note about changepath == "/tmp" above - if newdiff != "" && newdiff != "C /tmp" { + if !newdiff.is_empty() && newdiff != "C /tmp" { bail!( "Attempted to delete files, but container still has a diff: {:?}", newdiff @@ -803,7 +804,7 @@ impl DockerBuilder { let mut cmd = Command::new("docker"); cmd.arg("exec"); for (k, v) in env_vars { - if k.contains("=") { + if k.contains('=') { warn!("Skipping environment variable: {:?}", k); continue; } diff --git a/src/bin/sccache-dist/main.rs b/src/bin/sccache-dist/main.rs index cc36782e9..79a0a044f 100644 --- a/src/bin/sccache-dist/main.rs +++ b/src/bin/sccache-dist/main.rs @@ -1,30 +1,17 @@ -extern crate base64; +#![allow(clippy::complexity)] +#![deny(clippy::perf)] + #[macro_use] extern crate clap; -extern crate crossbeam_utils; -extern crate env_logger; -extern crate flate2; -extern crate hyperx; -extern crate jsonwebtoken as jwt; -extern crate libmount; #[macro_use] extern crate log; -extern crate lru_disk_cache; -extern crate nix; -extern crate openssl; -extern crate rand; -extern crate reqwest; -extern crate sccache; #[macro_use] extern crate serde_derive; -extern crate serde_json; -extern crate syslog; -extern crate tar; -extern crate void; use anyhow::{bail, Context, Error, Result}; use clap::{App, Arg, ArgMatches, SubCommand}; -use rand::RngCore; +use jsonwebtoken as jwt; +use rand::{rngs::OsRng, RngCore}; use sccache::config::{ scheduler as scheduler_config, server as server_config, INSECURE_DIST_CLIENT_TOKEN, }; @@ -89,7 +76,7 @@ fn main() { println!("sccache-dist: caused by: {}", e); } get_app().print_help().unwrap(); - println!(""); + println!(); 1 } }); @@ -262,10 +249,11 @@ fn create_jwt_server_token( header: &jwt::Header, key: &[u8], ) -> Result { - jwt::encode(&header, &ServerJwt { server_id }, key).map_err(Into::into) + let key = jwt::EncodingKey::from_secret(key); + jwt::encode(&header, &ServerJwt { server_id }, &key).map_err(Into::into) } fn dangerous_unsafe_extract_jwt_server_token(server_token: &str) -> Option { - jwt::dangerous_unsafe_decode::(&server_token) + jwt::dangerous_insecure_decode::(&server_token) .map(|res| res.claims.server_id) .ok() } @@ -274,7 +262,8 @@ fn check_jwt_server_token( key: &[u8], validation: &jwt::Validation, ) -> Option { - jwt::decode::(server_token, key, validation) + let key = jwt::DecodingKey::from_secret(key); + jwt::decode::(server_token, &key, validation) .map(|res| res.claims.server_id) .ok() } @@ -283,9 +272,7 @@ fn run(command: Command) -> Result { match command { Command::Auth(AuthSubcommand::Base64 { num_bytes }) => { let mut bytes = vec![0; num_bytes]; - let mut rng = - rand::OsRng::new().context("Failed to initialise a random number generator")?; - rng.fill_bytes(&mut bytes); + OsRng.fill_bytes(&mut bytes); // As long as it can be copied, it doesn't matter if this is base64 or hex etc println!("{}", base64::encode_config(&bytes, base64::URL_SAFE_NO_PAD)); Ok(0) @@ -319,12 +306,8 @@ fn run(command: Command) -> Result { issuer, jwks_url, } => Box::new( - token_check::ValidJWTCheck::new( - audience.to_owned(), - issuer.to_owned(), - &jwks_url, - ) - .context("Failed to create a checker for valid JWTs")?, + token_check::ValidJWTCheck::new(audience, issuer, &jwks_url) + .context("Failed to create a checker for valid JWTs")?, ), scheduler_config::ClientAuth::Mozilla { required_groups } => { Box::new(token_check::MozillaCheck::new(required_groups)) @@ -712,7 +695,7 @@ impl SchedulerIncoming for Scheduler { } } - if stale_jobs.len() > 0 { + if !stale_jobs.is_empty() { warn!( "The following stale jobs will be de-allocated: {:?}", stale_jobs @@ -941,6 +924,6 @@ impl ServerIncoming for Server { requester .do_update_job_state(job_id, JobState::Complete) .context("Updating job state failed")?; - return res; + res } } diff --git a/src/bin/sccache-dist/token_check.rs b/src/bin/sccache-dist/token_check.rs index 4d953bf2d..0ac2d01df 100644 --- a/src/bin/sccache-dist/token_check.rs +++ b/src/bin/sccache-dist/token_check.rs @@ -33,14 +33,15 @@ impl Jwk { .context("Failed to base64 decode n")?; let e = base64::decode_config(&self.e, base64::URL_SAFE) .context("Failed to base64 decode e")?; - let n_bn = openssl::bn::BigNum::from_slice(&n) - .context("Failed to create openssl bignum from n")?; - let e_bn = openssl::bn::BigNum::from_slice(&e) - .context("Failed to create openssl bignum from e")?; - let pubkey = openssl::rsa::Rsa::from_public_components(n_bn, e_bn) - .context("Failed to create pubkey from n and e")?; + + let n_bn = rsa::BigUint::from_bytes_be(&n); + let e_bn = rsa::BigUint::from_bytes_be(&e); + let pubkey = rsa::RSAPublicKey::new(n_bn, e_bn)?; + + let pubkey = rsa_export::RsaKey::new(pubkey); let der: Vec = pubkey - .public_key_to_der_pkcs1() + .as_pkcs1() + .map_err(|e| anyhow::anyhow!("{}", e)) .context("Failed to convert public key to der pkcs1")?; Ok(der) } @@ -77,7 +78,7 @@ const MOZ_USERINFO_ENDPOINT: &str = "https://auth.mozilla.auth0.com/userinfo"; // Mozilla-specific check by forwarding the token onto the auth0 userinfo endpoint pub struct MozillaCheck { auth_cache: Mutex>, // token, token_expiry - client: reqwest::Client, + client: reqwest::blocking::Client, required_groups: Vec, } @@ -96,7 +97,7 @@ impl MozillaCheck { pub fn new(required_groups: Vec) -> Self { Self { auth_cache: Mutex::new(HashMap::new()), - client: reqwest::Client::new(), + client: reqwest::blocking::Client::new(), required_groups, } } @@ -121,8 +122,8 @@ impl MozillaCheck { sub: String, } // We don't really do any validation here (just forwarding on) so it's ok to unsafely decode - let unsafe_token = - jwt::dangerous_unsafe_decode::(token).context("Unable to decode jwt")?; + let unsafe_token = jwt::dangerous_insecure_decode::(token) + .context("Unable to decode jwt")?; let user = unsafe_token.claims.sub; trace!("Validating token for user {} with mozilla", user); if UNIX_EPOCH + Duration::from_secs(unsafe_token.claims.exp) < SystemTime::now() { @@ -145,22 +146,18 @@ impl MozillaCheck { let header = hyperx::header::Authorization(hyperx::header::Bearer { token: token.to_owned(), }); - let mut res = self + let res = self .client .get(url.clone()) .set_header(header) .send() .context("Failed to make request to mozilla userinfo")?; + let status = res.status(); let res_text = res .text() .context("Failed to interpret response from mozilla userinfo as string")?; - if !res.status().is_success() { - bail!( - "JWT forwarded to {} returned {}: {}", - url, - res.status(), - res_text - ) + if !status.is_success() { + bail!("JWT forwarded to {} returned {}: {}", url, status, res_text) } // The API didn't return a HTTP error code, let's check the response @@ -241,7 +238,7 @@ fn test_auth_verify_check_mozilla_profile() { // Don't check a token is valid (it may not even be a JWT) just forward it to // an API and check for success pub struct ProxyTokenCheck { - client: reqwest::Client, + client: reqwest::blocking::Client, maybe_auth_cache: Option, Duration)>>, url: String, } @@ -265,7 +262,7 @@ impl ProxyTokenCheck { let maybe_auth_cache: Option, Duration)>> = cache_secs.map(|secs| Mutex::new((HashMap::new(), Duration::from_secs(secs)))); Self { - client: reqwest::Client::new(), + client: reqwest::blocking::Client::new(), maybe_auth_cache, url, } @@ -330,7 +327,8 @@ impl ClientAuthCheck for ValidJWTCheck { impl ValidJWTCheck { pub fn new(audience: String, issuer: String, jwks_url: &str) -> Result { - let mut res = reqwest::get(jwks_url).context("Failed to make request to JWKs url")?; + let res = + reqwest::blocking::get(jwks_url).context("Failed to make request to JWKs url")?; if !res.status().is_success() { bail!("Could not retrieve JWKs, HTTP error: {}", res.status()) } @@ -353,18 +351,65 @@ impl ValidJWTCheck { trace!("Validating JWT in scheduler"); // Prepare validation let kid = header.kid.context("No kid found")?; - let pkcs1 = self - .kid_to_pkcs1 - .get(&kid) - .context("kid not found in jwks")?; + let pkcs1 = jwt::DecodingKey::from_rsa_der( + self.kid_to_pkcs1 + .get(&kid) + .context("kid not found in jwks")?, + ); let mut validation = jwt::Validation::new(header.alg); - validation.set_audience(&self.audience); + validation.set_audience(&[&self.audience]); validation.iss = Some(self.issuer.clone()); #[derive(Deserialize)] struct Claims {} // Decode the JWT, discarding any claims - we just care about validity - let _tokendata = jwt::decode::(token, pkcs1, &validation) + let _tokendata = jwt::decode::(token, &pkcs1, &validation) .context("Unable to validate and decode jwt")?; Ok(()) } } + +#[cfg(all(test, feature = "vs_openssl"))] +mod tests { + use super::*; + + #[test] + fn der_repr() { + let n_be_bytes = rsa::BigUint::from(23757u32).to_bytes_be(); + let e_be_bytes = rsa::BigUint::from(65537u32).to_bytes_be(); + let n = base64::encode_config(n_be_bytes.as_slice(), base64::URL_SAFE); + let e = base64::encode_config(e_be_bytes.as_slice(), base64::URL_SAFE); + + let jwk = Jwk { + kty: "RSA".to_owned(), + kid: "XXX".to_owned(), + n, + e, + }; + + let expected = { + let n_bn = openssl::bn::BigNum::from_slice(&n_be_bytes) + .expect("Failed to create openssl bignum from n"); + let e_bn = openssl::bn::BigNum::from_slice(&e_be_bytes) + .expect("Failed to create openssl bignum from e"); + let pubkey = openssl::rsa::Rsa::from_public_components(n_bn, e_bn) + .expect("Failed to create pubkey from n and e"); + let der: Vec = pubkey + .public_key_to_der_pkcs1() + .expect("Failed to convert public key to der pkcs1"); + der + }; + let der = jwk.to_der_pkcs1().expect("Always able to encode."); + + let truth = openssl::rsa::Rsa::public_key_from_der_pkcs1(&der) + .expect("Openssl must be able to load pkcs#1 der key"); + let expected2 = truth + .public_key_to_der_pkcs1() + .expect("Must convert to der pkcs1"); + assert_eq!( + expected, expected2, + "Assumption that n and e are correct be slices failed" + ); + + assert_eq!(der, expected); + } +} diff --git a/src/cache/azure.rs b/src/cache/azure.rs index 63bae033f..757eedc90 100644 --- a/src/cache/azure.rs +++ b/src/cache/azure.rs @@ -16,15 +16,14 @@ use crate::azure::BlobContainer; use crate::azure::*; use crate::cache::{Cache, CacheRead, CacheWrite, Storage}; -use futures::future::Future; use std::io; -use std::rc::Rc; +use std::sync::Arc; use std::time::{Duration, Instant}; use crate::errors::*; pub struct AzureBlobCache { - container: Rc, + container: Arc, credentials: AzureCredentials, } @@ -44,53 +43,48 @@ impl AzureBlobCache { }; Ok(AzureBlobCache { - container: Rc::new(container), + container: Arc::new(container), credentials, }) } } +#[async_trait] impl Storage for AzureBlobCache { - fn get(&self, key: &str) -> SFuture { - Box::new( - self.container - .get(key, &self.credentials) - .then(|result| match result { - Ok(data) => { - let hit = CacheRead::from(io::Cursor::new(data))?; - Ok(Cache::Hit(hit)) - } - Err(e) => { - warn!("Got Azure error: {:?}", e); - Ok(Cache::Miss) - } - }), - ) + async fn get(&self, key: &str) -> Result { + match self.container.get(&key, &self.credentials).await { + Ok(data) => { + let hit = CacheRead::from(io::Cursor::new(data))?; + Ok(Cache::Hit(hit)) + } + Err(e) => { + warn!("Got Azure error: {:?}", e); + Ok(Cache::Miss) + } + } } - fn put(&self, key: &str, entry: CacheWrite) -> SFuture { + async fn put(&self, key: &str, entry: CacheWrite) -> Result { let start = Instant::now(); - let data = match entry.finish() { - Ok(data) => data, - Err(e) => return f_err(e), - }; + let data = entry.finish()?; - let response = self + let _ = self .container .put(key, data, &self.credentials) - .fcontext("Failed to put cache entry in Azure"); + .await + .context("Failed to put cache entry in Azure")?; - Box::new(response.map(move |_| start.elapsed())) + Ok(start.elapsed()) } fn location(&self) -> String { format!("Azure, container: {}", self.container) } - fn current_size(&self) -> SFuture> { - f_ok(None) + async fn current_size(&self) -> Result> { + Ok(None) } - fn max_size(&self) -> SFuture> { - f_ok(None) + async fn max_size(&self) -> Result> { + Ok(None) } } diff --git a/src/cache/cache.rs b/src/cache/cache.rs index c70d8e3dd..59bfc3d07 100644 --- a/src/cache/cache.rs +++ b/src/cache/cache.rs @@ -24,8 +24,7 @@ use crate::cache::redis::RedisCache; #[cfg(feature = "s3")] use crate::cache::s3::S3Cache; use crate::config::{self, CacheType, Config}; -use crate::util::SpawnExt; -use futures_03::executor::ThreadPool; + use std::fmt; use std::fs; #[cfg(feature = "gcs")] @@ -37,7 +36,6 @@ use std::time::Duration; use tempfile::NamedTempFile; use zip::write::FileOptions; use zip::{CompressionMethod, ZipArchive, ZipWriter}; -use zstd; use crate::errors::*; @@ -150,11 +148,11 @@ impl CacheRead { bytes } - pub fn extract_objects(mut self, objects: T, pool: &ThreadPool) -> SFuture<()> + pub async fn extract_objects(mut self, objects: T, pool: &tokio::runtime::Handle) -> Result<()> where T: IntoIterator + Send + Sync + 'static, { - Box::new(pool.spawn_fn(move || { + pool.spawn_blocking(move || { for (key, path) in objects { let dir = match path.parent() { Some(d) => d, @@ -171,7 +169,8 @@ impl CacheRead { } } Ok(()) - })) + }) + .await? } } @@ -189,11 +188,11 @@ impl CacheWrite { } /// Create a new cache entry populated with the contents of `objects`. - pub fn from_objects(objects: T, pool: &ThreadPool) -> SFuture + pub async fn from_objects(objects: T, pool: &tokio::runtime::Handle) -> Result where T: IntoIterator + Send + Sync + 'static, { - Box::new(pool.spawn_fn(move || -> Result<_> { + pool.spawn_blocking(move || { let mut entry = CacheWrite::new(); for (key, path) in objects { let mut f = fs::File::open(&path)?; @@ -203,7 +202,8 @@ impl CacheWrite { .with_context(|| format!("failed to put object `{:?}` in cache entry", path))?; } Ok(entry) - })) + }) + .await? } /// Add an object containing the contents of `from` to this cache entry at `name`. @@ -258,7 +258,8 @@ impl Default for CacheWrite { } /// An interface to cache storage. -pub trait Storage { +#[async_trait] +pub trait Storage: Send { /// Get a cache entry by `key`. /// /// If an error occurs, this method should return a `Cache::Error`. @@ -266,27 +267,27 @@ pub trait Storage { /// it should return a `Cache::Miss`. /// If the entry is successfully found in the cache, it should /// return a `Cache::Hit`. - fn get(&self, key: &str) -> SFuture; + async fn get(&self, key: &str) -> Result; /// Put `entry` in the cache under `key`. /// /// Returns a `Future` that will provide the result or error when the put is /// finished. - fn put(&self, key: &str, entry: CacheWrite) -> SFuture; + async fn put(&self, key: &str, entry: CacheWrite) -> Result; /// Get the storage location. fn location(&self) -> String; /// Get the current storage usage, if applicable. - fn current_size(&self) -> SFuture>; + async fn current_size(&self) -> Result>; /// Get the maximum storage size, if applicable. - fn max_size(&self) -> SFuture>; + async fn max_size(&self) -> Result>; } /// Get a suitable `Storage` implementation from configuration. #[allow(clippy::cognitive_complexity)] // TODO simplify! -pub fn storage_from_config(config: &Config, pool: &ThreadPool) -> Arc { +pub fn storage_from_config(config: &Config, pool: &tokio::runtime::Handle) -> Arc { for cache_type in config.caches.iter() { match *cache_type { CacheType::Azure(config::AzureCacheConfig) => { @@ -384,9 +385,21 @@ pub fn storage_from_config(config: &Config, pool: &ThreadPool) -> Arc { - debug!("Trying S3Cache({}, {})", c.bucket, c.endpoint); + debug!( + "Trying S3Cache({}, {}, {}, Anonymous {})", + c.bucket, + c.region.as_deref().unwrap_or("default region"), + c.endpoint.as_deref().unwrap_or("default endpoint"), + c.public, + ); #[cfg(feature = "s3")] - match S3Cache::new(&c.bucket, &c.endpoint, c.use_ssl, &c.key_prefix) { + match S3Cache::new( + &c.bucket, + c.region.as_deref(), + c.endpoint.as_deref(), + c.key_prefix.as_deref().unwrap_or(""), + c.public, + ) { Ok(s) => { trace!("Using S3Cache"); return Arc::new(s); diff --git a/src/cache/disk.rs b/src/cache/disk.rs index 6a490621d..1c958d56e 100644 --- a/src/cache/disk.rs +++ b/src/cache/disk.rs @@ -13,8 +13,6 @@ // limitations under the License. use crate::cache::{Cache, CacheRead, CacheWrite, Storage}; -use crate::util::SpawnExt; -use futures_03::executor::ThreadPool; use lru_disk_cache::Error as LruError; use lru_disk_cache::LruDiskCache; use std::ffi::OsStr; @@ -30,12 +28,12 @@ pub struct DiskCache { /// `LruDiskCache` does all the real work here. lru: Arc>, /// Thread pool to execute disk I/O - pool: ThreadPool, + pool: tokio::runtime::Handle, } impl DiskCache { /// Create a new `DiskCache` rooted at `root`, with `max_size` as the maximum cache size on-disk, in bytes. - pub fn new>(root: &T, max_size: u64, pool: &ThreadPool) -> DiskCache { + pub fn new>(root: &T, max_size: u64, pool: &tokio::runtime::Handle) -> DiskCache { DiskCache { //TODO: change this function to return a Result lru: Arc::new(Mutex::new( @@ -51,15 +49,17 @@ fn make_key_path(key: &str) -> PathBuf { Path::new(&key[0..1]).join(&key[1..2]).join(key) } +#[async_trait] impl Storage for DiskCache { - fn get(&self, key: &str) -> SFuture { + async fn get(&self, key: &str) -> Result { trace!("DiskCache::get({})", key); let path = make_key_path(key); let lru = self.lru.clone(); let key = key.to_owned(); - Box::new(self.pool.spawn_fn(move || { + + self.pool.spawn_blocking(move || { let mut lru = lru.lock().unwrap(); - let f = match lru.get(&path) { + let io = match lru.get(&path) { Ok(f) => f, Err(LruError::FileNotInCache) => { trace!("DiskCache::get({}): FileNotInCache", key); @@ -71,33 +71,36 @@ impl Storage for DiskCache { } Err(_) => unreachable!(), }; - let hit = CacheRead::from(f)?; + let hit = CacheRead::from(io)?; Ok(Cache::Hit(hit)) - })) + }) + .await? } - fn put(&self, key: &str, entry: CacheWrite) -> SFuture { + async fn put(&self, key: &str, entry: CacheWrite) -> Result { // We should probably do this on a background thread if we're going to buffer // everything in memory... trace!("DiskCache::finish_put({})", key); let lru = self.lru.clone(); let key = make_key_path(key); - Box::new(self.pool.spawn_fn(move || { + + self.pool.spawn_blocking(move || { let start = Instant::now(); let v = entry.finish()?; lru.lock().unwrap().insert_bytes(key, &v)?; Ok(start.elapsed()) - })) + }) + .await? } fn location(&self) -> String { format!("Local disk: {:?}", self.lru.lock().unwrap().path()) } - fn current_size(&self) -> SFuture> { - f_ok(Some(self.lru.lock().unwrap().size())) + async fn current_size(&self) -> Result> { + Ok(Some(self.lru.lock().unwrap().size())) } - fn max_size(&self) -> SFuture> { - f_ok(Some(self.lru.lock().unwrap().capacity())) + async fn max_size(&self) -> Result> { + Ok(Some(self.lru.lock().unwrap().capacity())) } } diff --git a/src/cache/gcs.rs b/src/cache/gcs.rs index 04a5976fd..63c213832 100644 --- a/src/cache/gcs.rs +++ b/src/cache/gcs.rs @@ -13,26 +13,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::{cell::RefCell, fmt, io, rc::Rc, time}; - +use std::{fmt, io, sync::Arc, time}; use crate::{ cache::{Cache, CacheRead, CacheWrite, Storage}, errors::*, util::HeadersExt, }; -use futures::{ - future::{self, Shared}, - Async, Future, Stream, -}; use hyper::Method; use hyperx::header::{Authorization, Bearer, ContentLength, ContentType}; -use reqwest::r#async::{Client, Request}; +use reqwest::{Client, Request}; use serde::de; use url::{ form_urlencoded, percent_encoding::{percent_encode, PATH_SEGMENT_ENCODE_SET, QUERY_ENCODE_SET}, }; + /// GCS bucket struct Bucket { name: String, @@ -52,7 +48,11 @@ impl Bucket { Ok(Bucket { name, client }) } - fn get(&self, key: &str, cred_provider: &Option) -> SFuture> { + async fn get( + &self, + key: &str, + cred_provider: &Option, + ) -> Result> { let url = format!( "https://www.googleapis.com/download/storage/v1/b/{}/o/{}?alt=media", percent_encode(self.name.as_bytes(), PATH_SEGMENT_ENCODE_SET), @@ -61,95 +61,83 @@ impl Bucket { let client = self.client.clone(); - let creds_opt_future = if let Some(ref cred_provider) = *cred_provider { - future::Either::A( - cred_provider - .credentials(&self.client) - .map_err(|err| { - warn!("Error getting credentials: {:?}", err); - err - }) - .map(Some), - ) + let creds_opt = if let Some(ref cred_provider) = *cred_provider { + cred_provider + .credentials(&self.client) + .await + .map_err(|err| { + warn!("Error getting credentials: {:?}", err); + err + }) + .map(Some)? } else { - future::Either::B(future::ok(None)) + None }; - Box::new(creds_opt_future.and_then(move |creds_opt| { - let mut request = Request::new(Method::GET, url.parse().unwrap()); - if let Some(creds) = creds_opt { - request - .headers_mut() - .set(Authorization(Bearer { token: creds.token })); - } - client - .execute(request) - .fwith_context(move || format!("failed GET: {}", url)) - .and_then(|res| { - if res.status().is_success() { - Ok(res.into_body()) - } else { - Err(BadHttpStatusError(res.status()).into()) - } - }) - .and_then(|body| { - body.fold(Vec::new(), |mut body, chunk| { - body.extend_from_slice(&chunk); - Ok::<_, reqwest::Error>(body) - }) - .fcontext("failed to read HTTP body") - }) - })) + let mut request = Request::new(Method::GET, url.parse().unwrap()); + if let Some(creds) = creds_opt { + request + .headers_mut() + .set(Authorization(Bearer { token: creds.token })); + } + let res = client.execute(request).await + .with_context(|| format!("failed GET: {}", url))?; + if res.status().is_success() { + let bytes = res.bytes().await.context("failed to read HTTP body")?; + Ok(bytes.into_iter().collect()) + } else { + Err(BadHttpStatusError(res.status()).into()) + } } - fn put( + async fn put( &self, key: &str, content: Vec, cred_provider: &Option, - ) -> SFuture<()> { + ) -> Result<()> { let url = format!( "https://www.googleapis.com/upload/storage/v1/b/{}/o?name={}&uploadType=media", percent_encode(self.name.as_bytes(), PATH_SEGMENT_ENCODE_SET), percent_encode(key.as_bytes(), QUERY_ENCODE_SET) ); + let url = url.parse().unwrap(); let client = self.client.clone(); - let creds_opt_future = if let Some(ref cred_provider) = cred_provider { - future::Either::A(cred_provider.credentials(&self.client).map(Some)) + let creds_opt = if let Some(ref cred_provider) = cred_provider { + let val = cred_provider.credentials(&self.client).await?; + Some(val) } else { - future::Either::B(future::ok(None)) + None }; - Box::new(creds_opt_future.and_then(move |creds_opt| { - let mut request = Request::new(Method::POST, url.parse().unwrap()); - { - let headers = request.headers_mut(); - if let Some(creds) = creds_opt { - headers.set(Authorization(Bearer { token: creds.token })); - } - headers.set(ContentType::octet_stream()); - headers.set(ContentLength(content.len() as u64)); + let mut request = Request::new(Method::POST, url); + { + let headers = request.headers_mut(); + if let Some(creds) = creds_opt { + headers.set(Authorization(Bearer { token: creds.token })); } - *request.body_mut() = Some(content.into()); - - client.execute(request).then(|result| match result { - Ok(res) => { - if res.status().is_success() { - trace!("PUT succeeded"); - Ok(()) - } else { - trace!("PUT failed with HTTP status: {}", res.status()); - Err(BadHttpStatusError(res.status()).into()) - } - } - Err(e) => { - trace!("PUT failed with error: {:?}", e); - Err(e.into()) + headers.set(ContentType::octet_stream()); + headers.set(ContentLength(content.len() as u64)); + } + *request.body_mut() = Some(content.into()); + + match client.execute(request).await { + Ok(res) => { + if res.status().is_success() { + trace!("PUT succeeded"); + Ok(()) + } else { + trace!("PUT failed with HTTP status: {}", res.status()); + Err(BadHttpStatusError(res.status()).into()) } - }) - })) + } + Err(e) => { + trace!("PUT failed with error: {:?}", e); + Err(e.into()) + } + } } } @@ -160,7 +148,7 @@ impl Bucket { pub struct GCSCredentialProvider { rw_mode: RWMode, sa_info: ServiceAccountInfo, - cached_credentials: RefCell>>>, + cached_credentials: futures_locks::Mutex>, } /// ServiceAccountInfo either contains a URL to fetch the oauth token @@ -298,8 +286,8 @@ fn sign_rsa( key: &[u8], alg: &'static dyn signature::RsaEncoding, ) -> Result { - let key_pair = signature::RsaKeyPair::from_pkcs8(untrusted::Input::from(key)) - .context("failed to deserialize rsa key")?; + let key_pair = + signature::RsaKeyPair::from_pkcs8(key).context("failed to deserialize rsa key")?; let mut signature = vec![0; key_pair.public_modulus_len()]; let rng = ring::rand::SystemRandom::new(); @@ -324,7 +312,7 @@ impl GCSCredentialProvider { GCSCredentialProvider { rw_mode, sa_info, - cached_credentials: RefCell::new(None), + cached_credentials: futures_locks::Mutex::new(None), } } @@ -338,7 +326,7 @@ impl GCSCredentialProvider { RWMode::ReadWrite => "https://www.googleapis.com/auth/devstorage.read_write", }; - Ok(encode( + encode( &Header { typ: "JWT", alg: "RS256", @@ -352,132 +340,94 @@ impl GCSCredentialProvider { }, &sa_key.private_key, ) - .unwrap()) } - fn request_new_token( + async fn request_new_token( &self, sa_key: &ServiceAccountKey, client: &Client, - ) -> SFuture { - let client = client.clone(); + ) -> Result { let expires_at = chrono::offset::Utc::now() + chrono::Duration::minutes(59); - let auth_jwt = self.auth_request_jwt(sa_key, &expires_at); + let auth_jwt = self.auth_request_jwt(sa_key, &expires_at)?; let url = sa_key.token_uri.clone(); - // Request credentials - Box::new( - future::result(auth_jwt) - .and_then(move |auth_jwt| { - let params = form_urlencoded::Serializer::new(String::new()) - .append_pair("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer") - .append_pair("assertion", &auth_jwt) - .finish(); - - let mut request = Request::new(Method::POST, url.parse().unwrap()); - { - let headers = request.headers_mut(); - headers.set(ContentType::form_url_encoded()); - headers.set(ContentLength(params.len() as u64)); - } - *request.body_mut() = Some(params.into()); + let params = form_urlencoded::Serializer::new(String::new()) + .append_pair("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer") + .append_pair("assertion", &auth_jwt) + .finish(); - client.execute(request).map_err(Into::into) - }) - .and_then(move |res| { - if res.status().is_success() { - Ok(res.into_body()) - } else { - Err(BadHttpStatusError(res.status()).into()) - } - }) - .and_then(move |body| { - // Concatenate body chunks into a single Vec - body.fold(Vec::new(), |mut body, chunk| { - body.extend_from_slice(&chunk); - Ok::<_, reqwest::Error>(body) - }) - .fcontext("failed to read HTTP body") - }) - .and_then(move |body| { - // Convert body to string and parse the token out of the response - let body_str = String::from_utf8(body)?; - let token_msg: TokenMsg = serde_json::from_str(&body_str)?; - - Ok(GCSCredential { - token: token_msg.access_token, - expiration_time: expires_at, - }) - }), - ) + let mut request = Request::new(Method::POST, url.parse().unwrap()); + { + let headers = request.headers_mut(); + headers.set(ContentType::form_url_encoded()); + headers.set(ContentLength(params.len() as u64)); + } + *request.body_mut() = Some(params.into()); + + let res = client.execute(request).await?; + + let token_msg = if res.status().is_success() { + let token_msg = res.json::().await?; + Ok(token_msg) + } else { + Err(BadHttpStatusError(res.status())) + }?; + + Ok(GCSCredential { + token: token_msg.access_token, + expiration_time: expires_at, + }) } - fn request_new_token_from_tcauth(&self, url: &str, client: &Client) -> SFuture { - Box::new( - client - .get(url) - .send() - .map_err(Into::into) - .and_then(move |res| { - if res.status().is_success() { - Ok(res.into_body()) - } else { - Err(BadHttpStatusError(res.status()).into()) - } - }) - .and_then(move |body| { - body.fold(Vec::new(), |mut body, chunk| { - body.extend_from_slice(&chunk); - Ok::<_, reqwest::Error>(body) - }) - .fcontext("failed to read HTTP body") - }) - .and_then(move |body| { - let body_str = String::from_utf8(body)?; - let resp: AuthResponse = serde_json::from_str(&body_str)?; - Ok(GCSCredential { - token: resp.access_token, - expiration_time: resp.expire_time.parse()?, - }) - }), - ) + async fn request_new_token_from_tcauth( + &self, + url: &str, + client: &Client, + ) -> Result { + let res = client.get(url).send().await?; + + if res.status().is_success() { + let resp = res.json::().await?; + Ok(GCSCredential { + token: resp.access_token, + expiration_time: resp.expire_time.parse()?, + }) + } else { + Err(BadHttpStatusError(res.status()).into()) + } } - pub fn credentials(&self, client: &Client) -> SFuture { - let mut future_opt = self.cached_credentials.borrow_mut(); + pub async fn credentials(&self, client: &Client) -> Result { + // NOTE: Only this function is responsible for managing credentials and + // its cache; make sure we hold the lock across the yield points + let mut cache = self.cached_credentials.lock().await; - let needs_refresh = match Option::as_mut(&mut future_opt).map(|f| f.poll()) { - None => true, - Some(Ok(Async::Ready(ref creds))) => creds.expiration_time < chrono::offset::Utc::now(), - _ => false, - }; + match *cache { + Some(ref creds) if creds.expiration_time >= chrono::offset::Utc::now() => { + Ok(creds.clone()) + } + _ => { + let new_creds = match self.sa_info { + ServiceAccountInfo::AccountKey(ref sa_key) => { + self.request_new_token(sa_key, client).await + } + ServiceAccountInfo::URL(ref url) => { + self.request_new_token_from_tcauth(url, client).await + } + }?; - if needs_refresh { - let credentials = match self.sa_info { - ServiceAccountInfo::AccountKey(ref sa_key) => { - self.request_new_token(sa_key, client) - } - ServiceAccountInfo::URL(ref url) => self.request_new_token_from_tcauth(url, client), - }; - *future_opt = Some(credentials.shared()); - }; + *cache = Some(new_creds.clone()); - Box::new( - Option::as_mut(&mut future_opt) - .unwrap() - .clone() - .then(|result| match result { - Ok(e) => Ok((*e).clone()), - Err(e) => Err(anyhow!(e.to_string())), - }), - ) + Ok(new_creds) + } + } } } /// A cache that stores entries in Google Cloud Storage pub struct GCSCache { /// The GCS bucket - bucket: Rc, + bucket: Arc, /// Credential provider for GCS credential_provider: Option, /// Read-only or not @@ -492,86 +442,88 @@ impl GCSCache { rw_mode: RWMode, ) -> Result { Ok(GCSCache { - bucket: Rc::new(Bucket::new(bucket)?), + bucket: Arc::new(Bucket::new(bucket)?), rw_mode, credential_provider, }) } } +#[async_trait] impl Storage for GCSCache { - fn get(&self, key: &str) -> SFuture { - Box::new( - self.bucket - .get(&key, &self.credential_provider) - .then(|result| match result { - Ok(data) => { - let hit = CacheRead::from(io::Cursor::new(data))?; - Ok(Cache::Hit(hit)) - } - Err(e) => { - warn!("Got GCS error: {:?}", e); - Ok(Cache::Miss) - } - }), - ) + async fn get(&self, key: &str) -> Result { + match self.bucket.get(&key, &self.credential_provider).await { + Ok(data) => { + let hit = CacheRead::from(io::Cursor::new(data))?; + Ok(Cache::Hit(hit)) + } + Err(e) => { + warn!("Got GCS error: {:?}", e); + Ok(Cache::Miss) + } + } } - fn put(&self, key: &str, entry: CacheWrite) -> SFuture { + async fn put(&self, key: &str, entry: CacheWrite) -> Result { if let RWMode::ReadOnly = self.rw_mode { - return Box::new(future::ok(time::Duration::new(0, 0))); + return Ok(time::Duration::new(0, 0)); } let start = time::Instant::now(); - let data = match entry.finish() { - Ok(data) => data, - Err(e) => return Box::new(future::err(e)), - }; + let data = entry.finish()?; + let bucket = self.bucket.clone(); - let response = bucket + let _ = bucket .put(&key, data, &self.credential_provider) - .fcontext("failed to put cache entry in GCS"); + .await + .context("failed to put cache entry in GCS")?; - Box::new(response.map(move |_| start.elapsed())) + Ok(start.elapsed()) } fn location(&self) -> String { format!("GCS, bucket: {}", self.bucket) } - fn current_size(&self) -> SFuture> { - Box::new(future::ok(None)) + async fn current_size(&self) -> Result> { + Ok(None) } - fn max_size(&self) -> SFuture> { - Box::new(future::ok(None)) + + async fn max_size(&self) -> Result> { + Ok(None) } } -#[test] -fn test_gcs_credential_provider() { +#[tokio::test] +async fn test_gcs_credential_provider() { + use futures::{FutureExt, TryFutureExt}; + use std::convert::Infallible; + const EXPIRE_TIME: &str = "3000-01-01T00:00:00.0Z"; - let addr = ([127, 0, 0, 1], 3000).into(); - let make_service = || { - hyper::service::service_fn_ok(|_| { + let addr = ([127, 0, 0, 1], 23535).into(); + let make_service = + hyper::service::make_service_fn(|_socket| async { + Ok::<_, Infallible>(hyper::service::service_fn(|_request| async { let token = serde_json::json!({ "accessToken": "1234567890", "expireTime": EXPIRE_TIME, }); - hyper::Response::new(hyper::Body::from(token.to_string())) - }) - }; + Ok::<_, Infallible>(hyper::Response::new(hyper::Body::from(token.to_string()))) + })) + }); + let server = hyper::Server::bind(&addr).serve(make_service); let credential_provider = GCSCredentialProvider::new( RWMode::ReadWrite, - ServiceAccountInfo::URL("http://127.0.0.1:3000/".to_string()), + ServiceAccountInfo::URL(format!("http://{}/", addr)), ); let client = Client::new(); let cred_fut = credential_provider .credentials(&client) - .map(move |credential| { + .map_ok(move |credential| { assert_eq!(credential.token, "1234567890"); assert_eq!( credential.expiration_time.timestamp(), @@ -583,5 +535,5 @@ fn test_gcs_credential_provider() { }) .map_err(move |err| panic!(err.to_string())); - server.with_graceful_shutdown(cred_fut); + let _ = server.with_graceful_shutdown(cred_fut.map(drop)).await; } diff --git a/src/cache/memcached.rs b/src/cache/memcached.rs index 7ab0ad458..51290c7a5 100644 --- a/src/cache/memcached.rs +++ b/src/cache/memcached.rs @@ -15,8 +15,7 @@ use crate::cache::{Cache, CacheRead, CacheWrite, Storage}; use crate::errors::*; -use crate::util::SpawnExt; -use futures_03::executor::ThreadPool; + use memcached::client::Client; use memcached::proto::NoReplyOperation; use memcached::proto::Operation; @@ -32,11 +31,11 @@ thread_local! { #[derive(Clone)] pub struct MemcachedCache { url: String, - pool: ThreadPool, + pool: tokio::runtime::Handle, } impl MemcachedCache { - pub fn new(url: &str, pool: &ThreadPool) -> Result { + pub fn new(url: &str, pool: &tokio::runtime::Handle) -> Result { Ok(MemcachedCache { url: url.to_owned(), pool: pool.clone(), @@ -66,36 +65,41 @@ impl MemcachedCache { } } +#[async_trait] impl Storage for MemcachedCache { - fn get(&self, key: &str) -> SFuture { + async fn get(&self, key: &str) -> Result { let key = key.to_owned(); let me = self.clone(); - Box::new(self.pool.spawn_fn(move || { + + self.pool.spawn_blocking(move || { me.exec(|c| c.get(&key.as_bytes())) - .map(|(d, _)| CacheRead::from(Cursor::new(d)).map(Cache::Hit)) - .unwrap_or(Ok(Cache::Miss)) - })) + .map(|(d, _)| CacheRead::from(Cursor::new(d)).map(Cache::Hit)) + .unwrap_or(Ok(Cache::Miss)) + }) + .await? } - fn put(&self, key: &str, entry: CacheWrite) -> SFuture { + async fn put(&self, key: &str, entry: CacheWrite) -> Result { let key = key.to_owned(); let me = self.clone(); - Box::new(self.pool.spawn_fn(move || { + + self.pool.spawn_blocking(move || { let start = Instant::now(); let d = entry.finish()?; me.exec(|c| c.set_noreply(&key.as_bytes(), &d, 0, 0))?; Ok(start.elapsed()) - })) + }) + .await? } fn location(&self) -> String { format!("Memcached: {}", self.url) } - fn current_size(&self) -> SFuture> { - f_ok(None) + async fn current_size(&self) -> Result> { + Ok(None) } - fn max_size(&self) -> SFuture> { - f_ok(None) + async fn max_size(&self) -> Result> { + Ok(None) } } diff --git a/src/cache/redis.rs b/src/cache/redis.rs index 2a2a49c20..6be3c42da 100644 --- a/src/cache/redis.rs +++ b/src/cache/redis.rs @@ -15,7 +15,6 @@ use crate::cache::{Cache, CacheRead, CacheWrite, Storage}; use crate::errors::*; -use futures_03::prelude::*; use redis::aio::Connection; use redis::{cmd, Client, InfoDict}; use std::collections::HashMap; @@ -39,44 +38,31 @@ impl RedisCache { } /// Returns a connection with configured read and write timeouts. - async fn connect(self) -> Result { + async fn connect(&self) -> Result { Ok(self.client.get_async_connection().await?) } } +#[async_trait] impl Storage for RedisCache { /// Open a connection and query for a key. - fn get(&self, key: &str) -> SFuture { - let key = key.to_owned(); - let me = self.clone(); - Box::new( - Box::pin(async move { - let mut c = me.connect().await?; - let d: Vec = cmd("GET").arg(key).query_async(&mut c).await?; - if d.is_empty() { - Ok(Cache::Miss) - } else { - CacheRead::from(Cursor::new(d)).map(Cache::Hit) - } - }) - .compat(), - ) + async fn get(&self, key: &str) -> Result { + let mut c = self.connect().await?; + let d: Vec = cmd("GET").arg(key).query_async(&mut c).await?; + if d.is_empty() { + Ok(Cache::Miss) + } else { + CacheRead::from(Cursor::new(d)).map(Cache::Hit) + } } /// Open a connection and store a object in the cache. - fn put(&self, key: &str, entry: CacheWrite) -> SFuture { - let key = key.to_owned(); - let me = self.clone(); + async fn put(&self, key: &str, entry: CacheWrite) -> Result { let start = Instant::now(); - Box::new( - Box::pin(async move { - let mut c = me.connect().await?; - let d = entry.finish()?; - cmd("SET").arg(key).arg(d).query_async(&mut c).await?; - Ok(start.elapsed()) - }) - .compat(), - ) + let mut c = self.connect().await?; + let d = entry.finish()?; + cmd("SET").arg(key).arg(d).query_async(&mut c).await?; + Ok(start.elapsed()) } /// Returns the cache location. @@ -86,35 +72,23 @@ impl Storage for RedisCache { /// Returns the current cache size. This value is aquired via /// the Redis INFO command (used_memory). - fn current_size(&self) -> SFuture> { - let me = self.clone(); // TODO Remove clone - Box::new( - Box::pin(async move { - let mut c = me.connect().await?; - let v: InfoDict = cmd("INFO").query_async(&mut c).await?; - Ok(v.get("used_memory")) - }) - .compat(), - ) + async fn current_size(&self) -> Result> { + let mut c = self.connect().await?; + let v: InfoDict = cmd("INFO").query_async(&mut c).await?; + Ok(v.get("used_memory")) } /// Returns the maximum cache size. This value is read via /// the Redis CONFIG command (maxmemory). If the server has no /// configured limit, the result is None. - fn max_size(&self) -> SFuture> { - let me = self.clone(); // TODO Remove clone - Box::new( - Box::pin(async move { - let mut c = me.connect().await?; - let h: HashMap = cmd("CONFIG") - .arg("GET") - .arg("maxmemory") - .query_async(&mut c) - .await?; - Ok(h.get("maxmemory") - .and_then(|&s| if s != 0 { Some(s as u64) } else { None })) - }) - .compat(), - ) + async fn max_size(&self) -> Result> { + let mut c = self.connect().await?; + let h: HashMap = cmd("CONFIG") + .arg("GET") + .arg("maxmemory") + .query_async(&mut c) + .await?; + Ok(h.get("maxmemory") + .and_then(|&s| if s != 0 { Some(s as u64) } else { None })) } } diff --git a/src/cache/s3.rs b/src/cache/s3.rs index b08fcee9e..da56f5f3d 100644 --- a/src/cache/s3.rs +++ b/src/cache/s3.rs @@ -13,52 +13,126 @@ // limitations under the License. use crate::cache::{Cache, CacheRead, CacheWrite, Storage}; -use crate::simples3::{ - AutoRefreshingProvider, Bucket, ChainProvider, ProfileProvider, ProvideAwsCredentials, Ssl, -}; +use crate::errors::*; use directories::UserDirs; -use futures::future; -use futures::future::Future; +use hyper_rustls; +use hyperx::header::CacheDirective; +use rusoto_core::{ + self, + credential::{AutoRefreshingProvider, ChainProvider, ProfileProvider}, + Region, +}; +use rusoto_s3::{GetObjectOutput, GetObjectRequest, PutObjectRequest, S3Client, S3}; use std::io; -use std::rc::Rc; +use std::str::FromStr; use std::time::{Duration, Instant}; - -use crate::errors::*; +use tokio::io::AsyncReadExt as _; /// A cache that stores entries in Amazon S3. pub struct S3Cache { - /// The S3 bucket. - bucket: Rc, - /// Credentials provider. - provider: AutoRefreshingProvider, + /// The name of the bucket. + bucket_name: String, + /// The S3 client to be used for the Get and Put requests. + client: S3Client, /// Prefix to be used for bucket keys. key_prefix: String, } +// TODO create a custom credential provider that also reads +// TODO `AWS_SESSION_TOKEN`, `AWS_ACCESS_KEY_ID` besides the config vars. + impl S3Cache { /// Create a new `S3Cache` storing data in `bucket`. - pub fn new(bucket: &str, endpoint: &str, use_ssl: bool, key_prefix: &str) -> Result { + /// TODO: Handle custom region + pub fn new( + bucket: &str, + region: Option<&str>, + endpoint: Option<&str>, + key_prefix: &str, + public: bool, + ) -> Result { let user_dirs = UserDirs::new().context("Couldn't get user directories")?; let home = user_dirs.home_dir(); - let profile_providers = vec![ - ProfileProvider::with_configuration(home.join(".aws").join("credentials"), "default"), - //TODO: this is hacky, this is where our mac builders store their - // credentials. We should either match what boto does more directly - // or make those builders put their credentials in ~/.aws/credentials - ProfileProvider::with_configuration(home.join(".boto"), "Credentials"), - ]; + let profile_provider = + ProfileProvider::with_configuration(home.join(".aws").join("credentials"), "default") + // //TODO: this is hacky, this is where our mac builders store their + // // credentials. We should either match what boto does more directly + // // or make those builders put their credentials in ~/.aws/credentials + // ProfileProvider::with_configuration(home.join(".boto"), "Credentials"), + ; let provider = - AutoRefreshingProvider::new(ChainProvider::with_profile_providers(profile_providers)); - let ssl_mode = if use_ssl { Ssl::Yes } else { Ssl::No }; - let bucket = Rc::new(Bucket::new(bucket, endpoint, ssl_mode)?); + AutoRefreshingProvider::new(ChainProvider::with_profile_provider(profile_provider))?; + let bucket_name = bucket.to_owned(); + let region = match endpoint { + Some(endpoint) => Region::Custom { + name: region + .map(ToOwned::to_owned) + .unwrap_or(Region::default().name().to_owned()), + endpoint: endpoint.to_owned(), + }, + None => region + .map(FromStr::from_str) + .unwrap_or_else(|| Ok(Region::default()))?, + }; + + // TODO currently only https works with public, TODO + let client = if endpoint + .filter(|endpoint| endpoint.starts_with("https")) + .is_some() + { + let connector = hyper_rustls::HttpsConnector::new(); + // let client = hyper::client::Client::builder().build(connector); + let client = rusoto_core::HttpClient::from_connector(connector); + let client = if public { + rusoto_core::Client::new_not_signing(client) + } else { + rusoto_core::Client::new_with(provider, client) + }; + S3Client::new_with_client(client, region) + } else { + S3Client::new(region) + }; + Ok(S3Cache { - bucket, - provider, + bucket_name, + client, key_prefix: key_prefix.to_owned(), }) } + async fn get_object(client: S3Client, request: GetObjectRequest) -> Result { + let result = client.get_object(request).await; + match result { + Ok(output) => Self::read_object_output(output).await, + Err(rusoto_core::RusotoError::Service(rusoto_s3::GetObjectError::NoSuchKey(_))) => { + Ok(Cache::Miss) + } + Err(e) => Err(e.into()), + } + } + + async fn read_object_output(output: GetObjectOutput) -> Result { + let body = output.body.context("no HTTP body")?; + let mut body_reader = body.into_async_read(); + let mut body = Vec::new(); + body_reader + .read_to_end(&mut body) + .await + .context("failed to read HTTP body")?; + let hit = CacheRead::from(io::Cursor::new(body))?; + Ok(Cache::Hit(hit)) + } + + async fn put_object(client: S3Client, request: PutObjectRequest) -> Result<()> { + client + .put_object(request) + .await + .map(|_| ()) + .context("failed to put cache entry in s3") + .into() + } + fn normalize_key(&self, key: &str) -> String { format!( "{}{}/{}/{}/{}", @@ -71,66 +145,51 @@ impl S3Cache { } } +#[async_trait] impl Storage for S3Cache { - fn get(&self, key: &str) -> SFuture { + async fn get(&self, key: &str) -> Result { let key = self.normalize_key(key); - let result_cb = |result| match result { - Ok(data) => { - let hit = CacheRead::from(io::Cursor::new(data))?; - Ok(Cache::Hit(hit)) - } - Err(e) => { - warn!("Got AWS error: {:?}", e); - Ok(Cache::Miss) - } + let client = self.client.clone(); + let request = GetObjectRequest { + bucket: self.bucket_name.clone(), + key, + ..Default::default() }; - let bucket = self.bucket.clone(); - let response = self - .provider - .credentials() - .then(move |credentials| match credentials { - Ok(creds) => bucket.get(&key, Some(&creds)), - Err(e) => { - debug!("Could not load AWS creds: {}", e); - bucket.get(&key, None) - } - }) - .then(result_cb); - Box::new(response) + Self::get_object(client, request).await } - fn put(&self, key: &str, entry: CacheWrite) -> SFuture { + async fn put(&self, key: &str, entry: CacheWrite) -> Result { let key = self.normalize_key(&key); let start = Instant::now(); - let data = match entry.finish() { - Ok(data) => data, - Err(e) => return f_err(e), + let data = entry.finish()?; + let data_length = data.len(); + + let client = self.client.clone(); + let request = PutObjectRequest { + bucket: self.bucket_name.clone(), + body: Some(data.into()), + // Two weeks + cache_control: Some(CacheDirective::MaxAge(1_296_000).to_string()), + content_length: Some(data_length as i64), + content_type: Some("application/octet-stream".to_owned()), + key, + ..Default::default() }; - let credentials = self - .provider - .credentials() - .fcontext("failed to get AWS credentials"); - - let bucket = self.bucket.clone(); - let response = credentials.and_then(move |credentials| { - bucket - .put(&key, data, &credentials) - .fcontext("failed to put cache entry in s3") - }); - - Box::new(response.map(move |_| start.elapsed())) + + Self::put_object(client, request).await?; + Ok(start.elapsed()) } fn location(&self) -> String { - format!("S3, bucket: {}", self.bucket) + format!("S3, bucket: {}", self.bucket_name) } - fn current_size(&self) -> SFuture> { - Box::new(future::ok(None)) + async fn current_size(&self) -> Result> { + Ok(None) } - fn max_size(&self) -> SFuture> { - Box::new(future::ok(None)) + async fn max_size(&self) -> Result> { + Ok(None) } } diff --git a/src/commands.rs b/src/commands.rs index 6620d2e7d..f4da22eca 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -23,9 +23,9 @@ use crate::server::{self, DistInfo, ServerInfo, ServerStartup}; use crate::util::daemonize; use atty::Stream; use byteorder::{BigEndian, ByteOrder}; -use futures::Future; +use futures::StreamExt; use log::Level::Trace; -use std::env; +use std::{env, process::ExitStatus}; use std::ffi::{OsStr, OsString}; use std::fs::{File, OpenOptions}; use std::io::{self, Write}; @@ -34,10 +34,8 @@ use std::os::unix::process::ExitStatusExt; use std::path::Path; use std::process; use strip_ansi_escapes::Writer; -use tokio_compat::runtime::current_thread::Runtime; -use tokio_io::io::read_exact; -use tokio_io::AsyncRead; -use tokio_timer::Timeout; +use tokio::io::AsyncReadExt; +use tokio::runtime::Runtime; use which::which_in; use crate::errors::*; @@ -56,33 +54,30 @@ fn get_port() -> u16 { .unwrap_or(DEFAULT_PORT) } -fn read_server_startup_status( - server: R, -) -> impl Future { +async fn read_server_startup_status( + mut server: R +) -> Result { // This is an async equivalent of ServerConnection::read_one_response - read_exact(server, [0u8; 4]) - .map_err(Error::from) - .and_then(|(server, bytes)| { - let len = BigEndian::read_u32(&bytes); - let data = vec![0; len as usize]; - read_exact(server, data) - .map_err(Error::from) - .and_then(|(_server, data)| Ok(bincode::deserialize(&data)?)) - }) + let mut bytes = [0u8; 4]; + server.read_exact(&mut bytes[..]).await?; + + let len = BigEndian::read_u32(&bytes); + let mut data = vec![0; len as usize]; + server.read_exact(data.as_mut_slice()).await?; + + Ok(bincode::deserialize(&data)?) } /// Re-execute the current executable as a background server, and wait /// for it to start up. #[cfg(not(windows))] fn run_server_process() -> Result { - use futures::Stream; use std::time::Duration; trace!("run_server_process"); let tempdir = tempfile::Builder::new().prefix("sccache").tempdir()?; let socket_path = tempdir.path().join("sock"); let mut runtime = Runtime::new()?; - let listener = tokio_uds::UnixListener::bind(&socket_path)?; let exe_path = env::current_exe()?; let _child = process::Command::new(exe_path) .env("SCCACHE_START_SERVER", "1") @@ -90,23 +85,21 @@ fn run_server_process() -> Result { .env("RUST_BACKTRACE", "1") .spawn()?; - let startup = listener.incoming().into_future().map_err(|e| e.0); - let startup = startup.map_err(Error::from).and_then(|(socket, _rest)| { + let startup = async move { + let mut listener = tokio::net::UnixListener::bind(&socket_path)?; + let socket = listener.incoming().next().await; let socket = socket.unwrap(); // incoming() never returns None - read_server_startup_status(socket) - }); + + read_server_startup_status(socket?).await + }; let timeout = Duration::from_millis(SERVER_STARTUP_TIMEOUT_MS.into()); - let timeout = Timeout::new(startup, timeout).or_else(|err| { - if err.is_elapsed() { - Ok(ServerStartup::TimedOut) - } else if err.is_inner() { - Err(err.into_inner().unwrap()) - } else { - Err(err.into_timer().unwrap().into()) + runtime.block_on(async move { + match tokio::time::timeout(timeout, startup).await { + Ok(result) => result, + Err(_elapsed) => Ok(ServerStartup::TimedOut), } - }); - runtime.block_on(timeout) + }) } #[cfg(not(windows))] @@ -165,7 +158,7 @@ fn run_server_process() -> Result { // Create a mini event loop and register our named pipe server let mut runtime = Runtime::new()?; let pipe_name = format!(r"\\.\pipe\{}", Uuid::new_v4().to_simple_ref()); - let server = runtime.block_on(future::lazy(|| { + let server = runtime.block_on(future::lazy(|_val| { NamedPipe::new( &pipe_name, #[allow(deprecated)] @@ -248,19 +241,16 @@ fn run_server_process() -> Result { let result = read_server_startup_status(server); let timeout = Duration::from_millis(SERVER_STARTUP_TIMEOUT_MS.into()); - let timeout = Timeout::new(result, timeout).or_else(|err| { - if err.is_elapsed() { - Ok(ServerStartup::TimedOut) - } else if err.is_inner() { - Err(err.into_inner().unwrap().into()) - } else { - Err(err.into_timer().unwrap().into()) - } - }); - runtime.block_on(timeout) + runtime.block_on( + tokio::time::timeout(timeout, result) + ) + .and_then(|x| x) + .or_else(|err| { + Ok(ServerStartup::TimedOut) + }) } -/// Attempt to connect to an sccache server listening on `port`, or start one if no server is running. +/// Attempt to connect to a sccache server listening on `port`, or start one if no server is running. fn connect_or_start_server(port: u16) -> Result { trace!("connect_or_start_server({})", port); match connect_to_server(port) { @@ -382,7 +372,7 @@ where /// Return the signal that caused a process to exit from `status`. #[cfg(unix)] #[allow(dead_code)] -fn status_signal(status: process::ExitStatus) -> Option { +fn status_signal(status: ExitStatus) -> Option { status.signal() } @@ -513,10 +503,11 @@ where if log_enabled!(Trace) { trace!("running command: {:?}", cmd); } - let status = runtime.block_on( - cmd.spawn() - .and_then(|c| c.wait().fcontext("failed to wait for child")), - )?; + + let status = runtime.block_on(async move { + let child = cmd.spawn().await?; + child.wait().await.with_context(|| "failed to wait for a child") + })?; Ok(status.code().unwrap_or_else(|| { if let Some(sig) = status_signal(status) { @@ -673,22 +664,22 @@ pub fn run_command(cmd: Command) -> Result { #[cfg(feature = "dist-client")] Command::PackageToolchain(executable, out) => { use crate::compiler; - use futures_03::executor::ThreadPool; trace!("Command::PackageToolchain({})", executable.display()); let mut runtime = Runtime::new()?; let jobserver = unsafe { Client::new() }; let creator = ProcessCommandCreator::new(&jobserver); let env: Vec<_> = env::vars_os().collect(); - let pool = ThreadPool::builder().pool_size(1).create()?; let out_file = File::create(out)?; let cwd = env::current_dir().expect("A current working dir should exist"); - let compiler = - compiler::get_compiler_info(creator, &executable, &cwd, &env, &pool, None); - let packager = compiler.map(|c| c.0.get_toolchain_packager()); - let res = packager.and_then(|p| p.write_pkg(out_file)); - runtime.block_on(res)? + let pool = runtime.handle().clone(); + runtime.block_on(async move { + compiler::get_compiler_info(creator, &executable, &cwd, &env, &pool, None) + .await + .map(|compiler| compiler.0.get_toolchain_packager()) + .and_then(|packager| packager.write_pkg(out_file)) + })? } #[cfg(not(feature = "dist-client"))] Command::PackageToolchain(_executable, _out) => bail!( diff --git a/src/compiler/args.rs b/src/compiler/args.rs index b605e2adf..410b98bd4 100644 --- a/src/compiler/args.rs +++ b/src/compiler/args.rs @@ -1,3 +1,5 @@ +#![allow(clippy::unnecessary_lazy_evaluations)] + use std::cmp::Ordering; use std::error::Error; use std::ffi::OsString; @@ -664,7 +666,6 @@ macro_rules! take_arg { mod tests { use super::*; use itertools::{diff_with, Diff}; - use std::iter::FromIterator; macro_rules! arg { ($name:ident($x:expr)) => { @@ -999,33 +1000,44 @@ mod tests { // Needs type annotation or ascription let raw: Argument = arg!(Raw("value")); let unknown: Argument = arg!(UnknownFlag("-foo")); - assert_eq!(Vec::from_iter(raw.iter_os_strings()), ovec!["value"]); - assert_eq!(Vec::from_iter(unknown.iter_os_strings()), ovec!["-foo"]); + assert_eq!(raw.iter_os_strings().collect::>(), ovec!["value"]); + assert_eq!(unknown.iter_os_strings().collect::>(), ovec!["-foo"]); assert_eq!( - Vec::from_iter(arg!(Flag("-foo", FooFlag)).iter_os_strings()), + arg!(Flag("-foo", FooFlag)) + .iter_os_strings() + .collect::>(), ovec!["-foo"] ); let arg = arg!(WithValue("-foo", Foo("bar"), Concatenated)); - assert_eq!(Vec::from_iter(arg.iter_os_strings()), ovec!["-foobar"]); + assert_eq!(arg.iter_os_strings().collect::>(), ovec!["-foobar"]); let arg = arg!(WithValue("-foo", Foo("bar"), Concatenated('='))); - assert_eq!(Vec::from_iter(arg.iter_os_strings()), ovec!["-foo=bar"]); + assert_eq!(arg.iter_os_strings().collect::>(), ovec!["-foo=bar"]); let arg = arg!(WithValue("-foo", Foo("bar"), CanBeSeparated)); - assert_eq!(Vec::from_iter(arg.iter_os_strings()), ovec!["-foobar"]); + assert_eq!(arg.iter_os_strings().collect::>(), ovec!["-foobar"]); let arg = arg!(WithValue("-foo", Foo("bar"), CanBeSeparated('='))); - assert_eq!(Vec::from_iter(arg.iter_os_strings()), ovec!["-foo=bar"]); + assert_eq!(arg.iter_os_strings().collect::>(), ovec!["-foo=bar"]); let arg = arg!(WithValue("-foo", Foo("bar"), CanBeConcatenated)); - assert_eq!(Vec::from_iter(arg.iter_os_strings()), ovec!["-foo", "bar"]); + assert_eq!( + arg.iter_os_strings().collect::>(), + ovec!["-foo", "bar"] + ); let arg = arg!(WithValue("-foo", Foo("bar"), CanBeConcatenated('='))); - assert_eq!(Vec::from_iter(arg.iter_os_strings()), ovec!["-foo", "bar"]); + assert_eq!( + arg.iter_os_strings().collect::>(), + ovec!["-foo", "bar"] + ); let arg = arg!(WithValue("-foo", Foo("bar"), Separated)); - assert_eq!(Vec::from_iter(arg.iter_os_strings()), ovec!["-foo", "bar"]); + assert_eq!( + arg.iter_os_strings().collect::>(), + ovec!["-foo", "bar"] + ); } #[test] diff --git a/src/compiler/c.rs b/src/compiler/c.rs index 92a5e0526..cf6bdeb1a 100644 --- a/src/compiler/c.rs +++ b/src/compiler/c.rs @@ -23,8 +23,6 @@ use crate::dist; use crate::dist::pkg; use crate::mock_command::CommandCreatorSync; use crate::util::{hash_all, Digest, HashToDigest}; -use futures::Future; -use futures_03::executor::ThreadPool; use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use std::ffi::{OsStr, OsString}; @@ -163,7 +161,8 @@ pub enum CCompilerKind { } /// An interface to a specific C compiler. -pub trait CCompilerImpl: Clone + fmt::Debug + Send + 'static { +#[async_trait::async_trait] +pub trait CCompilerImpl: Clone + fmt::Debug + Send + Sync + 'static { /// Return the kind of compiler. fn kind(&self) -> CCompilerKind; /// Return true iff this is g++ or clang++. @@ -176,7 +175,7 @@ pub trait CCompilerImpl: Clone + fmt::Debug + Send + 'static { ) -> CompilerArguments; /// Run the C preprocessor with the specified set of arguments. #[allow(clippy::too_many_arguments)] - fn preprocess( + async fn preprocess( &self, creator: &T, executable: &Path, @@ -185,7 +184,7 @@ pub trait CCompilerImpl: Clone + fmt::Debug + Send + 'static { env_vars: &[(OsString, OsString)], may_dist: bool, rewrite_includes_only: bool, - ) -> SFuture + ) -> Result where T: CommandCreatorSync; /// Generate a command that can be used to invoke the C compiler to perform @@ -205,14 +204,18 @@ impl CCompiler where I: CCompilerImpl, { - pub fn new(compiler: I, executable: PathBuf, pool: &ThreadPool) -> SFuture> { - Box::new( - Digest::file(executable.clone(), &pool).map(move |digest| CCompiler { - executable, - executable_digest: digest, - compiler, - }), - ) + pub async fn new( + compiler: I, + executable: PathBuf, + pool: &tokio::runtime::Handle + ) -> Result> { + let digest = Digest::file(executable.clone(), pool).await?; + + Ok(CCompiler { + executable, + executable_digest: digest, + compiler, + }) } } @@ -251,27 +254,28 @@ impl Compiler for CCompiler { } } +#[async_trait::async_trait] impl CompilerHasher for CCompilerHasher where T: CommandCreatorSync, I: CCompilerImpl, { - fn generate_hash_key( + async fn generate_hash_key( self: Box, creator: &T, cwd: PathBuf, env_vars: Vec<(OsString, OsString)>, may_dist: bool, - pool: &ThreadPool, + pool: &tokio::runtime::Handle, rewrite_includes_only: bool, - ) -> SFuture { - let me = *self; + ) -> Result { let CCompilerHasher { parsed_args, executable, executable_digest, compiler, - } = me; + } = *self; + let result = compiler.preprocess( creator, &executable, @@ -280,96 +284,90 @@ where &env_vars, may_dist, rewrite_includes_only, - ); + ).await; let out_pretty = parsed_args.output_pretty().into_owned(); - let result = result.map_err(move |e| { + let result = result.map_err(|e| { debug!("[{}]: preprocessor failed: {:?}", out_pretty, e); e }); - let out_pretty = parsed_args.output_pretty().into_owned(); - let extra_hashes = hash_all(&parsed_args.extra_hash_files, &pool.clone()); + + let extra_hashes = hash_all(&parsed_args.extra_hash_files, &pool.clone()).await?; let outputs = parsed_args.outputs.clone(); let args_cwd = cwd.clone(); - Box::new( - result - .or_else(move |err| { - // Errors remove all traces of potential output. - debug!("removing files {:?}", &outputs); - - let v: std::result::Result<(), std::io::Error> = - outputs.values().fold(Ok(()), |r, f| { - r.and_then(|_| { - let mut path = (&args_cwd).clone(); - path.push(&f); - match fs::metadata(&path) { - // File exists, remove it. - Ok(_) => fs::remove_file(&path), - _ => Ok(()), - } - }) - }); - if v.is_err() { - warn!("Could not remove files after preprocessing failed!\n"); - } - - match err.downcast::() { - Ok(ProcessError(output)) => { - debug!( - "[{}]: preprocessor returned error status {:?}", - out_pretty, - output.status.code() - ); - // Drop the stdout since it's the preprocessor output, - // just hand back stderr and the exit status. - bail!(ProcessError(process::Output { - stdout: vec!(), - ..output - })) + let preprocessor_result = result.or_else(move |err| { + // Errors remove all traces of potential output. + debug!("removing files {:?}", &outputs); + + let v: std::result::Result<(), std::io::Error> = + outputs.values().fold(Ok(()), |r, f| { + r.and_then(|_| { + let mut path = (&args_cwd).clone(); + path.push(&f); + match fs::metadata(&path) { + // File exists, remove it. + Ok(_) => fs::remove_file(&path), + _ => Ok(()), } - Err(err) => Err(err), - } - }) - .and_then(move |preprocessor_result| { - trace!( - "[{}]: Preprocessor output is {} bytes", - parsed_args.output_pretty(), - preprocessor_result.stdout.len() - ); + }) + }); + if v.is_err() { + warn!("Could not remove files after preprocessing failed!"); + } - Box::new(extra_hashes.and_then(move |extra_hashes| { - let key = { - hash_key( - &executable_digest, - parsed_args.language, - &parsed_args.common_args, - &extra_hashes, - &env_vars, - &preprocessor_result.stdout, - compiler.plusplus(), - ) - }; - // A compiler binary may be a symlink to another and so has the same digest, but that means - // the toolchain will not contain the correct path to invoke the compiler! Add the compiler - // executable path to try and prevent this - let weak_toolchain_key = - format!("{}-{}", executable.to_string_lossy(), executable_digest); - Ok(HashResult { - key, - compilation: Box::new(CCompilation { - parsed_args, - #[cfg(feature = "dist-client")] - preprocessed_input: preprocessor_result.stdout, - executable, - compiler, - cwd, - env_vars, - }), - weak_toolchain_key, - }) + match err.downcast::() { + Ok(ProcessError(output)) => { + debug!( + "[{}]: preprocessor returned error status {:?}", + out_pretty, + output.status.code() + ); + // Drop the stdout since it's the preprocessor output, + // just hand back stderr and the exit status. + bail!(ProcessError(process::Output { + stdout: vec!(), + ..output })) - }), - ) + } + Err(err) => Err(err), + } + })?; + + trace!( + "[{}]: Preprocessor output is {} bytes", + parsed_args.output_pretty(), + preprocessor_result.stdout.len() + ); + + let key = { + hash_key( + &executable_digest, + parsed_args.language, + &parsed_args.common_args, + &extra_hashes, + &env_vars, + &preprocessor_result.stdout, + compiler.plusplus(), + ) + }; + // A compiler binary may be a symlink to another and so has the same digest, but that means + // the toolchain will not contain the correct path to invoke the compiler! Add the compiler + // executable path to try and prevent this + let weak_toolchain_key = + format!("{}-{}", executable.to_string_lossy(), executable_digest); + Ok(HashResult { + key, + compilation: Box::new(CCompilation { + parsed_args, + #[cfg(feature = "dist-client")] + preprocessed_input: preprocessor_result.stdout, + executable, + compiler, + cwd, + env_vars, + }), + weak_toolchain_key, + }) } fn color_mode(&self) -> ColorMode { diff --git a/src/compiler/clang.rs b/src/compiler/clang.rs index 1563cfd3a..5bc655782 100644 --- a/src/compiler/clang.rs +++ b/src/compiler/clang.rs @@ -21,9 +21,9 @@ use crate::compiler::{gcc, write_temp_file, Cacheable, CompileCommand, CompilerA use crate::dist; use crate::mock_command::{CommandCreator, CommandCreatorSync, RunCommand}; use crate::util::{run_input_output, OsStrExt}; -use futures::future::{self, Future}; use std::ffi::OsString; use std::fs::File; +use std::future::Future; use std::io::{self, Write}; use std::path::{Path, PathBuf}; use std::process; @@ -37,6 +37,7 @@ pub struct Clang { pub clangplusplus: bool, } +#[async_trait::async_trait] impl CCompilerImpl for Clang { fn kind(&self) -> CCompilerKind { CCompilerKind::Clang @@ -57,7 +58,7 @@ impl CCompilerImpl for Clang { ) } - fn preprocess( + async fn preprocess( &self, creator: &T, executable: &Path, @@ -66,7 +67,7 @@ impl CCompilerImpl for Clang { env_vars: &[(OsString, OsString)], may_dist: bool, rewrite_includes_only: bool, - ) -> SFuture + ) -> Result where T: CommandCreatorSync, { @@ -80,6 +81,7 @@ impl CCompilerImpl for Clang { self.kind(), rewrite_includes_only, ) + .await } fn generate_compile_commands( @@ -136,8 +138,8 @@ mod test { use crate::compiler::*; use crate::mock_command::*; use crate::test::utils::*; - use futures::Future; use std::collections::HashMap; + use std::future::Future; use std::path::PathBuf; fn parse_arguments_(arguments: Vec) -> CompilerArguments { diff --git a/src/compiler/compiler.rs b/src/compiler/compiler.rs index 2a6893f86..70408264d 100644 --- a/src/compiler/compiler.rs +++ b/src/compiler/compiler.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(clippy::complexity)] + use crate::cache::{Cache, CacheWrite, DecompressionFailure, Storage}; use crate::compiler::c::{CCompiler, CCompilerKind}; use crate::compiler::clang::Clang; @@ -25,10 +27,8 @@ use crate::dist; #[cfg(feature = "dist-client")] use crate::dist::pkg; use crate::mock_command::{exit_status, CommandChild, CommandCreatorSync, RunCommand}; -use crate::util::{fmt_duration_as_secs, ref_env, run_input_output, SpawnExt}; +use crate::util::{fmt_duration_as_secs, ref_env, run_input_output}; use filetime::FileTime; -use futures::Future; -use futures_03::executor::ThreadPool; use std::borrow::Cow; use std::collections::HashMap; use std::ffi::OsString; @@ -36,14 +36,15 @@ use std::fmt; #[cfg(feature = "dist-client")] use std::fs; use std::fs::File; +use std::future::Future; use std::io::prelude::*; use std::path::{Path, PathBuf}; +use std::pin::Pin; use std::process::{self, Stdio}; use std::str; use std::sync::Arc; use std::time::{Duration, Instant}; use tempfile::TempDir; -use tokio_timer::Timeout; use crate::errors::*; @@ -65,7 +66,7 @@ pub struct CompileCommand { } impl CompileCommand { - pub fn execute(self, creator: &T) -> SFuture + pub async fn execute(self, creator: &T) -> Result where T: CommandCreatorSync, { @@ -74,7 +75,7 @@ impl CompileCommand { .env_clear() .envs(self.env_vars) .current_dir(self.cwd); - Box::new(run_input_output(cmd, None)) + run_input_output(cmd, None).await } } @@ -149,14 +150,21 @@ where creator: T, cwd: PathBuf, env_vars: &[(OsString, OsString)], - ) -> SFuture<(PathBuf, FileTime)>; + ) -> Pin> + Send + 'static>>; /// Create a clone of `Self` and puts it in a `Box` fn box_clone(&self) -> Box>; } +impl Clone for Box> { + fn clone(&self) -> Box> { + self.box_clone() + } +} + /// An interface to a compiler for hash key generation, the result of /// argument parsing. +#[async_trait::async_trait] pub trait CompilerHasher: fmt::Debug + Send + 'static where T: CommandCreatorSync, @@ -164,15 +172,15 @@ where /// Given information about a compiler command, generate a hash key /// that can be used for cache lookups, as well as any additional /// information that can be reused for compilation if necessary. - fn generate_hash_key( + async fn generate_hash_key( self: Box, creator: &T, cwd: PathBuf, env_vars: Vec<(OsString, OsString)>, may_dist: bool, - pool: &ThreadPool, + pool: &tokio::runtime::Handle, rewrite_includes_only: bool, - ) -> SFuture; + ) -> Result; /// Return the state of any `--color` option passed to the compiler. fn color_mode(&self) -> ColorMode; @@ -180,231 +188,202 @@ where /// Look up a cached compile result in `storage`. If not found, run the /// compile and store the result. #[allow(clippy::too_many_arguments)] - fn get_cached_or_compile( + async fn get_cached_or_compile( self: Box, - dist_client: Result>>, + dist_client: Option>, creator: T, - storage: Arc, + storage: Arc, arguments: Vec, cwd: PathBuf, env_vars: Vec<(OsString, OsString)>, cache_control: CacheControl, - pool: ThreadPool, - ) -> SFuture<(CompileResult, process::Output)> { + pool: tokio::runtime::Handle, + ) -> Result<(CompileResult, process::Output)> { + let out_pretty = self.output_pretty().into_owned(); debug!("[{}]: get_cached_or_compile: {:?}", out_pretty, arguments); let start = Instant::now(); - let may_dist = match dist_client { - Ok(Some(_)) => true, - _ => false, - }; + let may_dist = dist_client.is_some(); let rewrite_includes_only = match dist_client { - Ok(Some(ref client)) => client.rewrite_includes_only(), + Some(ref client) => client.rewrite_includes_only(), _ => false, }; let result = self.generate_hash_key( - &creator, - cwd.clone(), - env_vars, - may_dist, - &pool, - rewrite_includes_only, + &creator, + cwd.clone(), + env_vars, + may_dist, + &pool, + rewrite_includes_only, + ).await; + debug!( + "[{}]: generate_hash_key took {}", + out_pretty, + fmt_duration_as_secs(&start.elapsed()) ); - Box::new(result.then(move |res| -> SFuture<_> { - debug!( - "[{}]: generate_hash_key took {}", - out_pretty, - fmt_duration_as_secs(&start.elapsed()) - ); - let (key, compilation, weak_toolchain_key) = match res { - Err(e) => { - return match e.downcast::() { - Ok(ProcessError(output)) => f_ok((CompileResult::Error, output)), - Err(e) => f_err(e), - }; - } - Ok(HashResult { - key, - compilation, - weak_toolchain_key, - }) => (key, compilation, weak_toolchain_key), - }; - trace!("[{}]: Hash key: {}", out_pretty, key); - // If `ForceRecache` is enabled, we won't check the cache. - let start = Instant::now(); - let cache_status = if cache_control == CacheControl::ForceRecache { - f_ok(Cache::Recache) + let (key, compilation, weak_toolchain_key) = match result { + Err(e) => { + return match e.downcast::() { + Ok(ProcessError(output)) => Ok((CompileResult::Error, output)), + Err(e) => Err(e), + }; + } + Ok(HashResult { + key, + compilation, + weak_toolchain_key, + }) => (key, compilation, weak_toolchain_key), + }; + trace!("[{}]: Hash key: {}", out_pretty, key); + // If `ForceRecache` is enabled, we won't check the cache. + let start = Instant::now(); + let cache_status = async { + if cache_control == CacheControl::ForceRecache { + Ok(Cache::Recache) } else { - storage.get(&key) - }; - - // Set a maximum time limit for the cache to respond before we forge - // ahead ourselves with a compilation. - let timeout = Duration::new(60, 0); - let cache_status = Timeout::new(cache_status, timeout); + storage.get(&key).await + } + }; - // Check the result of the cache lookup. - Box::new(cache_status.then(move |result| { - let out_pretty2 = out_pretty.clone(); - let duration = start.elapsed(); - let outputs = compilation - .outputs() - .map(|(key, path)| (key.to_string(), cwd.join(path))) - .collect::>(); - - let miss_type = Box::new(match result { - Ok(Cache::Hit(mut entry)) => { - debug!( - "[{}]: Cache hit in {}", - out_pretty, - fmt_duration_as_secs(&duration) - ); - let stdout = entry.get_stdout(); - let stderr = entry.get_stderr(); - let write = entry.extract_objects(outputs.clone(), &pool); - let output = process::Output { - status: exit_status(0), - stdout, - stderr, - }; - let hit = CompileResult::CacheHit(duration); - Box::new(write.then(move |result| match result { - Ok(()) => f_ok(CacheLookupResult::Success(hit, output)), - Err(e) => { - if let Some(_) = e.downcast_ref::() { - debug!("[{}]: Failed to decompress object", out_pretty); - f_ok(CacheLookupResult::Miss(MissType::CacheReadError)) - } else { - f_err(e) - } - } - })) - } - Ok(Cache::Miss) => { - debug!( - "[{}]: Cache miss in {}", - out_pretty, - fmt_duration_as_secs(&duration) - ); - f_ok(CacheLookupResult::Miss(MissType::Normal)) - } - Ok(Cache::Recache) => { - debug!( - "[{}]: Cache recache in {}", - out_pretty, - fmt_duration_as_secs(&duration) - ); - f_ok(CacheLookupResult::Miss(MissType::ForcedRecache)) - } - Err(err) => { - if err.is_elapsed() { - debug!( - "[{}]: Cache timed out {}", - out_pretty, - fmt_duration_as_secs(&duration) - ); - f_ok(CacheLookupResult::Miss(MissType::TimedOut)) + // Set a maximum time limit for the cache to respond before we forge + // ahead ourselves with a compilation. + let timeout = Duration::new(60, 0); + let cache_status = tokio::time::timeout(timeout, cache_status); + + // Check the result of the cache lookup. + let duration = start.elapsed(); + let outputs = compilation + .outputs() + .map(|(key, path)| (key.to_string(), cwd.join(path))) + .collect::>(); + + let lookup = match cache_status.await { + Ok(Ok(Cache::Hit(mut entry))) => { + debug!( + "[{}]: Cache hit in {}", + out_pretty, + fmt_duration_as_secs(&duration) + ); + let stdout = entry.get_stdout(); + let stderr = entry.get_stderr(); + let output = process::Output { + status: exit_status(0), + stdout, + stderr, + }; + let hit = CompileResult::CacheHit(duration); + match entry.extract_objects(outputs.clone(), &pool).await { + Ok(()) => Ok(CacheLookupResult::Success(hit, output)), + Err(e) => { + if e.downcast_ref::().is_some() { + debug!("[{}]: Failed to decompress object", out_pretty); + Ok(CacheLookupResult::Miss(MissType::CacheReadError)) } else { - error!("[{}]: Cache read error: {}", out_pretty, err); - if err.is_inner() { - let err = err.into_inner().unwrap(); - for e in err.chain().skip(1) { - error!("[{}] \t{}", out_pretty, e); - } - } - f_ok(CacheLookupResult::Miss(MissType::CacheReadError)) + Err(e) } } - }); + } + } + Ok(Ok(Cache::Miss)) => { + debug!( + "[{}]: Cache miss in {}", + out_pretty, + fmt_duration_as_secs(&duration) + ); + Ok(CacheLookupResult::Miss(MissType::Normal)) + } + Ok(Ok(Cache::Recache)) => { + debug!( + "[{}]: Cache recache in {}", + out_pretty, + fmt_duration_as_secs(&duration) + ); + Ok(CacheLookupResult::Miss(MissType::ForcedRecache)) + } + Ok(Err(err)) => { + error!("[{}]: Cache read error: {:?}", out_pretty, err); + Ok(CacheLookupResult::Miss(MissType::CacheReadError)) + } + Err(_elapsed) => { + debug!( + "[{}]: Cache timed out {}", + out_pretty, + fmt_duration_as_secs(&duration) + ); + Ok(CacheLookupResult::Miss(MissType::TimedOut)) + } + }?; - Box::new(miss_type.and_then(move |result| { - match result { - CacheLookupResult::Success(compile_result, output) => { - f_ok((compile_result, output)) - } - CacheLookupResult::Miss(miss_type) => { - // Cache miss, so compile it. - let start = Instant::now(); - let compile = dist_or_local_compile( - dist_client, - creator, - cwd, - compilation, - weak_toolchain_key, - out_pretty2.clone(), - ); - - Box::new(compile.and_then( - move |(cacheable, dist_type, compiler_result)| { - let duration = start.elapsed(); - if !compiler_result.status.success() { - debug!( - "[{}]: Compiled but failed, not storing in cache", - out_pretty2 - ); - return f_ok((CompileResult::CompileFailed, compiler_result)) - as SFuture<_>; - } - if cacheable != Cacheable::Yes { - // Not cacheable - debug!("[{}]: Compiled but not cacheable", out_pretty2); - return f_ok(( - CompileResult::NotCacheable, - compiler_result, - )); - } - debug!( - "[{}]: Compiled in {}, storing in cache", - out_pretty2, - fmt_duration_as_secs(&duration) - ); - let write = CacheWrite::from_objects(outputs, &pool); - let write = write.fcontext("failed to zip up compiler outputs"); - let o = out_pretty2.clone(); - Box::new( - write - .and_then(move |mut entry| { - entry.put_stdout(&compiler_result.stdout)?; - entry.put_stderr(&compiler_result.stderr)?; - - // Try to finish storing the newly-written cache - // entry. We'll get the result back elsewhere. - let future = - storage.put(&key, entry).then(move |res| { - match res { - Ok(_) => debug!( - "[{}]: Stored in cache successfully!", - out_pretty2 - ), - Err(ref e) => debug!( - "[{}]: Cache write error: {:?}", - out_pretty2, e - ), - } - res.map(|duration| CacheWriteInfo { - object_file_pretty: out_pretty2, - duration, - }) - }); - let future = Box::new(future); - Ok(( - CompileResult::CacheMiss( - miss_type, dist_type, duration, future, - ), - compiler_result, - )) - }) - .fwith_context(move || { - format!("failed to store `{}` to cache", o) - }), - ) - }, - )) - } + + match lookup { + CacheLookupResult::Success(compile_result, output) => { + Ok::<_, Error>((compile_result, output)) + } + CacheLookupResult::Miss(miss_type) => { + // Cache miss, so compile it. + let start = Instant::now(); + + let (cacheable, dist_type, compiler_result) = + dist_or_local_compile( + dist_client, + creator, + cwd, + compilation, + weak_toolchain_key, + out_pretty.clone(), + ).await?; + let duration = start.elapsed(); + if !compiler_result.status.success() { + debug!( + "[{}]: Compiled but failed, not storing in cache", + out_pretty + ); + return Ok((CompileResult::CompileFailed, compiler_result)); + } + if cacheable != Cacheable::Yes { + // Not cacheable + debug!("[{}]: Compiled but not cacheable", out_pretty); + return Ok(( + CompileResult::NotCacheable, + compiler_result, + )); + } + debug!( + "[{}]: Compiled in {}, storing in cache", + out_pretty, + fmt_duration_as_secs(&duration) + ); + let mut entry = CacheWrite::from_objects(outputs, &pool) + .await + .context("failed to zip up compiler outputs")?; + + entry.put_stdout(&compiler_result.stdout)?; + entry.put_stderr(&compiler_result.stderr)?; + + let out_pretty2 = out_pretty.clone(); + // Try to finish storing the newly-written cache + // entry. We'll get the result back elsewhere. + let future = async move { + match storage.put(&key, entry).await { + Ok(_) => debug!("[{}]: Stored in cache successfully!", out_pretty2), + Err(ref e) => debug!("[{}]: Cache write error: {:?}", out_pretty2, e), } - })) - })) - })) + + Ok(CacheWriteInfo { + object_file_pretty: out_pretty2, + duration, + }) + }; + let future = Box::pin(future); + Ok(( + CompileResult::CacheMiss( + miss_type, dist_type, duration, future, + ), + compiler_result, + )) + } + } + .with_context(|| format!("failed to store `{}` to cache", out_pretty)) } /// A descriptive string about the file that we're going to be producing. @@ -417,210 +396,187 @@ where } #[cfg(not(feature = "dist-client"))] -fn dist_or_local_compile( - _dist_client: Result>>, +async fn dist_or_local_compile( + _dist_client: Option>, creator: T, _cwd: PathBuf, compilation: Box, _weak_toolchain_key: String, out_pretty: String, -) -> SFuture<(Cacheable, DistType, process::Output)> +) -> Result<(Cacheable, DistType, process::Output)> where T: CommandCreatorSync, { let mut path_transformer = dist::PathTransformer::default(); - let compile_commands = compilation + let (compile_cmd, _dist_compile_cmd, cacheable) = compilation .generate_compile_commands(&mut path_transformer, true) - .context("Failed to generate compile commands"); - let (compile_cmd, _dist_compile_cmd, cacheable) = match compile_commands { - Ok(cmds) => cmds, - Err(e) => return f_err(e), - }; + .context("Failed to generate compile commands")?; debug!("[{}]: Compiling locally", out_pretty); - Box::new( - compile_cmd - .execute(&creator) - .map(move |o| (cacheable, DistType::NoDist, o)), - ) + compile_cmd + .execute(&creator) + .await + .map(move |o| (cacheable, DistType::NoDist, o)) } #[cfg(feature = "dist-client")] -fn dist_or_local_compile( - dist_client: Result>>, +async fn dist_or_local_compile( + dist_client: Option>, creator: T, cwd: PathBuf, compilation: Box, weak_toolchain_key: String, out_pretty: String, -) -> SFuture<(Cacheable, DistType, process::Output)> +) -> Result<(Cacheable, DistType, process::Output)> where T: CommandCreatorSync, { - use futures::future; use std::io; let rewrite_includes_only = match dist_client { - Ok(Some(ref client)) => client.rewrite_includes_only(), + Some(ref client) => client.rewrite_includes_only(), _ => false, }; let mut path_transformer = dist::PathTransformer::default(); - let compile_commands = compilation + let (compile_cmd, dist_compile_cmd, cacheable) = compilation .generate_compile_commands(&mut path_transformer, rewrite_includes_only) - .context("Failed to generate compile commands"); - let (compile_cmd, dist_compile_cmd, cacheable) = match compile_commands { - Ok(cmds) => cmds, - Err(e) => return f_err(e), - }; + .context("Failed to generate compile commands")?; let dist_client = match dist_client { - Ok(Some(dc)) => dc, - Ok(None) => { + Some(dc) => dc, + None => { debug!("[{}]: Compiling locally", out_pretty); - return Box::new( - compile_cmd - .execute(&creator) - .map(move |o| (cacheable, DistType::NoDist, o)), - ); - } - Err(e) => { - return f_err(e); + return compile_cmd + .execute(&creator) + .await + .map(move |o| (cacheable, DistType::NoDist, o)); } }; debug!("[{}]: Attempting distributed compilation", out_pretty); - let compile_out_pretty = out_pretty.clone(); - let compile_out_pretty2 = out_pretty.clone(); - let compile_out_pretty3 = out_pretty.clone(); - let compile_out_pretty4 = out_pretty; + let out_pretty2 = out_pretty.clone(); + let local_executable = compile_cmd.executable.clone(); - let local_executable2 = local_executable.clone(); - // TODO: the number of map_errs is subideal, but there's no futures-based carrier trait AFAIK - Box::new(future::result(dist_compile_cmd.context("Could not create distributed compile command")) - .and_then(move |dist_compile_cmd| { - debug!("[{}]: Creating distributed compile request", compile_out_pretty); - let dist_output_paths = compilation.outputs() - .map(|(_key, path)| path_transformer.as_dist_abs(&cwd.join(path))) - .collect::>() - .context("Failed to adapt an output path for distributed compile")?; - compilation.into_dist_packagers(path_transformer) - .map(|packagers| (dist_compile_cmd, packagers, dist_output_paths)) - }) - .and_then(move |(mut dist_compile_cmd, (inputs_packager, toolchain_packager, outputs_rewriter), dist_output_paths)| { - debug!("[{}]: Identifying dist toolchain for {:?}", compile_out_pretty2, local_executable); - dist_client.put_toolchain(&local_executable, &weak_toolchain_key, toolchain_packager) - .and_then(|(dist_toolchain, maybe_dist_compile_executable)| { - let mut tc_archive = None; - if let Some((dist_compile_executable, archive_path)) = maybe_dist_compile_executable { - dist_compile_cmd.executable = dist_compile_executable; - tc_archive = Some(archive_path); - } - Ok((dist_client, dist_compile_cmd, dist_toolchain, inputs_packager, outputs_rewriter, dist_output_paths, tc_archive)) - }) - }) - .and_then(move |(dist_client, dist_compile_cmd, dist_toolchain, inputs_packager, outputs_rewriter, dist_output_paths, tc_archive)| { - debug!("[{}]: Requesting allocation", compile_out_pretty3); - dist_client.do_alloc_job(dist_toolchain.clone()) - .and_then(move |jares| { - let alloc = match jares { - dist::AllocJobResult::Success { job_alloc, need_toolchain: true } => { - debug!("[{}]: Sending toolchain {} for job {}", - compile_out_pretty3, dist_toolchain.archive_id, job_alloc.job_id); - Box::new(dist_client.do_submit_toolchain(job_alloc.clone(), dist_toolchain) - .and_then(move |res| { - match res { - dist::SubmitToolchainResult::Success => Ok(job_alloc), - dist::SubmitToolchainResult::JobNotFound => - bail!("Job {} not found on server", job_alloc.job_id), - dist::SubmitToolchainResult::CannotCache => - bail!("Toolchain for job {} could not be cached by server", job_alloc.job_id), - } - }) - .fcontext("Could not submit toolchain")) - }, - dist::AllocJobResult::Success { job_alloc, need_toolchain: false } => - f_ok(job_alloc), - dist::AllocJobResult::Fail { msg } => - f_err(anyhow!("Failed to allocate job").context(msg)), - }; - alloc - .and_then(move |job_alloc| { - let job_id = job_alloc.job_id; - let server_id = job_alloc.server_id; - debug!("[{}]: Running job", compile_out_pretty3); - dist_client.do_run_job(job_alloc, dist_compile_cmd, dist_output_paths, inputs_packager) - .map(move |res| ((job_id, server_id), res)) - .fwith_context(move || format!("could not run distributed compilation job on {:?}", server_id)) - }) - }) - .and_then(move |((job_id, server_id), (jres, path_transformer))| { - let jc = match jres { - dist::RunJobResult::Complete(jc) => jc, - dist::RunJobResult::JobNotFound => bail!("Job {} not found on server", job_id), - }; - info!("fetched {:?}", jc.outputs.iter().map(|&(ref p, ref bs)| (p, bs.lens().to_string())).collect::>()); - let mut output_paths: Vec = vec![]; - macro_rules! try_or_cleanup { - ($v:expr) => {{ - match $v { - Ok(v) => v, - Err(e) => { - // Do our best to clear up. We may end up deleting a file that we just wrote over - // the top of, but it's better to clear up too much than too little - for local_path in output_paths.iter() { - if let Err(e) = fs::remove_file(local_path) { - if e.kind() != io::ErrorKind::NotFound { - warn!("{} while attempting to clear up {}", e, local_path.display()) - } - } - } - return Err(e) - }, + let local_executable2 = compile_cmd.executable.clone(); + + let do_dist_compile = async move { + let mut dist_compile_cmd = dist_compile_cmd + .context("Could not create distributed compile command")?; + debug!("[{}]: Creating distributed compile request", out_pretty); + let dist_output_paths = compilation.outputs() + .map(|(_key, path)| path_transformer.as_dist_abs(&cwd.join(path))) + .collect::>() + .context("Failed to adapt an output path for distributed compile")?; + let (inputs_packager, toolchain_packager, outputs_rewriter) = + compilation.into_dist_packagers(path_transformer)?; + + debug!("[{}]: Identifying dist toolchain for {:?}", out_pretty, local_executable); + let (dist_toolchain, maybe_dist_compile_executable) = + dist_client.put_toolchain(local_executable, weak_toolchain_key, toolchain_packager) + .await?; + let mut tc_archive = None; + if let Some((dist_compile_executable, archive_path)) = maybe_dist_compile_executable { + dist_compile_cmd.executable = dist_compile_executable; + tc_archive = Some(archive_path); + } + + debug!("[{}]: Requesting allocation", out_pretty); + let jares = dist_client.do_alloc_job(dist_toolchain.clone()).await?; + let job_alloc = match jares { + dist::AllocJobResult::Success { job_alloc, need_toolchain: true } => { + debug!("[{}]: Sending toolchain {} for job {}", + out_pretty, dist_toolchain.archive_id, job_alloc.job_id); + + match dist_client.do_submit_toolchain(job_alloc.clone(), dist_toolchain).await.map_err(|e| e.context("Could not submit toolchain"))? { + dist::SubmitToolchainResult::Success => Ok(job_alloc), + dist::SubmitToolchainResult::JobNotFound => + bail!("Job {} not found on server", job_alloc.job_id), + dist::SubmitToolchainResult::CannotCache => + bail!("Toolchain for job {} could not be cached by server", job_alloc.job_id), + } + }, + dist::AllocJobResult::Success { job_alloc, need_toolchain: false } => + Ok(job_alloc), + dist::AllocJobResult::Fail { msg } => + Err(anyhow!("Failed to allocate job").context(msg)), + }?; + let job_id = job_alloc.job_id; + let server_id = job_alloc.server_id; + debug!("[{}]: Running job", out_pretty); + let ((job_id, server_id), (jres, path_transformer)) = + dist_client.do_run_job(job_alloc, dist_compile_cmd, dist_output_paths, inputs_packager).await + .map(move |res| ((job_id, server_id), res)) + .with_context(|| format!("could not run distributed compilation job on {:?}", server_id))?; + + let jc = match jres { + dist::RunJobResult::Complete(jc) => jc, + dist::RunJobResult::JobNotFound => bail!("Job {} not found on server", job_id), + }; + info!("fetched {:?}", jc.outputs.iter().map(|&(ref p, ref bs)| (p, bs.lens().to_string())).collect::>()); + let mut output_paths: Vec = vec![]; + macro_rules! try_or_cleanup { + ($v:expr) => {{ + match $v { + Ok(v) => v, + Err(e) => { + // Do our best to clear up. We may end up deleting a file that we just wrote over + // the top of, but it's better to clear up too much than too little + for local_path in output_paths.iter() { + if let Err(e) = fs::remove_file(local_path) { + if e.kind() != io::ErrorKind::NotFound { + warn!("{} while attempting to clear up {}", e, local_path.display()) + } } - }}; - } + } + return Err(e) + }, + } + }}; + } - for (path, output_data) in jc.outputs { - let len = output_data.lens().actual; - let local_path = try_or_cleanup!(path_transformer.to_local(&path) - .with_context(|| format!("unable to transform output path {}", path))); - output_paths.push(local_path); - // Do this first so cleanup works correctly - let local_path = output_paths.last().expect("nothing in vec after push"); + for (path, output_data) in jc.outputs { + let len = output_data.lens().actual; + let local_path = try_or_cleanup!(path_transformer.to_local(&path) + .with_context(|| format!("unable to transform output path {}", path))); + output_paths.push(local_path); + // Do this first so cleanup works correctly + let local_path = output_paths.last().expect("nothing in vec after push"); - let mut file = try_or_cleanup!(File::create(&local_path) - .with_context(|| format!("Failed to create output file {}", local_path.display()))); - let count = try_or_cleanup!(io::copy(&mut output_data.into_reader(), &mut file) - .with_context(|| format!("Failed to write output to {}", local_path.display()))); + let mut file = try_or_cleanup!(File::create(&local_path) + .with_context(|| format!("Failed to create output file {}", local_path.display()))); + let count = try_or_cleanup!(io::copy(&mut output_data.into_reader(), &mut file) + .with_context(|| format!("Failed to write output to {}", local_path.display()))); - assert!(count == len); - } - let extra_inputs = match tc_archive { - Some(p) => vec![p], - None => vec![], - }; - try_or_cleanup!(outputs_rewriter.handle_outputs(&path_transformer, &output_paths, &extra_inputs) - .with_context(|| "failed to rewrite outputs from compile")); - Ok((DistType::Ok(server_id), jc.output.into())) - }) - }) - .or_else(move |e| { - if let Some(HttpClientError(_)) = e.downcast_ref::() { - f_err(e) - } else if let Some(lru_disk_cache::Error::FileTooLarge) = e.downcast_ref::() { - f_err(anyhow!( - "Could not cache dist toolchain for {:?} locally. - Increase `toolchain_cache_size` or decrease the toolchain archive size.", - local_executable2)) - } else { - // `{:#}` prints the error and the causes in a single line. - let errmsg = format!("{:#}", e); - warn!("[{}]: Could not perform distributed compile, falling back to local: {}", compile_out_pretty4, errmsg); - Box::new(compile_cmd.execute(&creator).map(|o| (DistType::Error, o))) - } - }) - .map(move |(dt, o)| (cacheable, dt, o)) - ) + assert!(count == len); + } + let extra_inputs = match tc_archive { + Some(p) => vec![p], + None => vec![], + }; + try_or_cleanup!(outputs_rewriter.handle_outputs(&path_transformer, &output_paths, &extra_inputs) + .with_context(|| "failed to rewrite outputs from compile")); + Ok((DistType::Ok(server_id), jc.output.into())) + }; + + use futures::TryFutureExt; + do_dist_compile.or_else(move |e| async move { + if let Some(HttpClientError(_)) = e.downcast_ref::() { + Err(e) + } else if let Some(lru_disk_cache::Error::FileTooLarge) = e.downcast_ref::() { + Err(anyhow!( + "Could not cache dist toolchain for {:?} locally. + Increase `toolchain_cache_size` or decrease the toolchain archive size.", + local_executable2)) + } else { + // `{:#}` prints the error and the causes in a single line. + let errmsg = format!("{:#}", e); + warn!("[{}]: Could not perform distributed compile, falling back to local: {}", out_pretty2, errmsg); + + compile_cmd.execute(&creator).await.map(|o| (DistType::Error, o)) + } + }) + .map_ok(move |(dt, o)| (cacheable, dt, o)) + .await } impl Clone for Box> { @@ -630,7 +586,7 @@ impl Clone for Box> { } /// An interface to a compiler for actually invoking compilation. -pub trait Compilation { +pub trait Compilation: Send { /// Given information about a compiler command, generate a command that can /// execute the compiler. fn generate_compile_commands( @@ -654,7 +610,7 @@ pub trait Compilation { } #[cfg(feature = "dist-client")] -pub trait OutputsRewriter { +pub trait OutputsRewriter: Send { /// Perform any post-compilation handling of outputs, given a Vec of the dist_path and local_path fn handle_outputs( self: Box, @@ -757,7 +713,12 @@ pub enum CompileResult { /// /// The `CacheWriteFuture` will resolve when the result is finished /// being stored in the cache. - CacheMiss(MissType, DistType, Duration, SFuture), + CacheMiss( + MissType, + DistType, + Duration, + Pin> + Send>>, + ), /// Not in cache, but the compilation result was determined to be not cacheable. NotCacheable, /// Not in cache, but compilation failed. @@ -835,31 +796,32 @@ pub enum CacheControl { /// /// Note that when the `TempDir` is dropped it will delete all of its contents /// including the path returned. -pub fn write_temp_file( - pool: &ThreadPool, +pub async fn write_temp_file( + pool: &tokio::runtime::Handle, path: &Path, contents: Vec, -) -> SFuture<(TempDir, PathBuf)> { +) -> Result<(TempDir, PathBuf)> { let path = path.to_owned(); - pool.spawn_fn(move || -> Result<_> { + pool.spawn_blocking(move || { let dir = tempfile::Builder::new().prefix("sccache").tempdir()?; let src = dir.path().join(path); let mut file = File::create(&src)?; file.write_all(&contents)?; - Ok((dir, src)) + Ok::<_,anyhow::Error>((dir, src)) }) - .fcontext("failed to write temporary file") + .await? + .context("failed to write temporary file") } /// If `executable` is a known compiler, return `Some(Box)`. -fn detect_compiler( +async fn detect_compiler( creator: T, executable: &Path, cwd: &Path, env: &[(OsString, OsString)], - pool: &ThreadPool, + pool: &tokio::runtime::Handle, dist_archive: Option, -) -> SFuture<(Box>, Option>>)> +) -> Result<(Box>, Option>>)> where T: CommandCreatorSync, { @@ -867,7 +829,7 @@ where // First, see if this looks like rustc. let filename = match executable.file_stem() { - None => return f_err(anyhow!("could not determine compiler kind")), + None => bail!("could not determine compiler kind"), Some(f) => f, }; let filename = filename.to_string_lossy().to_lowercase(); @@ -878,116 +840,89 @@ where let mut child = creator.clone().new_command_sync(executable); child.env_clear().envs(ref_env(env)).args(&["-vV"]); - Box::new(run_input_output(child, None).map(|output| { + run_input_output(child, None).await.map(|output| { if let Ok(stdout) = String::from_utf8(output.stdout.clone()) { if stdout.starts_with("rustc ") { return Some(Ok(stdout)); } } Some(Err(ProcessError(output))) - })) + })? } else { - f_ok(None) + None }; - let creator1 = creator.clone(); - let creator2 = creator.clone(); let executable = executable.to_owned(); - let executable2 = executable.clone(); - let env1 = env.to_owned(); - let env2 = env.to_owned(); - let env3 = env.to_owned(); let pool = pool.clone(); - let cwd = cwd.to_owned().clone(); - Box::new( - rustc_vv - .and_then(move |rustc_vv| match rustc_vv { - Some(Ok(rustc_verbose_version)) => { - debug!("Found rustc"); - - Box::new( - RustupProxy::find_proxy_executable::(&executable2,"rustup", creator, &env1) - .and_then(move |proxy : Result>| -> SFuture<(Option, PathBuf)> { - match proxy { - Ok(Some(proxy)) => { - trace!("Found rustup proxy executable"); - let fut = - proxy - .resolve_proxied_executable(creator1, cwd, &env2) - .then(move |res| { - // take the pathbuf for rustc as resolved by the proxy - match res { - Ok((resolved_path, _time)) => { - trace!("Resolved path with rustup proxy {:?}", &resolved_path); - f_ok((Some(proxy), resolved_path)) - }, - Err(e) => { - trace!("Could not resolve compiler with rustup proxy: {}", e); - f_ok((None, executable)) - }, - } - }); - Box::new(fut) - }, - Ok(None) => { - trace!("Did not find rustup"); - f_ok((None, executable)) - }, - Err(e) => { - trace!("Did not find rustup due to {}", e); - f_ok((None, executable)) - }, - } - }) - .then(move |res: Result<(Option, PathBuf)>| { - let (proxy, resolved_rustc) : (_, PathBuf) - = res - .map(|(proxy,resolved_compiler_executable)| { - ( - proxy.map(Box::new).map(|x : Box| { - x as Box> - }), - resolved_compiler_executable - ) - }) - .unwrap_or_else(|_e| { - trace!("Compiling rust without proxy"); - (None, executable2) - }); - - Rust::new( - creator2, - resolved_rustc, - &env3, - &rustc_verbose_version, - dist_archive, - pool, - ) - .map(|c| { - ( - Box::new(c) as Box >, - proxy as Option>> - ) - }) + let cwd = cwd.to_owned(); + match rustc_vv { + Some(Ok(rustc_verbose_version)) => { + debug!("Found rustc"); + + let proxy = RustupProxy::find_proxy_executable::( + &executable, + "rustup", + creator.clone(), + &env, + ) + .await?; + + let (proxy, resolved_rustc) = match proxy { + Ok(Some(proxy)) => { + trace!("Found rustup proxy executable"); + // take the pathbuf for rustc as resolved by the proxy + match proxy.resolve_proxied_executable(creator.clone(), cwd, &env).await { + Ok((resolved_path, _time)) => { + trace!("Resolved path with rustup proxy {:?}", &resolved_path); + let proxy = Box::new(proxy) as Box>; + (Some(proxy), resolved_path) + } + Err(e) => { + trace!("Could not resolve compiler with rustup proxy: {}", e); + (None, executable) + } } - ) + } + Ok(None) => { + trace!("Did not find rustup"); + (None, executable) + } + Err(e) => { + trace!("Did not find rustup due to {}, compiling without proxy", e); + (None, executable) + }, + }; + + Rust::new( + creator, + resolved_rustc, + &env, + &rustc_verbose_version, + dist_archive, + pool, ) - } - Some(Err(e)) => f_err(e), - None => { - let cc = detect_c_compiler(creator, executable, env1.to_vec(), pool); - Box::new(cc.map(|c : Box>| { (c, None ) })) - }, - }) - ) + .await + .map(|c| { + ( + Box::new(c) as Box>, + proxy as Option>>, + ) + }) + } + Some(Err(e)) => Err(e).context("Failed to launch subprocess for compiler determination"), + None => { + let cc = detect_c_compiler(creator, executable, env.to_vec(), pool).await; + cc.map(|c| (c, None)) + } + } } -fn detect_c_compiler( +async fn detect_c_compiler( creator: T, executable: PathBuf, env: Vec<(OsString, OsString)>, - pool: ThreadPool, -) -> SFuture>> + pool: tokio::runtime::Handle, +) -> Result>> where T: CommandCreatorSync, { @@ -1014,149 +949,137 @@ diab #endif " .to_vec(); - let write = write_temp_file(&pool, "testfile.c".as_ref(), test); + let (tempdir, src) = write_temp_file(&pool, "testfile.c".as_ref(), test).await?; let mut cmd = creator.clone().new_command_sync(&executable); cmd.stdout(Stdio::piped()) .stderr(Stdio::piped()) .envs(env.iter().map(|s| (&s.0, &s.1))); - let output = write.and_then(move |(tempdir, src)| { - cmd.arg("-E").arg(src); - trace!("compiler {:?}", cmd); - cmd.spawn() - .and_then(|child| { - child - .wait_with_output() - .fcontext("failed to read child output") - }) - .map(|e| { - drop(tempdir); - e - }) - }); - Box::new(output.and_then(move |output| -> SFuture<_> { - let stdout = match str::from_utf8(&output.stdout) { - Ok(s) => s, - Err(_) => return f_err(anyhow!("Failed to parse output")), - }; - for line in stdout.lines() { - //TODO: do something smarter here. - match line { - "clang" | "clang++" => { - debug!("Found {}", line); - return Box::new( - CCompiler::new( - Clang { - clangplusplus: line == "clang++", - }, - executable, - &pool, - ) - .map(|c| Box::new(c) as Box>), - ); - } - "diab" => { - debug!("Found diab"); - return Box::new( - CCompiler::new(Diab, executable, &pool) - .map(|c| Box::new(c) as Box>), - ); - } - "gcc" | "g++" => { - debug!("Found {}", line); - return Box::new( - CCompiler::new( - GCC { - gplusplus: line == "g++", - }, - executable, - &pool, - ) - .map(|c| Box::new(c) as Box>), - ); - } - "msvc" | "msvc-clang" => { - let is_clang = line == "msvc-clang"; - debug!("Found MSVC (is clang: {})", is_clang); - let prefix = msvc::detect_showincludes_prefix( - &creator, - executable.as_ref(), + cmd.arg("-E").arg(src); + trace!("compiler {:?}", cmd); + let child = cmd.spawn().await?; + let output = child + .wait_with_output() + .await + .context("failed to read child output")?; + + drop(tempdir); + + let stdout = match str::from_utf8(&output.stdout) { + Ok(s) => s, + Err(_) => bail!("Failed to parse output"), + }; + for line in stdout.lines() { + //TODO: do something smarter here. + match line { + "clang" | "clang++" => { + debug!("Found {}", line); + return CCompiler::new( + Clang { + clangplusplus: line == "clang++", + }, + executable, + &pool, + ) + .await + .map(|c| Box::new(c) as Box>); + } + "diab" => { + debug!("Found diab"); + return CCompiler::new(Diab, executable, &pool) + .await + .map(|c| Box::new(c) as Box>); + } + "gcc" | "g++" => { + debug!("Found {}", line); + return CCompiler::new( + GCC { + gplusplus: line == "g++", + }, + executable, + &pool, + ) + .await + .map(|c| Box::new(c) as Box>); + } + "msvc" | "msvc-clang" => { + let is_clang = line == "msvc-clang"; + debug!("Found MSVC (is clang: {})", is_clang); + let prefix = msvc::detect_showincludes_prefix( + &creator, + executable.as_ref(), + is_clang, + env, + &pool, + ) + .await?; + trace!("showIncludes prefix: '{}'", prefix); + return CCompiler::new( + MSVC { + includes_prefix: prefix, is_clang, - env, - &pool, - ); - return Box::new(prefix.and_then(move |prefix| { - trace!("showIncludes prefix: '{}'", prefix); - CCompiler::new( - MSVC { - includes_prefix: prefix, - is_clang, - }, - executable, - &pool, - ) - .map(|c| Box::new(c) as Box>) - })); - } - "nvcc" => { - debug!("Found NVCC"); - return Box::new( - CCompiler::new(NVCC, executable, &pool) - .map(|c| Box::new(c) as Box>), - ); - } - _ => (), + }, + executable, + &pool, + ) + .await + .map(|c| Box::new(c) as Box>); } + "nvcc" => { + debug!("Found NVCC"); + return CCompiler::new(NVCC, executable, &pool) + .await + .map(|c| Box::new(c) as Box>); + } + _ => continue, } + } - let stderr = String::from_utf8_lossy(&output.stderr); - debug!("nothing useful in detection output {:?}", stdout); - debug!("compiler status: {}", output.status); - debug!("compiler stderr:\n{}", stderr); + let stderr = String::from_utf8_lossy(&output.stderr); + debug!("nothing useful in detection output {:?}", stdout); + debug!("compiler status: {}", output.status); + debug!("compiler stderr:\n{}", stderr); - f_err(anyhow!(stderr.into_owned())) - })) + bail!(stderr.into_owned()) } /// If `executable` is a known compiler, return a `Box` containing information about it. -pub fn get_compiler_info( +pub async fn get_compiler_info( creator: T, executable: &Path, cwd: &Path, env: &[(OsString, OsString)], - pool: &ThreadPool, + pool: &tokio::runtime::Handle, dist_archive: Option, -) -> SFuture<(Box>, Option>>)> +) -> Result<(Box>, Option>>)> where T: CommandCreatorSync, { let pool = pool.clone(); - detect_compiler(creator, executable, cwd, env, &pool, dist_archive) + detect_compiler(creator, executable, cwd, env, &pool, dist_archive).await } #[cfg(test)] mod test { use super::*; use crate::cache::disk::DiskCache; - use crate::cache::Storage; use crate::mock_command::*; use crate::test::mock_storage::MockStorage; use crate::test::utils::*; - use futures::{future, Future}; - use futures_03::executor::ThreadPool; use std::fs::{self, File}; use std::io::Write; use std::sync::Arc; use std::time::Duration; use std::u64; - use tokio_compat::runtime::current_thread::Runtime; + use tokio::runtime::Runtime; #[test] fn test_detect_compiler_kind_gcc() { let f = TestFixture::new(); let creator = new_creator(); - let pool = ThreadPool::sized(1); + let runtime = single_threaded_runtime(); + let pool = runtime.handle(); next_command( &creator, Ok(MockChild::new(exit_status(0), "foo\nbar\ngcc", "")), @@ -1172,7 +1095,8 @@ mod test { fn test_detect_compiler_kind_clang() { let f = TestFixture::new(); let creator = new_creator(); - let pool = ThreadPool::sized(1); + let runtime = single_threaded_runtime(); + let pool = runtime.handle(); next_command( &creator, Ok(MockChild::new(exit_status(0), "clang\nfoo", "")), @@ -1186,9 +1110,10 @@ mod test { #[test] fn test_detect_compiler_kind_msvc() { - drop(env_logger::try_init()); + let _ = env_logger::Builder::new().is_test(true).try_init(); let creator = new_creator(); - let pool = ThreadPool::sized(1); + let runtime = single_threaded_runtime(); + let pool = runtime.handle(); let f = TestFixture::new(); let srcfile = f.touch("test.h").unwrap(); let mut s = srcfile.to_str().unwrap(); @@ -1218,7 +1143,8 @@ mod test { fn test_detect_compiler_kind_nvcc() { let f = TestFixture::new(); let creator = new_creator(); - let pool = ThreadPool::sized(1); + let runtime = single_threaded_runtime(); + let pool = runtime.handle(); next_command( &creator, Ok(MockChild::new(exit_status(0), "nvcc\nfoo", "")), @@ -1238,7 +1164,8 @@ mod test { fs::create_dir(f.tempdir.path().join("bin")).unwrap(); let rustc = f.mk_bin("rustc").unwrap(); let creator = new_creator(); - let pool = ThreadPool::sized(1); + let runtime = single_threaded_runtime(); + let pool = runtime.handle(); // rustc --vV next_command( &creator, @@ -1271,7 +1198,8 @@ LLVM version: 6.0", fn test_detect_compiler_kind_diab() { let f = TestFixture::new(); let creator = new_creator(); - let pool = ThreadPool::sized(1); + let runtime = single_threaded_runtime(); + let pool = runtime.handle(); next_command( &creator, Ok(MockChild::new(exit_status(0), "foo\ndiab\nbar", "")), @@ -1287,7 +1215,8 @@ LLVM version: 6.0", fn test_detect_compiler_kind_unknown() { let f = TestFixture::new(); let creator = new_creator(); - let pool = ThreadPool::sized(1); + let runtime = single_threaded_runtime(); + let pool = runtime.handle(); next_command( &creator, Ok(MockChild::new(exit_status(0), "something", "")), @@ -1308,7 +1237,8 @@ LLVM version: 6.0", fn test_detect_compiler_kind_process_fail() { let f = TestFixture::new(); let creator = new_creator(); - let pool = ThreadPool::sized(1); + let runtime = single_threaded_runtime(); + let pool = runtime.handle(); next_command(&creator, Ok(MockChild::new(exit_status(1), "", ""))); assert!(detect_compiler( creator, @@ -1325,7 +1255,8 @@ LLVM version: 6.0", #[test] fn test_get_compiler_info() { let creator = new_creator(); - let pool = ThreadPool::sized(1); + let runtime = single_threaded_runtime(); + let pool = runtime.handle(); let f = TestFixture::new(); // Pretend to be GCC. next_command(&creator, Ok(MockChild::new(exit_status(0), "gcc", ""))); @@ -1339,13 +1270,13 @@ LLVM version: 6.0", #[test] fn test_compiler_get_cached_or_compile() { - drop(env_logger::try_init()); + let _ = env_logger::Builder::new().is_test(true).try_init(); let creator = new_creator(); let f = TestFixture::new(); - let pool = ThreadPool::sized(1); let mut runtime = Runtime::new().unwrap(); + let pool = runtime.handle().clone(); let storage = DiskCache::new(&f.tempdir.path().join("cache"), u64::MAX, &pool); - let storage: Arc = Arc::new(storage); + let storage = Arc::new(storage); // Pretend to be GCC. next_command(&creator, Ok(MockChild::new(exit_status(0), "gcc", ""))); let c = get_compiler_info( @@ -1387,9 +1318,9 @@ LLVM version: 6.0", }; let hasher2 = hasher.clone(); let (cached, res) = runtime - .block_on(future::lazy(|| { + .block_on(async { hasher.get_cached_or_compile( - Ok(None), + None, creator.clone(), storage.clone(), arguments.clone(), @@ -1397,14 +1328,11 @@ LLVM version: 6.0", vec![], CacheControl::Default, pool.clone(), - ) - })) + ).await + }) .unwrap(); // Ensure that the object file was created. - assert_eq!( - true, - fs::metadata(&obj).and_then(|m| Ok(m.len() > 0)).unwrap() - ); + assert!(1 <= fs::metadata(&obj).map(|m| m.len()).unwrap()); match cached { CompileResult::CacheMiss(MissType::Normal, DistType::NoDist, _, f) => { // wait on cache write future so we don't race with it! @@ -1424,9 +1352,9 @@ LLVM version: 6.0", ); // There should be no actual compiler invocation. let (cached, res) = runtime - .block_on(future::lazy(|| { + .block_on(async { hasher2.get_cached_or_compile( - Ok(None), + None, creator, storage, arguments, @@ -1434,14 +1362,11 @@ LLVM version: 6.0", vec![], CacheControl::Default, pool, - ) - })) + ).await + }) .unwrap(); // Ensure that the object file was created. - assert_eq!( - true, - fs::metadata(&obj).and_then(|m| Ok(m.len() > 0)).unwrap() - ); + assert!(1 <= fs::metadata(&obj).map(|m| m.len()).unwrap()); assert_eq!(CompileResult::CacheHit(Duration::new(0, 0)), cached); assert_eq!(exit_status(0), res.status); assert_eq!(COMPILER_STDOUT, res.stdout.as_slice()); @@ -1451,13 +1376,13 @@ LLVM version: 6.0", #[test] #[cfg(feature = "dist-client")] fn test_compiler_get_cached_or_compile_dist() { - drop(env_logger::try_init()); + let _ = env_logger::Builder::new().is_test(true).try_init(); let creator = new_creator(); let f = TestFixture::new(); - let pool = ThreadPool::sized(1); let mut runtime = Runtime::new().unwrap(); + let pool = runtime.handle().clone(); let storage = DiskCache::new(&f.tempdir.path().join("cache"), u64::MAX, &pool); - let storage: Arc = Arc::new(storage); + let storage = Arc::new(storage); // Pretend to be GCC. next_command(&creator, Ok(MockChild::new(exit_status(0), "gcc", ""))); let c = get_compiler_info( @@ -1494,9 +1419,9 @@ LLVM version: 6.0", }; let hasher2 = hasher.clone(); let (cached, res) = runtime - .block_on(future::lazy(|| { + .block_on(async { hasher.get_cached_or_compile( - Ok(dist_client.clone()), + dist_client.clone(), creator.clone(), storage.clone(), arguments.clone(), @@ -1504,14 +1429,11 @@ LLVM version: 6.0", vec![], CacheControl::Default, pool.clone(), - ) - })) + ).await + }) .unwrap(); // Ensure that the object file was created. - assert_eq!( - true, - fs::metadata(&obj).and_then(|m| Ok(m.len() > 0)).unwrap() - ); + assert!(1 <= fs::metadata(&obj).map(|m| m.len()).unwrap()); match cached { CompileResult::CacheMiss(MissType::Normal, DistType::Ok(_), _, f) => { // wait on cache write future so we don't race with it! @@ -1531,9 +1453,9 @@ LLVM version: 6.0", ); // There should be no actual compiler invocation. let (cached, res) = runtime - .block_on(future::lazy(|| { + .block_on(async { hasher2.get_cached_or_compile( - Ok(dist_client.clone()), + dist_client.clone(), creator, storage, arguments, @@ -1541,14 +1463,11 @@ LLVM version: 6.0", vec![], CacheControl::Default, pool, - ) - })) + ).await + }) .unwrap(); // Ensure that the object file was created. - assert_eq!( - true, - fs::metadata(&obj).and_then(|m| Ok(m.len() > 0)).unwrap() - ); + assert!(1 <= fs::metadata(&obj).map(|m| m.len()).unwrap()); assert_eq!(CompileResult::CacheHit(Duration::new(0, 0)), cached); assert_eq!(exit_status(0), res.status); assert_eq!(COMPILER_STDOUT, res.stdout.as_slice()); @@ -1559,11 +1478,11 @@ LLVM version: 6.0", /// Test that a cache read that results in an error is treated as a cache /// miss. fn test_compiler_get_cached_or_compile_cache_error() { - drop(env_logger::try_init()); + let _ = env_logger::Builder::new().is_test(true).try_init(); let creator = new_creator(); let f = TestFixture::new(); - let pool = ThreadPool::sized(1); let mut runtime = Runtime::new().unwrap(); + let pool = runtime.handle().clone(); let storage = MockStorage::new(); let storage: Arc = Arc::new(storage); // Pretend to be GCC. @@ -1606,11 +1525,11 @@ LLVM version: 6.0", o => panic!("Bad result from parse_arguments: {:?}", o), }; // The cache will return an error. - storage.next_get(f_err(anyhow!("Some Error"))); + storage.next_get(Err(anyhow!("Some Error"))); let (cached, res) = runtime - .block_on(future::lazy(|| { + .block_on( hasher.get_cached_or_compile( - Ok(None), + None, creator, storage, arguments.clone(), @@ -1619,17 +1538,14 @@ LLVM version: 6.0", CacheControl::Default, pool, ) - })) + ) .unwrap(); // Ensure that the object file was created. - assert_eq!( - true, - fs::metadata(&obj).and_then(|m| Ok(m.len() > 0)).unwrap() - ); + assert!(1 <= fs::metadata(&obj).map(|m| m.len()).unwrap()); match cached { CompileResult::CacheMiss(MissType::CacheReadError, DistType::NoDist, _, f) => { // wait on cache write future so we don't race with it! - f.wait().unwrap(); + let _ = f.wait(); } _ => assert!(false, "Unexpected compile result: {:?}", cached), } @@ -1641,13 +1557,13 @@ LLVM version: 6.0", #[test] fn test_compiler_get_cached_or_compile_force_recache() { - drop(env_logger::try_init()); + let _ = env_logger::Builder::new().is_test(true).try_init(); let creator = new_creator(); let f = TestFixture::new(); - let pool = ThreadPool::sized(1); - let mut runtime = Runtime::new().unwrap(); + let mut runtime = single_threaded_runtime(); + let pool = runtime.handle().clone(); let storage = DiskCache::new(&f.tempdir.path().join("cache"), u64::MAX, &pool); - let storage: Arc = Arc::new(storage); + let storage = Arc::new(storage); // Pretend to be GCC. next_command(&creator, Ok(MockChild::new(exit_status(0), "gcc", ""))); let c = get_compiler_info( @@ -1693,9 +1609,9 @@ LLVM version: 6.0", }; let hasher2 = hasher.clone(); let (cached, res) = runtime - .block_on(future::lazy(|| { + .block_on(async { hasher.get_cached_or_compile( - Ok(None), + None, creator.clone(), storage.clone(), arguments.clone(), @@ -1703,14 +1619,11 @@ LLVM version: 6.0", vec![], CacheControl::Default, pool.clone(), - ) - })) + ).await + }) .unwrap(); // Ensure that the object file was created. - assert_eq!( - true, - fs::metadata(&obj).and_then(|m| Ok(m.len() > 0)).unwrap() - ); + assert!(1 <= fs::metadata(&obj).map(|m| m.len()).unwrap()); match cached { CompileResult::CacheMiss(MissType::Normal, DistType::NoDist, _, f) => { // wait on cache write future so we don't race with it! @@ -1725,7 +1638,7 @@ LLVM version: 6.0", fs::remove_file(&obj).unwrap(); let (cached, res) = hasher2 .get_cached_or_compile( - Ok(None), + None, creator, storage, arguments, @@ -1737,10 +1650,7 @@ LLVM version: 6.0", .wait() .unwrap(); // Ensure that the object file was created. - assert_eq!( - true, - fs::metadata(&obj).and_then(|m| Ok(m.len() > 0)).unwrap() - ); + assert!(1 <= fs::metadata(&obj).map(|m| m.len()).unwrap()); match cached { CompileResult::CacheMiss(MissType::ForcedRecache, DistType::NoDist, _, f) => { // wait on cache write future so we don't race with it! @@ -1755,13 +1665,13 @@ LLVM version: 6.0", #[test] fn test_compiler_get_cached_or_compile_preprocessor_error() { - drop(env_logger::try_init()); + let _ = env_logger::Builder::new().is_test(true).try_init(); let creator = new_creator(); let f = TestFixture::new(); - let pool = ThreadPool::sized(1); - let mut runtime = Runtime::new().unwrap(); + let mut runtime = single_threaded_runtime(); + let pool = runtime.handle().clone(); let storage = DiskCache::new(&f.tempdir.path().join("cache"), u64::MAX, &pool); - let storage: Arc = Arc::new(storage); + let storage = Arc::new(storage); // Pretend to be GCC. Also inject a fake object file that the subsequent // preprocessor failure should remove. let obj = f.tempdir.path().join("foo.o"); @@ -1801,9 +1711,9 @@ LLVM version: 6.0", o => panic!("Bad result from parse_arguments: {:?}", o), }; let (cached, res) = runtime - .block_on(future::lazy(|| { + .block_on(async { hasher.get_cached_or_compile( - Ok(None), + None, creator, storage, arguments, @@ -1811,8 +1721,8 @@ LLVM version: 6.0", vec![], CacheControl::Default, pool, - ) - })) + ).await + }) .unwrap(); assert_eq!(cached, CompileResult::Error); assert_eq!(exit_status(1), res.status); @@ -1826,10 +1736,11 @@ LLVM version: 6.0", #[test] #[cfg(feature = "dist-client")] fn test_compiler_get_cached_or_compile_dist_error() { - drop(env_logger::try_init()); + let _ = env_logger::Builder::new().is_test(true).try_init(); let creator = new_creator(); let f = TestFixture::new(); - let pool = ThreadPool::sized(1); + let runtime = Runtime::new().unwrap(); + let pool = runtime.handle().clone(); let dist_clients = vec![ test_dist::ErrorPutToolchainClient::new(), test_dist::ErrorAllocJobClient::new(), @@ -1837,7 +1748,7 @@ LLVM version: 6.0", test_dist::ErrorRunJobClient::new(), ]; let storage = DiskCache::new(&f.tempdir.path().join("cache"), u64::MAX, &pool); - let storage: Arc = Arc::new(storage); + let storage = Arc::new(storage); // Pretend to be GCC. next_command(&creator, Ok(MockChild::new(exit_status(0), "gcc", ""))); let c = get_compiler_info( @@ -1889,7 +1800,7 @@ LLVM version: 6.0", let hasher = hasher.clone(); let (cached, res) = hasher .get_cached_or_compile( - Ok(Some(dist_client.clone())), + Some(dist_client.clone()), creator.clone(), storage.clone(), arguments.clone(), @@ -1899,18 +1810,16 @@ LLVM version: 6.0", pool.clone(), ) .wait() - .unwrap(); + .expect("Does not error if storage put fails. qed"); // Ensure that the object file was created. - assert_eq!( - true, - fs::metadata(&obj).and_then(|m| Ok(m.len() > 0)).unwrap() - ); + assert!(1 <= fs::metadata(&obj).map(|m| m.len()).unwrap()); + match cached { CompileResult::CacheMiss(MissType::ForcedRecache, DistType::Error, _, f) => { // wait on cache write future so we don't race with it! f.wait().unwrap(); } - _ => assert!(false, "Unexpected compile result: {:?}", cached), + _ => panic!("Unexpected compile result: {:?}", cached), } assert_eq!(exit_status(0), res.status); assert_eq!(COMPILER_STDOUT, res.stdout.as_slice()); @@ -1928,9 +1837,8 @@ mod test_dist { PathTransformer, ProcessOutput, RunJobResult, SchedulerStatusResult, ServerId, SubmitToolchainResult, Toolchain, }; - use std::cell::Cell; - use std::path::{Path, PathBuf}; - use std::sync::Arc; + use std::path::PathBuf; + use std::sync::{Arc, atomic::AtomicBool}; use crate::errors::*; @@ -1941,32 +1849,33 @@ mod test_dist { Arc::new(ErrorPutToolchainClient) } } + #[async_trait::async_trait] impl dist::Client for ErrorPutToolchainClient { - fn do_alloc_job(&self, _: Toolchain) -> SFuture { + async fn do_alloc_job(&self, _: Toolchain) -> Result { unreachable!() } - fn do_get_status(&self) -> SFuture { + async fn do_get_status(&self) -> Result { unreachable!() } - fn do_submit_toolchain(&self, _: JobAlloc, _: Toolchain) -> SFuture { + async fn do_submit_toolchain(&self, _: JobAlloc, _: Toolchain) -> Result { unreachable!() } - fn do_run_job( + async fn do_run_job( &self, _: JobAlloc, _: CompileCommand, _: Vec, _: Box, - ) -> SFuture<(RunJobResult, PathTransformer)> { + ) -> Result<(RunJobResult, PathTransformer)> { unreachable!() } - fn put_toolchain( + async fn put_toolchain( &self, - _: &Path, - _: &str, + _: PathBuf, + _: String, _: Box, - ) -> SFuture<(Toolchain, Option<(String, PathBuf)>)> { - f_err(anyhow!("put toolchain failure")) + ) -> Result<(Toolchain, Option<(String, PathBuf)>)> { + Err(anyhow!("MOCK: put toolchain failure")) } fn rewrite_includes_only(&self) -> bool { false @@ -1989,33 +1898,34 @@ mod test_dist { }) } } + #[async_trait::async_trait] impl dist::Client for ErrorAllocJobClient { - fn do_alloc_job(&self, tc: Toolchain) -> SFuture { + async fn do_alloc_job(&self, tc: Toolchain) -> Result { assert_eq!(self.tc, tc); - f_err(anyhow!("alloc job failure")) + Err(anyhow!("MOCK: alloc job failure")) } - fn do_get_status(&self) -> SFuture { + async fn do_get_status(&self) -> Result { unreachable!() } - fn do_submit_toolchain(&self, _: JobAlloc, _: Toolchain) -> SFuture { + async fn do_submit_toolchain(&self, _: JobAlloc, _: Toolchain) -> Result { unreachable!() } - fn do_run_job( + async fn do_run_job( &self, _: JobAlloc, _: CompileCommand, _: Vec, _: Box, - ) -> SFuture<(RunJobResult, PathTransformer)> { + ) -> Result<(RunJobResult, PathTransformer)> { unreachable!() } - fn put_toolchain( + async fn put_toolchain( &self, - _: &Path, - _: &str, + _: PathBuf, + _: String, _: Box, - ) -> SFuture<(Toolchain, Option<(String, PathBuf)>)> { - f_ok((self.tc.clone(), None)) + ) -> Result<(Toolchain, Option<(String, PathBuf)>)> { + Ok((self.tc.clone(), None)) } fn rewrite_includes_only(&self) -> bool { false @@ -2026,25 +1936,27 @@ mod test_dist { } pub struct ErrorSubmitToolchainClient { - has_started: Cell, + has_started: AtomicBool, tc: Toolchain, } impl ErrorSubmitToolchainClient { #[allow(clippy::new_ret_no_self)] pub fn new() -> Arc { Arc::new(Self { - has_started: Cell::new(false), + has_started: AtomicBool::default(), tc: Toolchain { archive_id: "somearchiveid".to_owned(), }, }) } } + + #[async_trait::async_trait] impl dist::Client for ErrorSubmitToolchainClient { - fn do_alloc_job(&self, tc: Toolchain) -> SFuture { - assert!(!self.has_started.replace(true)); + async fn do_alloc_job(&self, tc: Toolchain) -> Result { + assert!(!self.has_started.swap(true, std::sync::atomic::Ordering::AcqRel)); assert_eq!(self.tc, tc); - f_ok(AllocJobResult::Success { + Ok(AllocJobResult::Success { job_alloc: JobAlloc { auth: "abcd".to_owned(), job_id: JobId(0), @@ -2053,34 +1965,34 @@ mod test_dist { need_toolchain: true, }) } - fn do_get_status(&self) -> SFuture { - unreachable!() + async fn do_get_status(&self) -> Result { + unreachable!("fn do_get_status is not used for this test. qed") } - fn do_submit_toolchain( + async fn do_submit_toolchain( &self, job_alloc: JobAlloc, tc: Toolchain, - ) -> SFuture { + ) -> Result { assert_eq!(job_alloc.job_id, JobId(0)); assert_eq!(self.tc, tc); - f_err(anyhow!("submit toolchain failure")) + Err(anyhow!("MOCK: submit toolchain failure")) } - fn do_run_job( + async fn do_run_job( &self, _: JobAlloc, _: CompileCommand, _: Vec, _: Box, - ) -> SFuture<(RunJobResult, PathTransformer)> { - unreachable!() + ) -> Result<(RunJobResult, PathTransformer)> { + unreachable!("fn do_run_job is not used for this test. qed") } - fn put_toolchain( + async fn put_toolchain( &self, - _: &Path, - _: &str, + _: PathBuf, + _: String, _: Box, - ) -> SFuture<(Toolchain, Option<(String, PathBuf)>)> { - f_ok((self.tc.clone(), None)) + ) -> Result<(Toolchain, Option<(String, PathBuf)>)> { + Ok((self.tc.clone(), None)) } fn rewrite_includes_only(&self) -> bool { false @@ -2091,25 +2003,27 @@ mod test_dist { } pub struct ErrorRunJobClient { - has_started: Cell, + has_started: AtomicBool, tc: Toolchain, } impl ErrorRunJobClient { #[allow(clippy::new_ret_no_self)] pub fn new() -> Arc { Arc::new(Self { - has_started: Cell::new(false), + has_started: AtomicBool::default(), tc: Toolchain { archive_id: "somearchiveid".to_owned(), }, }) } } + + #[async_trait::async_trait] impl dist::Client for ErrorRunJobClient { - fn do_alloc_job(&self, tc: Toolchain) -> SFuture { - assert!(!self.has_started.replace(true)); + async fn do_alloc_job(&self, tc: Toolchain) -> Result { + assert!(!self.has_started.swap(true, std::sync::atomic::Ordering::AcqRel)); assert_eq!(self.tc, tc); - f_ok(AllocJobResult::Success { + Ok(AllocJobResult::Success { job_alloc: JobAlloc { auth: "abcd".to_owned(), job_id: JobId(0), @@ -2118,36 +2032,36 @@ mod test_dist { need_toolchain: true, }) } - fn do_get_status(&self) -> SFuture { + async fn do_get_status(&self) -> Result { unreachable!() } - fn do_submit_toolchain( + async fn do_submit_toolchain( &self, job_alloc: JobAlloc, tc: Toolchain, - ) -> SFuture { + ) -> Result { assert_eq!(job_alloc.job_id, JobId(0)); assert_eq!(self.tc, tc); - f_ok(SubmitToolchainResult::Success) + Ok(SubmitToolchainResult::Success) } - fn do_run_job( + async fn do_run_job( &self, job_alloc: JobAlloc, command: CompileCommand, _: Vec, _: Box, - ) -> SFuture<(RunJobResult, PathTransformer)> { + ) -> Result<(RunJobResult, PathTransformer)> { assert_eq!(job_alloc.job_id, JobId(0)); assert_eq!(command.executable, "/overridden/compiler"); - f_err(anyhow!("run job failure")) + Err(anyhow!("MOCK: run job failure")) } - fn put_toolchain( + async fn put_toolchain( &self, - _: &Path, - _: &str, + _: PathBuf, + _: String, _: Box, - ) -> SFuture<(Toolchain, Option<(String, PathBuf)>)> { - f_ok(( + ) -> Result<(Toolchain, Option<(String, PathBuf)>)> { + Ok(( self.tc.clone(), Some(( "/overridden/compiler".to_owned(), @@ -2164,7 +2078,7 @@ mod test_dist { } pub struct OneshotClient { - has_started: Cell, + has_started: AtomicBool, tc: Toolchain, output: ProcessOutput, } @@ -2173,7 +2087,7 @@ mod test_dist { #[allow(clippy::new_ret_no_self)] pub fn new(code: i32, stdout: Vec, stderr: Vec) -> Arc { Arc::new(Self { - has_started: Cell::new(false), + has_started: AtomicBool::default(), tc: Toolchain { archive_id: "somearchiveid".to_owned(), }, @@ -2182,12 +2096,13 @@ mod test_dist { } } + #[async_trait::async_trait] impl dist::Client for OneshotClient { - fn do_alloc_job(&self, tc: Toolchain) -> SFuture { - assert!(!self.has_started.replace(true)); + async fn do_alloc_job(&self, tc: Toolchain) -> Result { + assert!(!self.has_started.swap(true, std::sync::atomic::Ordering::AcqRel)); assert_eq!(self.tc, tc); - f_ok(AllocJobResult::Success { + Ok(AllocJobResult::Success { job_alloc: JobAlloc { auth: "abcd".to_owned(), job_id: JobId(0), @@ -2196,26 +2111,26 @@ mod test_dist { need_toolchain: true, }) } - fn do_get_status(&self) -> SFuture { - unreachable!() + async fn do_get_status(&self) -> Result { + unreachable!("fn do_get_status is not used for this test. qed") } - fn do_submit_toolchain( + async fn do_submit_toolchain( &self, job_alloc: JobAlloc, tc: Toolchain, - ) -> SFuture { + ) -> Result { assert_eq!(job_alloc.job_id, JobId(0)); assert_eq!(self.tc, tc); - f_ok(SubmitToolchainResult::Success) + Ok(SubmitToolchainResult::Success) } - fn do_run_job( + async fn do_run_job( &self, job_alloc: JobAlloc, command: CompileCommand, outputs: Vec, inputs_packager: Box, - ) -> SFuture<(RunJobResult, PathTransformer)> { + ) -> Result<(RunJobResult, PathTransformer)> { assert_eq!(job_alloc.job_id, JobId(0)); assert_eq!(command.executable, "/overridden/compiler"); @@ -2233,15 +2148,16 @@ mod test_dist { output: self.output.clone(), outputs, }); - f_ok((result, path_transformer)) + Ok((result, path_transformer)) } - fn put_toolchain( + async fn put_toolchain( &self, - _: &Path, - _: &str, + _: PathBuf, + _: String, _: Box, - ) -> SFuture<(Toolchain, Option<(String, PathBuf)>)> { - f_ok(( + ) -> Result<(Toolchain, Option<(String, PathBuf)>)> { + + Ok(( self.tc.clone(), Some(( "/overridden/compiler".to_owned(), diff --git a/src/compiler/diab.rs b/src/compiler/diab.rs index ae02bd522..a8b714161 100644 --- a/src/compiler/diab.rs +++ b/src/compiler/diab.rs @@ -34,6 +34,7 @@ use std::process; #[derive(Clone, Debug)] pub struct Diab; +#[async_trait::async_trait] impl CCompilerImpl for Diab { fn kind(&self) -> CCompilerKind { CCompilerKind::Diab @@ -49,7 +50,7 @@ impl CCompilerImpl for Diab { parse_arguments(arguments, cwd, &ARGS[..]) } - fn preprocess( + async fn preprocess( &self, creator: &T, executable: &Path, @@ -58,11 +59,11 @@ impl CCompilerImpl for Diab { env_vars: &[(OsString, OsString)], may_dist: bool, _rewrite_includes_only: bool, - ) -> SFuture + ) -> Result where T: CommandCreatorSync, { - preprocess(creator, executable, parsed_args, cwd, env_vars, may_dist) + preprocess(creator, executable, parsed_args, cwd, env_vars, may_dist).await } fn generate_compile_commands( @@ -284,14 +285,14 @@ where }) } -pub fn preprocess( +pub async fn preprocess( creator: &T, executable: &Path, parsed_args: &ParsedArguments, cwd: &Path, env_vars: &[(OsString, OsString)], _may_dist: bool, -) -> SFuture +) -> Result where T: CommandCreatorSync, { @@ -308,7 +309,7 @@ where if log_enabled!(Trace) { trace!("preprocess: {:?}", cmd); } - Box::new(run_input_output(cmd, None)) + run_input_output(cmd, None).await } pub fn generate_compile_commands( @@ -422,7 +423,6 @@ mod test { use crate::compiler::*; use crate::mock_command::*; use crate::test::utils::*; - use futures::Future; use std::fs::File; use std::io::Write; diff --git a/src/compiler/gcc.rs b/src/compiler/gcc.rs index dec61e05c..ba24f2912 100644 --- a/src/compiler/gcc.rs +++ b/src/compiler/gcc.rs @@ -34,6 +34,7 @@ pub struct GCC { pub gplusplus: bool, } +#[async_trait] impl CCompilerImpl for GCC { fn kind(&self) -> CCompilerKind { CCompilerKind::GCC @@ -49,7 +50,7 @@ impl CCompilerImpl for GCC { parse_arguments(arguments, cwd, &ARGS[..], self.gplusplus) } - fn preprocess( + async fn preprocess( &self, creator: &T, executable: &Path, @@ -58,7 +59,7 @@ impl CCompilerImpl for GCC { env_vars: &[(OsString, OsString)], may_dist: bool, rewrite_includes_only: bool, - ) -> SFuture + ) -> Result where T: CommandCreatorSync, { @@ -72,6 +73,7 @@ impl CCompilerImpl for GCC { self.kind(), rewrite_includes_only, ) + .await } fn generate_compile_commands( @@ -484,7 +486,7 @@ where } #[allow(clippy::too_many_arguments)] -pub fn preprocess( +pub async fn preprocess( creator: &T, executable: &Path, parsed_args: &ParsedArguments, @@ -493,7 +495,7 @@ pub fn preprocess( may_dist: bool, kind: CCompilerKind, rewrite_includes_only: bool, -) -> SFuture +) -> Result where T: CommandCreatorSync, { @@ -536,7 +538,7 @@ where if log_enabled!(Trace) { trace!("preprocess: {:?}", cmd); } - Box::new(run_input_output(cmd, None)) + run_input_output(cmd, None).await } pub fn generate_compile_commands( @@ -725,7 +727,6 @@ mod test { use crate::compiler::*; use crate::mock_command::*; use crate::test::utils::*; - use futures::Future; fn parse_arguments_( arguments: Vec, diff --git a/src/compiler/msvc.rs b/src/compiler/msvc.rs index b0cefb181..9cbc6f630 100644 --- a/src/compiler/msvc.rs +++ b/src/compiler/msvc.rs @@ -19,9 +19,7 @@ use crate::compiler::{ }; use crate::dist; use crate::mock_command::{CommandCreatorSync, RunCommand}; -use crate::util::{run_input_output, SpawnExt}; -use futures::future::Future; -use futures_03::executor::ThreadPool; +use crate::util::run_input_output; use local_encoding::{Encoder, Encoding}; use log::Level::Debug; use std::collections::{HashMap, HashSet}; @@ -43,6 +41,7 @@ pub struct MSVC { pub is_clang: bool, } +#[async_trait] impl CCompilerImpl for MSVC { fn kind(&self) -> CCompilerKind { CCompilerKind::MSVC @@ -58,7 +57,7 @@ impl CCompilerImpl for MSVC { parse_arguments(arguments, cwd, self.is_clang) } - fn preprocess( + async fn preprocess( &self, creator: &T, executable: &Path, @@ -67,7 +66,7 @@ impl CCompilerImpl for MSVC { env_vars: &[(OsString, OsString)], may_dist: bool, _rewrite_includes_only: bool, - ) -> SFuture + ) -> Result where T: CommandCreatorSync, { @@ -80,6 +79,7 @@ impl CCompilerImpl for MSVC { may_dist, &self.includes_prefix, ) + .await } fn generate_compile_commands( @@ -100,94 +100,89 @@ fn from_local_codepage(bytes: &[u8]) -> io::Result { } /// Detect the prefix included in the output of MSVC's -showIncludes output. -pub fn detect_showincludes_prefix( +pub async fn detect_showincludes_prefix( creator: &T, exe: &OsStr, is_clang: bool, env: Vec<(OsString, OsString)>, - pool: &ThreadPool, -) -> SFuture + pool: &tokio::runtime::Handle, +) -> Result where T: CommandCreatorSync, { - let write = write_temp_file(pool, "test.c".as_ref(), b"#include \"test.h\"\n".to_vec()); + let (tempdir, input) = + write_temp_file(pool, "test.c".as_ref(), b"#include \"test.h\"\n".to_vec()).await?; let exe = exe.to_os_string(); let mut creator = creator.clone(); let pool = pool.clone(); - let write2 = write.and_then(move |(tempdir, input)| { - let header = tempdir.path().join("test.h"); - pool.spawn_fn(move || -> Result<_> { + + let header = tempdir.path().join("test.h"); + let tempdir = pool + .spawn_blocking(move || { let mut file = File::create(&header)?; file.write_all(b"/* empty */\n")?; - Ok((tempdir, input)) + Ok::<_, std::io::Error>(tempdir) }) - .fcontext("failed to write temporary file") - }); - let output = write2.and_then(move |(tempdir, input)| { - let mut cmd = creator.new_command_sync(&exe); - // clang.exe on Windows reports the same set of built-in preprocessor defines as clang-cl, - // but it doesn't accept MSVC commandline arguments unless you pass --driver-mode=cl. - // clang-cl.exe will accept this argument as well, so always add it in this case. - if is_clang { - cmd.arg("--driver-mode=cl"); - } - cmd.args(&["-nologo", "-showIncludes", "-c", "-Fonul", "-I."]) - .arg(&input) - .current_dir(&tempdir.path()) - // The MSDN docs say the -showIncludes output goes to stderr, - // but that's not true unless running with -E. - .stdout(Stdio::piped()) - .stderr(Stdio::null()); - for (k, v) in env { - cmd.env(k, v); - } - trace!("detect_showincludes_prefix: {:?}", cmd); + .await? + .context("Failed to write temporary file")?; + + let mut cmd = creator.new_command_sync(&exe); + // clang.exe on Windows reports the same set of built-in preprocessor defines as clang-cl, + // but it doesn't accept MSVC commandline arguments unless you pass --driver-mode=cl. + // clang-cl.exe will accept this argument as well, so always add it in this case. + if is_clang { + cmd.arg("--driver-mode=cl"); + } + cmd.args(&["-nologo", "-showIncludes", "-c", "-Fonul", "-I."]) + .arg(&input) + .current_dir(&tempdir.path()) + // The MSDN docs say the -showIncludes output goes to stderr, + // but that's not true unless running with -E. + .stdout(Stdio::piped()) + .stderr(Stdio::null()); + for (k, v) in env { + cmd.env(k, v); + } + trace!("detect_showincludes_prefix: {:?}", cmd); - run_input_output(cmd, None).map(|e| { - // Keep the tempdir around so test.h still exists for the - // checks below. - (e, tempdir) - }) - }); + let output = run_input_output(cmd, None).await?; - Box::new(output.and_then(|(output, tempdir)| { - if !output.status.success() { - bail!("Failed to detect showIncludes prefix") - } + if !output.status.success() { + bail!("Failed to detect showIncludes prefix") + } - let process::Output { - stdout: stdout_bytes, - .. - } = output; - let stdout = from_local_codepage(&stdout_bytes) - .context("Failed to convert compiler stdout while detecting showIncludes prefix")?; - for line in stdout.lines() { - if !line.ends_with("test.h") { + let process::Output { + stdout: stdout_bytes, + .. + } = output; + let stdout = from_local_codepage(&stdout_bytes) + .context("Failed to convert compiler stdout while detecting showIncludes prefix")?; + for line in stdout.lines() { + if !line.ends_with("test.h") { + continue; + } + for (i, c) in line.char_indices().rev() { + if c != ' ' { continue; } - for (i, c) in line.char_indices().rev() { - if c != ' ' { - continue; - } - let path = tempdir.path().join(&line[i + 1..]); - // See if the rest of this line is a full pathname. - if path.exists() { - // Everything from the beginning of the line - // to this index is the prefix. - return Ok(line[..=i].to_owned()); - } + let path = tempdir.path().join(&line[i + 1..]); + // See if the rest of this line is a full pathname. + if path.exists() { + // Everything from the beginning of the line + // to this index is the prefix. + return Ok(line[..=i].to_owned()); } } - drop(tempdir); + } + drop(tempdir); - debug!( - "failed to detect showIncludes prefix with output: {}", - stdout - ); + debug!( + "failed to detect showIncludes prefix with output: {}", + stdout + ); - bail!("Failed to detect showIncludes prefix") - })) + bail!("Failed to detect showIncludes prefix") } #[cfg(unix)] @@ -684,7 +679,7 @@ fn normpath(path: &str) -> String { path.to_owned() } -pub fn preprocess( +pub async fn preprocess( creator: &T, executable: &Path, parsed_args: &ParsedArguments, @@ -692,7 +687,7 @@ pub fn preprocess( env_vars: &[(OsString, OsString)], _may_dist: bool, includes_prefix: &str, -) -> SFuture +) -> Result where T: CommandCreatorSync, { @@ -718,65 +713,65 @@ where let includes_prefix = includes_prefix.to_string(); let cwd = cwd.to_owned(); - Box::new(run_input_output(cmd, None).and_then(move |output| { - let parsed_args = &parsed_args; - if let (Some(ref objfile), &Some(ref depfile)) = - (parsed_args.outputs.get("obj"), &parsed_args.depfile) - { - let f = File::create(cwd.join(depfile))?; - let mut f = BufWriter::new(f); - - encode_path(&mut f, &objfile) - .with_context(|| format!("Couldn't encode objfile filename: '{:?}'", objfile))?; - write!(f, ": ")?; - encode_path(&mut f, &parsed_args.input) - .with_context(|| format!("Couldn't encode input filename: '{:?}'", objfile))?; - write!(f, " ")?; - let process::Output { - status, - stdout, - stderr: stderr_bytes, - } = output; - let stderr = from_local_codepage(&stderr_bytes) - .context("Failed to convert preprocessor stderr")?; - let mut deps = HashSet::new(); - let mut stderr_bytes = vec![]; - for line in stderr.lines() { - if line.starts_with(&includes_prefix) { - let dep = normpath(line[includes_prefix.len()..].trim()); - trace!("included: {}", dep); - if deps.insert(dep.clone()) && !dep.contains(' ') { - write!(f, "{} ", dep)?; - } - if !parsed_args.msvc_show_includes { - continue; - } + let output = run_input_output(cmd, None).await?; + + let parsed_args = &parsed_args; + if let (Some(ref objfile), &Some(ref depfile)) = + (parsed_args.outputs.get("obj"), &parsed_args.depfile) + { + let f = File::create(cwd.join(depfile))?; + let mut f = BufWriter::new(f); + + encode_path(&mut f, &objfile) + .with_context(|| format!("Couldn't encode objfile filename: '{:?}'", objfile))?; + write!(f, ": ")?; + encode_path(&mut f, &parsed_args.input) + .with_context(|| format!("Couldn't encode input filename: '{:?}'", objfile))?; + write!(f, " ")?; + let process::Output { + status, + stdout, + stderr: stderr_bytes, + } = output; + let stderr = + from_local_codepage(&stderr_bytes).context("Failed to convert preprocessor stderr")?; + let mut deps = HashSet::new(); + let mut stderr_bytes = vec![]; + for line in stderr.lines() { + if line.starts_with(&includes_prefix) { + let dep = normpath(line[includes_prefix.len()..].trim()); + trace!("included: {}", dep); + if deps.insert(dep.clone()) && !dep.contains(' ') { + write!(f, "{} ", dep)?; } - stderr_bytes.extend_from_slice(line.as_bytes()); - stderr_bytes.push(b'\n'); - } - writeln!(f)?; - // Write extra rules for each dependency to handle - // removed files. - encode_path(&mut f, &parsed_args.input) - .with_context(|| format!("Couldn't encode filename: '{:?}'", parsed_args.input))?; - writeln!(f, ":")?; - let mut sorted = deps.into_iter().collect::>(); - sorted.sort(); - for dep in sorted { - if !dep.contains(' ') { - writeln!(f, "{}:", dep)?; + if !parsed_args.msvc_show_includes { + continue; } } - Ok(process::Output { - status, - stdout, - stderr: stderr_bytes, - }) - } else { - Ok(output) + stderr_bytes.extend_from_slice(line.as_bytes()); + stderr_bytes.push(b'\n'); } - })) + writeln!(f)?; + // Write extra rules for each dependency to handle + // removed files. + encode_path(&mut f, &parsed_args.input) + .with_context(|| format!("Couldn't encode filename: '{:?}'", parsed_args.input))?; + writeln!(f, ":")?; + let mut sorted = deps.into_iter().collect::>(); + sorted.sort(); + for dep in sorted { + if !dep.contains(' ') { + writeln!(f, "{}:", dep)?; + } + } + Ok(process::Output { + status, + stdout, + stderr: stderr_bytes, + }) + } else { + Ok(output) + } } fn generate_compile_commands( @@ -865,8 +860,6 @@ mod test { use crate::env; use crate::mock_command::*; use crate::test::utils::*; - use futures::Future; - use futures_03::executor::ThreadPool; fn parse_arguments(arguments: Vec) -> CompilerArguments { super::parse_arguments(&arguments, &env::current_dir().unwrap(), false) @@ -874,9 +867,10 @@ mod test { #[test] fn test_detect_showincludes_prefix() { - drop(env_logger::try_init()); + let _ = env_logger::Builder::new().is_test(true).try_init(); let creator = new_creator(); - let pool = ThreadPool::sized(1); + let runtime = single_threaded_runtime(); + let pool = runtime.handle().clone(); let f = TestFixture::new(); let srcfile = f.touch("test.h").unwrap(); let mut s = srcfile.to_str().unwrap(); diff --git a/src/compiler/nvcc.rs b/src/compiler/nvcc.rs index 9c9b72c93..e4134343e 100644 --- a/src/compiler/nvcc.rs +++ b/src/compiler/nvcc.rs @@ -21,10 +21,10 @@ use crate::compiler::{gcc, write_temp_file, Cacheable, CompileCommand, CompilerA use crate::dist; use crate::mock_command::{CommandCreator, CommandCreatorSync, RunCommand}; use crate::util::{run_input_output, OsStrExt}; -use futures::future::{self, Future}; use log::Level::Trace; use std::ffi::OsString; use std::fs::File; +use std::future::Future; use std::io::{self, Write}; use std::path::{Path, PathBuf}; use std::process; @@ -35,6 +35,7 @@ use crate::errors::*; #[derive(Clone, Debug)] pub struct NVCC; +#[async_trait] impl CCompilerImpl for NVCC { fn kind(&self) -> CCompilerKind { CCompilerKind::NVCC @@ -50,7 +51,7 @@ impl CCompilerImpl for NVCC { gcc::parse_arguments(arguments, cwd, (&gcc::ARGS[..], &ARGS[..]), false) } - fn preprocess( + async fn preprocess( &self, creator: &T, executable: &Path, @@ -59,7 +60,7 @@ impl CCompilerImpl for NVCC { env_vars: &[(OsString, OsString)], may_dist: bool, rewrite_includes_only: bool, - ) -> SFuture + ) -> Result where T: CommandCreatorSync, { @@ -82,7 +83,7 @@ impl CCompilerImpl for NVCC { } command.arg("-x").arg(language).arg(&parsed_args.input); - return command; + command }; let dep_before_preprocessor = || { @@ -109,7 +110,7 @@ impl CCompilerImpl for NVCC { if log_enabled!(Trace) { trace!("dep-gen command: {:?}", dep_cmd); } - return dep_cmd; + dep_cmd }; trace!("preprocess"); @@ -128,12 +129,16 @@ impl CCompilerImpl for NVCC { //Need to chain the dependency generation and the preprocessor //to emulate a `proper` front end - if parsed_args.dependency_args.len() > 0 { + if !parsed_args.dependency_args.is_empty() { let first = run_input_output(dep_before_preprocessor(), None); let second = run_input_output(cmd, None); - return Box::new(first.join(second).map(|(f, s)| s)); + // TODO: If we need to chain these to emulate a frontend, shouldn't + // we explicitly wait on the first one before starting the second one? + // (rather than via which drives these concurrently) + let (_f, s) = futures::future::try_join(first, second).await?; + Ok(s) } else { - return Box::new(run_input_output(cmd, None)); + run_input_output(cmd, None).await } } @@ -205,7 +210,6 @@ mod test { use crate::compiler::*; use crate::mock_command::*; use crate::test::utils::*; - use futures::Future; use std::collections::HashMap; use std::path::PathBuf; diff --git a/src/compiler/rust.rs b/src/compiler/rust.rs index e3d3e9b1a..61e62de83 100644 --- a/src/compiler/rust.rs +++ b/src/compiler/rust.rs @@ -15,7 +15,7 @@ use crate::compiler::args::*; use crate::compiler::{ Cacheable, ColorMode, Compilation, CompileCommand, Compiler, CompilerArguments, CompilerHasher, - CompilerKind, CompilerProxy, HashResult, + CompilerKind, CompilerProxy, HashResult }; #[cfg(feature = "dist-client")] use crate::compiler::{DistPackagers, OutputsRewriter}; @@ -24,10 +24,8 @@ use crate::dist; use crate::dist::pkg; use crate::mock_command::{CommandCreatorSync, RunCommand}; use crate::util::{fmt_duration_as_secs, hash_all, run_input_output, Digest}; -use crate::util::{ref_env, HashToDigest, OsStrExt, SpawnExt}; +use crate::util::{ref_env, HashToDigest, OsStrExt}; use filetime::FileTime; -use futures::Future; -use futures_03::executor::ThreadPool; use log::Level::Trace; #[cfg(feature = "dist-client")] use lru_disk_cache::{LruCache, Meter}; @@ -44,12 +42,14 @@ use std::env::consts::{DLL_PREFIX, EXE_EXTENSION}; use std::ffi::OsString; use std::fmt; use std::fs; +use std::future::Future; use std::hash::Hash; #[cfg(feature = "dist-client")] use std::io; use std::io::Read; use std::iter; use std::path::{Path, PathBuf}; +use std::pin::Pin; use std::process; #[cfg(feature = "dist-client")] use std::sync::{Arc, Mutex}; @@ -197,24 +197,24 @@ lazy_static! { const CACHE_VERSION: &[u8] = b"6"; /// Get absolute paths for all source files listed in rustc's dep-info output. -fn get_source_files( +async fn get_source_files( creator: &T, crate_name: &str, executable: &Path, arguments: &[OsString], cwd: &Path, env_vars: &[(OsString, OsString)], - pool: &ThreadPool, -) -> SFuture> + pool: &tokio::runtime::Handle, +) -> Result> where T: CommandCreatorSync, { let start = time::Instant::now(); // Get the full list of source files from rustc's dep-info. - let temp_dir = ftry!(tempfile::Builder::new() + let temp_dir = tempfile::Builder::new() .prefix("sccache") .tempdir() - .context("Failed to create temp dir")); + .context("Failed to create temp dir")?; let dep_file = temp_dir.path().join("deps.d"); let mut cmd = creator.clone().new_command_sync(executable); cmd.args(&arguments) @@ -225,29 +225,29 @@ where .envs(ref_env(env_vars)) .current_dir(cwd); trace!("[{}]: get dep-info: {:?}", crate_name, cmd); - let dep_info = run_input_output(cmd, None); + // Output of command is in file under dep_file, so we ignore stdout&stderr + let _dep_info = run_input_output(cmd, None).await?; // Parse the dep-info file, then hash the contents of those files. - let pool = pool.clone(); let cwd = cwd.to_owned(); - let crate_name = crate_name.to_owned(); - Box::new(dep_info.and_then(move |_| -> SFuture<_> { - let name2 = crate_name.clone(); - let parsed = pool.spawn_fn(move || { + let name2 = crate_name.to_owned(); + let parsed = pool + .spawn_blocking(move || { parse_dep_file(&dep_file, &cwd) .with_context(|| format!("Failed to parse dep info for {}", name2)) - }); - Box::new(parsed.map(move |files| { - trace!( - "[{}]: got {} source files from dep-info in {}", - crate_name, - files.len(), - fmt_duration_as_secs(&start.elapsed()) - ); - // Just to make sure we capture temp_dir. - drop(temp_dir); - files - })) - })) + }) + .await?; + + parsed.map(move |files| { + trace!( + "[{}]: got {} source files from dep-info in {}", + crate_name, + files.len(), + fmt_duration_as_secs(&start.elapsed()) + ); + // Just to make sure we capture temp_dir. + drop(temp_dir); + files + }) } /// Parse dependency info from `file` and return a Vec of files mentioned. @@ -315,15 +315,15 @@ where } /// Run `rustc --print file-names` to get the outputs of compilation. -fn get_compiler_outputs( +async fn get_compiler_outputs( creator: &T, executable: &Path, arguments: Vec, cwd: &Path, env_vars: &[(OsString, OsString)], -) -> SFuture> +) -> Result> where - T: CommandCreatorSync, + T: Clone + CommandCreatorSync, { let mut cmd = creator.clone().new_command_sync(executable); cmd.args(&arguments) @@ -334,37 +334,36 @@ where if log_enabled!(Trace) { trace!("get_compiler_outputs: {:?}", cmd); } - let outputs = run_input_output(cmd, None); - Box::new(outputs.and_then(move |output| -> Result<_> { - let outstr = String::from_utf8(output.stdout).context("Error parsing rustc output")?; - if log_enabled!(Trace) { - trace!("get_compiler_outputs: {:?}", outstr); - } - Ok(outstr.lines().map(|l| l.to_owned()).collect()) - })) + let outputs = run_input_output(cmd, None).await?; + + let outstr = String::from_utf8(outputs.stdout).context("Error parsing rustc output")?; + if log_enabled!(Trace) { + trace!("get_compiler_outputs: {:?}", outstr); + } + Ok(outstr.lines().map(|l| l.to_owned()).collect()) } impl Rust { /// Create a new Rust compiler instance, calculating the hashes of /// all the shared libraries in its sysroot. - pub fn new( + pub async fn new( mut creator: T, executable: PathBuf, env_vars: &[(OsString, OsString)], rustc_verbose_version: &str, dist_archive: Option, - pool: ThreadPool, - ) -> SFuture + pool: tokio::runtime::Handle, + ) -> Result where T: CommandCreatorSync, { // Taken from Cargo - let host = ftry!(rustc_verbose_version + let host = rustc_verbose_version .lines() .find(|l| l.starts_with("host: ")) .map(|l| &l[6..]) - .context("rustc verbose version didn't have a line for `host:`")) - .to_string(); + .context("rustc verbose version didn't have a line for `host:`")? + .to_string(); // it's fine to use the `executable` directly no matter if proxied or not let mut cmd = creator.new_command_sync(&executable); @@ -373,8 +372,8 @@ impl Rust { .arg("--print=sysroot") .env_clear() .envs(ref_env(env_vars)); - let output = run_input_output(cmd, None); - let sysroot_and_libs = output.and_then(move |output| -> Result<_> { + let sysroot_and_libs = async move { + let output = run_input_output(cmd, None).await?; //debug!("output.and_then: {}", output); let outstr = String::from_utf8(output.stdout).context("Error parsing sysroot")?; let sysroot = PathBuf::from(outstr.trim_end()); @@ -401,45 +400,49 @@ impl Rust { libs.push(path); }; libs.sort(); - Ok((sysroot, libs)) - }); - - #[cfg(feature = "dist-client")] - let rlib_dep_reader = { - let executable = executable.clone(); - let env_vars = env_vars.to_owned(); - pool.spawn_fn(move || Ok(RlibDepReader::new_with_check(executable, &env_vars))) + Result::Ok((sysroot, libs)) }; #[cfg(feature = "dist-client")] - return Box::new(sysroot_and_libs.join(rlib_dep_reader).and_then(move |((sysroot, libs), rlib_dep_reader)| { + { + use futures::TryFutureExt; + let rlib_dep_reader = { + let executable = executable.clone(); + let env_vars = env_vars.to_owned(); + pool.spawn_blocking(move || { + RlibDepReader::new_with_check(executable, &env_vars) + }).map_err(anyhow::Error::from) + }; + + let ((sysroot, libs), rlib_dep_reader) = + futures::future::try_join(sysroot_and_libs, rlib_dep_reader).await?; + let rlib_dep_reader = match rlib_dep_reader { Ok(r) => Some(Arc::new(r)), Err(e) => { warn!("Failed to initialise RlibDepDecoder, distributed compiles will be inefficient: {}", e); None - }, - }; - hash_all(&libs, &pool).map(move |digests| { - Rust { - executable, - host, - sysroot, - compiler_shlibs_digests: digests, - rlib_dep_reader, } + }; + hash_all(&libs, &pool).await.map(move |digests| Rust { + executable, + host, + sysroot, + compiler_shlibs_digests: digests, + rlib_dep_reader, }) - })); + } #[cfg(not(feature = "dist-client"))] - return Box::new(sysroot_and_libs.and_then(move |(sysroot, libs)| { - hash_all(&libs, &pool).map(move |digests| Rust { + { + let (sysroot, libs) = sysroot_and_libs.await?; + hash_all(&libs, &pool).await.map(move |digests| Rust { executable, host, sysroot, compiler_shlibs_digests: digests, }) - })); + } } } @@ -503,49 +506,41 @@ where mut creator: T, cwd: PathBuf, env: &[(OsString, OsString)], - ) -> SFuture<(PathBuf, FileTime)> { - let proxy_executable = self.proxy_executable.clone(); - - let mut child = creator.new_command_sync(&proxy_executable); + ) -> Pin> + Send>> { + let mut child = creator.new_command_sync(&self.proxy_executable); child .current_dir(&cwd) .env_clear() .envs(ref_env(&env)) .args(&["which", "rustc"]); - let lookup = run_input_output(child, None) - .map_err(|e| anyhow!("Failed to execute rustup which rustc: {}", e)) - .and_then(move |output| { - String::from_utf8(output.stdout.clone()) - .map_err(|e| anyhow!("Failed to parse output of rustup which rustc: {}", e)) - .and_then(|stdout| { - let proxied_compiler = PathBuf::from(stdout.trim()); - trace!( - "proxy: rustup which rustc produced: {:?}", - &proxied_compiler - ); - let res = fs::metadata(proxied_compiler.as_path()) - .map_err(|e| { - anyhow!( - "Failed to obtain metadata of the resolved, true rustc: {}", - e - ) - }) - .and_then(|attr| { - if attr.is_file() { - Ok(FileTime::from_last_modification_time(&attr)) - } else { - Err(anyhow!( - "proxy: rustup resolved compiler is not of type file" - )) - } - }) - .map(|filetime| (proxied_compiler, filetime)); - res - }) - }); + Box::pin(async move { + let output = run_input_output(child, None) + .await + .with_context(|| format!("Failed to execute rustup which rustc"))?; + + let stdout = String::from_utf8(output.stdout) + .with_context(|| format!("Failed to parse output of rustup which rustc"))?; - Box::new(lookup) + let proxied_compiler = PathBuf::from(stdout.trim()); + trace!( + "proxy: rustup which rustc produced: {:?}", + &proxied_compiler + ); + // TODO: Delegate FS access to a thread pool if possible + let attr = fs::metadata(proxied_compiler.as_path()) + .context("Failed to obtain metadata of the resolved, true rustc")?; + let res = if attr.is_file() { + Ok(FileTime::from_last_modification_time(&attr)) + } else { + Err(anyhow!( + "proxy: rustup resolved compiler is not of type file" + )) + } + .map(move |filetime| (proxied_compiler, filetime)); + + res + }) } fn box_clone(&self) -> Box> { @@ -567,23 +562,15 @@ impl RustupProxy { }) } - pub fn find_proxy_executable( + pub async fn find_proxy_executable( compiler_executable: &Path, proxy_name: &str, mut creator: T, env: &[(OsString, OsString)], - ) -> SFuture>> + ) -> Result>> where T: CommandCreatorSync, { - let compiler_executable1 = compiler_executable.to_owned(); - let compiler_executable2 = compiler_executable.to_owned(); - let proxy_name1 = proxy_name.to_owned(); - let proxy_name2 = proxy_name.to_owned(); - - let env1 = env.to_owned(); - let env2 = env.to_owned(); - enum ProxyPath { Candidate(PathBuf), ToBeDiscovered, @@ -613,93 +600,80 @@ impl RustupProxy { // verify rustc is proxy let mut child = creator.new_command_sync(compiler_executable.to_owned()); - child.env_clear().envs(ref_env(&env1)).args(&["+stable"]); - let find_candidate = run_input_output(child, None) - .map(move |output| { - if output.status.success() { - trace!("proxy: Found a compiler proxy managed by rustup"); - ProxyPath::ToBeDiscovered - } else { - trace!("proxy: Found a regular compiler"); - ProxyPath::None - } - }) - .and_then(move |state| { - let state = match state { - ProxyPath::Candidate(_) => { unreachable!("qed") } - ProxyPath::ToBeDiscovered => { - // simple check: is there a rustup in the same parent dir as rustc? - // that would be the prefered one - Ok(match compiler_executable1.parent().map(|parent| { parent.to_owned() }) { - Some(mut parent) => { - parent.push(proxy_name1); - let proxy_candidate = parent; - if proxy_candidate.exists() { - trace!("proxy: Found a compiler proxy at {}", proxy_candidate.display()); - ProxyPath::Candidate(proxy_candidate) - } else { - ProxyPath::ToBeDiscovered - } - }, - None => { - ProxyPath::ToBeDiscovered - }, - }) - }, - x => Ok(x), - }; - f_ok(state) - }).and_then(move |state| { - let state = match state { - Ok(ProxyPath::ToBeDiscovered) => { - // still no rustup found, use which crate to find one - match which::which(&proxy_name2) { - Ok(proxy_candidate) => { - warn!("proxy: rustup found, but not where it was expected (next to rustc {})", compiler_executable2.display()); - Ok(ProxyPath::Candidate(proxy_candidate)) - }, - Err(e) => { - trace!("proxy: rustup is not present: {}", e); - Ok(ProxyPath::ToBeDiscovered) - }, + child.env_clear().envs(ref_env(&env)).args(&["+stable"]); + let state = run_input_output(child, None).await.map(move |output| { + if output.status.success() { + trace!("proxy: Found a compiler proxy managed by rustup"); + ProxyPath::ToBeDiscovered + } else { + trace!("proxy: Found a regular compiler"); + ProxyPath::None + } + }); + + let state = match state { + Ok(ProxyPath::Candidate(_)) => unreachable!("Q.E.D."), + Ok(ProxyPath::ToBeDiscovered) => { + // simple check: is there a rustup in the same parent dir as rustc? + // that would be the prefered one + Ok(match compiler_executable.parent().map(Path::to_owned) { + Some(parent) => { + let proxy_candidate = parent.join(proxy_name); + if proxy_candidate.exists() { + trace!("proxy: Found a compiler proxy at {}", proxy_candidate.display()); + ProxyPath::Candidate(proxy_candidate) + } else { + ProxyPath::ToBeDiscovered } - } - x => x, - }; - f_ok(state) - }); - - let f = find_candidate.and_then(move |state| { - match state { - Err(e) => f_ok(Err(e)), - Ok(ProxyPath::ToBeDiscovered) => f_ok(Err(anyhow!( - "Failed to discover a rustup executable, but rustc behaves like a proxy" - ))), - Ok(ProxyPath::None) => f_ok(Ok(None)), - Ok(ProxyPath::Candidate(proxy_executable)) => { - // verify the candidate is a rustup - let mut child = creator.new_command_sync(proxy_executable.to_owned()); - child.env_clear().envs(ref_env(&env2)).args(&["--version"]); - let rustup_candidate_check = run_input_output(child, None).map(move |output| { - String::from_utf8(output.stdout.clone()) - .map_err(|_e| { - anyhow!("Response of `rustup --version` is not valid UTF-8") - }) - .and_then(|stdout| { - if stdout.trim().starts_with("rustup ") { - trace!("PROXY rustup --version produced: {}", &stdout); - Self::new(&proxy_executable).map(|proxy| Some(proxy)) - } else { - Err(anyhow!("Unexpected output or `rustup --version`")) - } - }) - }); - Box::new(rustup_candidate_check) + }, + None => ProxyPath::ToBeDiscovered, + }, + ) + } + x => x, + }; + let state = match state { + Ok(ProxyPath::ToBeDiscovered) => { + // still no rustup found, use which crate to find one + match which::which(&proxy_name) { + Ok(proxy_candidate) => { + warn!( + "proxy: rustup found, but not where it was expected (next to rustc {})", + compiler_executable.display() + ); + Ok(ProxyPath::Candidate(proxy_candidate)) + }, + Err(e) => { + trace!("proxy: rustup is not present: {}", e); + Ok(ProxyPath::ToBeDiscovered) + }, } } - }); + x => x, + }; - Box::new(f) + match state { + Err(e) => Err(e), + Ok(ProxyPath::ToBeDiscovered) => Ok(Err(anyhow!( + "Failed to discover a rustup executable, but rustc behaves like a proxy" + ))), + Ok(ProxyPath::None) => Ok(Ok(None)), + Ok(ProxyPath::Candidate(proxy_executable)) => { + // verify the candidate is a rustup + let mut child = creator.new_command_sync(proxy_executable.to_owned()); + child.env_clear().envs(ref_env(&env)).args(&["--version"]); + let rustup_candidate_check = run_input_output(child, None).await?; + + let stdout = String::from_utf8(rustup_candidate_check.stdout) + .map_err(|_e| anyhow!("Response of `rustup --version` is not valid UTF-8"))?; + Ok(if stdout.trim().starts_with("rustup ") { + trace!("PROXY rustup --version produced: {}", &stdout); + Self::new(&proxy_executable).map(Some) + } else { + Err(anyhow!("Unexpected output or `rustup --version`")) + }) + } + } } } @@ -754,7 +728,7 @@ impl IntoArg for ArgCrateTypes { .chain(if rlib { Some("rlib") } else { None }) .chain(if staticlib { Some("staticlib") } else { None }) .collect(); - types.sort(); + types.sort_unstable(); let types_string = types.join(","); types_string.into() } @@ -770,7 +744,7 @@ impl IntoArg for ArgCrateTypes { .chain(if rlib { Some("rlib") } else { None }) .chain(if staticlib { Some("staticlib") } else { None }) .collect(); - types.sort(); + types.sort_unstable(); let types_string = types.join(","); Ok(types_string) } @@ -1211,21 +1185,20 @@ fn parse_arguments(arguments: &[OsString], cwd: &Path) -> CompilerArguments CompilerHasher for RustHasher where T: CommandCreatorSync, { - fn generate_hash_key( + async fn generate_hash_key( self: Box, creator: &T, cwd: PathBuf, env_vars: Vec<(OsString, OsString)>, _may_dist: bool, - pool: &ThreadPool, + pool: &tokio::runtime::Handle, _rewrite_includes_only: bool, - ) -> SFuture { - let me = *self; - #[rustfmt::skip] // https://github.com/rust-lang/rustfmt/issues/3759 + ) -> Result { let RustHasher { executable, host, @@ -1247,7 +1220,7 @@ where has_json, .. }, - } = me; + } = *self; trace!("[{}]: generate_hash_key", crate_name); // TODO: this doesn't produce correct arguments if they should be concatenated - should use iter_os_strings let os_string_arguments: Vec<(OsString, Option)> = arguments @@ -1276,19 +1249,22 @@ where .collect::>(); // Find all the source files and hash them let source_hashes_pool = pool.clone(); - let source_files = get_source_files( - creator, - &crate_name, - &executable, - &filtered_arguments, - &cwd, - &env_vars, - pool, - ); - let source_files_and_hashes = source_files.and_then(move |source_files| { - hash_all(&source_files, &source_hashes_pool) - .map(|source_hashes| (source_files, source_hashes)) - }); + let source_files_and_hashes = async { + let source_files = get_source_files( + creator, + &crate_name, + &executable, + &filtered_arguments, + &cwd, + &env_vars, + pool, + ) + .await?; + let source_hashes = hash_all(&source_files, &source_hashes_pool) + .await?; + Ok((source_files, source_hashes)) + }; + // Hash the contents of the externs listed on the commandline. trace!("[{}]: hashing {} externs", crate_name, externs.len()); let abs_externs = externs.iter().map(|e| cwd.join(e)).collect::>(); @@ -1297,196 +1273,192 @@ where trace!("[{}]: hashing {} staticlibs", crate_name, staticlibs.len()); let abs_staticlibs = staticlibs.iter().map(|s| cwd.join(s)).collect::>(); let staticlib_hashes = hash_all(&abs_staticlibs, pool); - let creator = creator.clone(); - let hashes = source_files_and_hashes.join3(extern_hashes, staticlib_hashes); - Box::new(hashes.and_then( - move |((source_files, source_hashes), extern_hashes, staticlib_hashes)| -> SFuture<_> { - // If you change any of the inputs to the hash, you should change `CACHE_VERSION`. - let mut m = Digest::new(); - // Hash inputs: - // 1. A version - m.update(CACHE_VERSION); - // 2. compiler_shlibs_digests - for d in compiler_shlibs_digests { - m.update(d.as_bytes()); - } - let weak_toolchain_key = m.clone().finish(); - // 3. The full commandline (self.arguments) - // TODO: there will be full paths here, it would be nice to - // normalize them so we can get cross-machine cache hits. - // A few argument types are not passed in a deterministic order - // by cargo: --extern, -L, --cfg. We'll filter those out, sort them, - // and append them to the rest of the arguments. - let args = { - let (mut sortables, rest): (Vec<_>, Vec<_>) = os_string_arguments - .iter() - // We exclude a few arguments from the hash: - // -L, --extern, --out-dir - // These contain paths which aren't relevant to the output, and the compiler inputs - // in those paths (rlibs and static libs used in the compilation) are used as hash - // inputs below. - .filter(|&&(ref arg, _)| { - !(arg == "--extern" || arg == "-L" || arg == "--out-dir") - }) - // A few argument types were not passed in a deterministic order - // by older versions of cargo: --extern, -L, --cfg. We'll filter the rest of those - // out, sort them, and append them to the rest of the arguments. - .partition(|&&(ref arg, _)| arg == "--cfg"); - sortables.sort(); - rest.into_iter() - .chain(sortables) - .flat_map(|&(ref arg, ref val)| iter::once(arg).chain(val.as_ref())) - .fold(OsString::new(), |mut a, b| { - a.push(b); - a - }) - }; - args.hash(&mut HashToDigest { digest: &mut m }); - // 4. The digest of all source files (this includes src file from cmdline). - // 5. The digest of all files listed on the commandline (self.externs). - // 6. The digest of all static libraries listed on the commandline (self.staticlibs). - for h in source_hashes - .into_iter() - .chain(extern_hashes) - .chain(staticlib_hashes) - { - m.update(h.as_bytes()); + + let ((source_files, source_hashes), extern_hashes, staticlib_hashes) = + futures::try_join!(source_files_and_hashes, extern_hashes, staticlib_hashes)?; + // If you change any of the inputs to the hash, you should change `CACHE_VERSION`. + let mut m = Digest::new(); + // Hash inputs: + // 1. A version + m.update(CACHE_VERSION); + // 2. compiler_shlibs_digests + for d in compiler_shlibs_digests { + m.update(d.as_bytes()); + } + let weak_toolchain_key = m.clone().finish(); + // 3. The full commandline (self.arguments) + // TODO: there will be full paths here, it would be nice to + // normalize them so we can get cross-machine cache hits. + // A few argument types are not passed in a deterministic order + // by cargo: --extern, -L, --cfg. We'll filter those out, sort them, + // and append them to the rest of the arguments. + let args = { + let (mut sortables, rest): (Vec<_>, Vec<_>) = os_string_arguments + .iter() + // We exclude a few arguments from the hash: + // -L, --extern, --out-dir + // These contain paths which aren't relevant to the output, and the compiler inputs + // in those paths (rlibs and static libs used in the compilation) are used as hash + // inputs below. + .filter(|&&(ref arg, _)| { + !(arg == "--extern" || arg == "-L" || arg == "--out-dir") + }) + // A few argument types were not passed in a deterministic order + // by older versions of cargo: --extern, -L, --cfg. We'll filter the rest of those + // out, sort them, and append them to the rest of the arguments. + .partition(|&&(ref arg, _)| arg == "--cfg"); + sortables.sort(); + rest.into_iter() + .chain(sortables) + .flat_map(|&(ref arg, ref val)| iter::once(arg).chain(val.as_ref())) + .fold(OsString::new(), |mut a, b| { + a.push(b); + a + }) + }; + args.hash(&mut HashToDigest { digest: &mut m }); + // 4. The digest of all source files (this includes src file from cmdline). + // 5. The digest of all files listed on the commandline (self.externs). + // 6. The digest of all static libraries listed on the commandline (self.staticlibs). + for h in source_hashes + .into_iter() + .chain(extern_hashes) + .chain(staticlib_hashes) + { + m.update(h.as_bytes()); + } + // 7. Environment variables. Ideally we'd use anything referenced + // via env! in the program, but we don't have a way to determine that + // currently, and hashing all environment variables is too much, so + // we'll just hash the CARGO_ env vars and hope that's sufficient. + // Upstream Rust issue tracking getting information about env! usage: + // https://github.com/rust-lang/rust/issues/40364 + let mut env_vars: Vec<_> = env_vars + .iter() + // Filter out RUSTC_COLOR since we control color usage with command line flags. + // rustc reports an error when both are present. + .filter(|(ref k, _)| k != "RUSTC_COLOR") + .cloned() + .collect(); + env_vars.sort(); + for &(ref var, ref val) in env_vars.iter() { + // CARGO_MAKEFLAGS will have jobserver info which is extremely non-cacheable. + if var.starts_with("CARGO_") && var != "CARGO_MAKEFLAGS" { + var.hash(&mut HashToDigest { digest: &mut m }); + m.update(b"="); + val.hash(&mut HashToDigest { digest: &mut m }); + } + } + // 8. The cwd of the compile. This will wind up in the rlib. + cwd.hash(&mut HashToDigest { digest: &mut m }); + // Turn arguments into a simple Vec to calculate outputs. + let flat_os_string_arguments: Vec = os_string_arguments + .into_iter() + .flat_map(|(arg, val)| iter::once(arg).chain(val)) + .collect(); + + let mut outputs = get_compiler_outputs( + creator, + &executable, + flat_os_string_arguments, + &cwd, + &env_vars, + ) + .await?; + + // metadata / dep-info don't ever generate binaries, but + // rustc still makes them appear in the --print + // file-names output (see + // https://github.com/rust-lang/rust/pull/68799). + // + // So if we see a binary in the rustc output and figure + // out that we're not _actually_ generating it, then we + // can avoid generating everything that isn't an rlib / + // rmeta. + // + // This can go away once the above rustc PR makes it in. + let emit_generates_only_metadata = + !emit.is_empty() && emit.iter().all(|e| e == "metadata" || e == "dep-info"); + + if emit_generates_only_metadata { + outputs.retain(|o| o.ends_with(".rlib") || o.ends_with(".rmeta")); + } + + if emit.contains("metadata") { + // rustc currently does not report rmeta outputs with --print file-names + // --emit metadata the rlib is printed, and with --emit metadata,link + // only the rlib is printed. + let rlibs: HashSet<_> = outputs + .iter() + .cloned() + .filter(|p| p.ends_with(".rlib")) + .collect(); + for lib in rlibs { + let rmeta = lib.replacen(".rlib", ".rmeta", 1); + // Do this defensively for future versions of rustc that may + // be fixed. + if !outputs.contains(&rmeta) { + outputs.push(rmeta); } - // 7. Environment variables. Ideally we'd use anything referenced - // via env! in the program, but we don't have a way to determine that - // currently, and hashing all environment variables is too much, so - // we'll just hash the CARGO_ env vars and hope that's sufficient. - // Upstream Rust issue tracking getting information about env! usage: - // https://github.com/rust-lang/rust/issues/40364 - let mut env_vars: Vec<_> = env_vars - .iter() - // Filter out RUSTC_COLOR since we control color usage with command line flags. - // rustc reports an error when both are present. - .filter(|(ref k, _)| k != "RUSTC_COLOR") - .cloned() - .collect(); - env_vars.sort(); - for &(ref var, ref val) in env_vars.iter() { - // CARGO_MAKEFLAGS will have jobserver info which is extremely non-cacheable. - if var.starts_with("CARGO_") && var != "CARGO_MAKEFLAGS" { - var.hash(&mut HashToDigest { digest: &mut m }); - m.update(b"="); - val.hash(&mut HashToDigest { digest: &mut m }); - } + if !emit.contains("link") { + outputs.retain(|p| *p != lib); } - // 8. The cwd of the compile. This will wind up in the rlib. - cwd.hash(&mut HashToDigest { digest: &mut m }); - // Turn arguments into a simple Vec to calculate outputs. - let flat_os_string_arguments: Vec = os_string_arguments - .into_iter() - .flat_map(|(arg, val)| iter::once(arg).chain(val)) - .collect(); - Box::new( - get_compiler_outputs( - &creator, - &executable, - flat_os_string_arguments, - &cwd, - &env_vars, - ) - .map(move |mut outputs| { - // metadata / dep-info don't ever generate binaries, but - // rustc still makes them appear in the --print - // file-names output (see - // https://github.com/rust-lang/rust/pull/68799). - // - // So if we see a binary in the rustc output and figure - // out that we're not _actually_ generating it, then we - // can avoid generating everything that isn't an rlib / - // rmeta. - // - // This can go away once the above rustc PR makes it in. - let emit_generates_only_metadata = !emit.is_empty() - && emit.iter().all(|e| e == "metadata" || e == "dep-info"); - - if emit_generates_only_metadata { - outputs.retain(|o| o.ends_with(".rlib") || o.ends_with(".rmeta")); - } + } + } - if emit.contains("metadata") { - // rustc currently does not report rmeta outputs with --print file-names - // --emit metadata the rlib is printed, and with --emit metadata,link - // only the rlib is printed. - let rlibs: HashSet<_> = outputs - .iter() - .cloned() - .filter(|p| p.ends_with(".rlib")) - .collect(); - for lib in rlibs { - let rmeta = lib.replacen(".rlib", ".rmeta", 1); - // Do this defensively for future versions of rustc that may - // be fixed. - if !outputs.contains(&rmeta) { - outputs.push(rmeta); - } - if !emit.contains("link") { - outputs.retain(|p| *p != lib); - } - } - } + // Convert output files into a map of basename -> full + // path, and remove some unneeded / non-existing ones, + // see https://github.com/rust-lang/rust/pull/68799. + let mut outputs = outputs + .into_iter() + .map(|o| { + let p = output_dir.join(&o); + (o, p) + }) + .collect::>(); + let dep_info = if let Some(dep_info) = dep_info { + let p = output_dir.join(&dep_info); + outputs.insert(dep_info.to_string_lossy().into_owned(), p.clone()); + Some(p) + } else { + None + }; + let mut arguments = arguments; + // Request color output unless json was requested. The client will strip colors if needed. + if !has_json { + arguments.push(Argument::WithValue( + "--color", + ArgData::Color("always".into()), + ArgDisposition::Separated, + )); + } - // Convert output files into a map of basename -> full - // path, and remove some unneeded / non-existing ones, - // see https://github.com/rust-lang/rust/pull/68799. - let mut outputs = outputs - .into_iter() - .map(|o| { - let p = output_dir.join(&o); - (o, p) - }) - .collect::>(); - let dep_info = if let Some(dep_info) = dep_info { - let p = output_dir.join(&dep_info); - outputs.insert(dep_info.to_string_lossy().into_owned(), p.clone()); - Some(p) - } else { - None - }; - let mut arguments = arguments; - // Request color output unless json was requested. The client will strip colors if needed. - if !has_json { - arguments.push(Argument::WithValue( - "--color", - ArgData::Color("always".into()), - ArgDisposition::Separated, - )); - } + let inputs = source_files + .into_iter() + .chain(abs_externs) + .chain(abs_staticlibs) + .collect(); - let inputs = source_files - .into_iter() - .chain(abs_externs) - .chain(abs_staticlibs) - .collect(); - - HashResult { - key: m.finish(), - compilation: Box::new(RustCompilation { - executable, - host, - sysroot, - arguments, - inputs, - outputs, - crate_link_paths, - crate_name, - crate_types, - dep_info, - cwd, - env_vars, - #[cfg(feature = "dist-client")] - rlib_dep_reader, - }), - weak_toolchain_key, - } - }), - ) - }, - )) + Ok(HashResult { + key: m.finish(), + compilation: Box::new(RustCompilation { + executable, + host, + sysroot, + arguments, + inputs, + outputs, + crate_link_paths, + crate_name, + crate_types, + dep_info, + cwd, + env_vars, + #[cfg(feature = "dist-client")] + rlib_dep_reader, + }), + weak_toolchain_key, + }) } fn color_mode(&self) -> ColorMode { @@ -1845,15 +1817,13 @@ impl pkg::InputsPackager for RustInputsPackager { // If we're just creating an rlib then the only thing inspected inside dependency rlibs is the // metadata, in which case we can create a trimmed rlib (which is actually a .a) with the metadata - let can_trim_rlibs = if let CrateTypes { - rlib: true, - staticlib: false, - } = crate_types - { - true - } else { - false - }; + let can_trim_rlibs = matches!( + crate_types, + CrateTypes { + rlib: true, + staticlib: false, + } + ); let mut builder = tar::Builder::new(wtr); @@ -2242,7 +2212,7 @@ fn parse_rustc_z_ls(stdout: &str) -> Result> { let mut dep_names = vec![]; while let Some(line) = lines.next() { - if line == "" { + if line.is_empty() { break; } @@ -2282,7 +2252,7 @@ fn parse_rustc_z_ls(stdout: &str) -> Result> { } for line in lines { - if line != "" { + if !line.is_empty() { bail!("Trailing non-blank lines in rustc -Z ls output") } } @@ -2910,7 +2880,7 @@ c:/foo/bar.rs: #[test] fn test_generate_hash_key() { - drop(env_logger::try_init()); + let _ = env_logger::Builder::new().is_test(true).try_init(); let f = TestFixture::new(); const FAKE_DIGEST: &str = "abcd1234"; // We'll just use empty files for each of these. @@ -2960,7 +2930,8 @@ c:/foo/bar.rs: let creator = new_creator(); mock_dep_info(&creator, &["foo.rs", "bar.rs"]); mock_file_names(&creator, &["foo.rlib", "foo.a"]); - let pool = ThreadPool::sized(1); + let runtime = single_threaded_runtime(); + let pool = runtime.handle().clone(); let res = hasher .generate_hash_key( &creator, @@ -3050,7 +3021,9 @@ c:/foo/bar.rs: }); let creator = new_creator(); - let pool = ThreadPool::sized(1); + let runtime = single_threaded_runtime(); + let pool = runtime.handle().clone(); + mock_dep_info(&creator, &["foo.rs"]); mock_file_names(&creator, &["foo.rlib"]); hasher diff --git a/src/config.rs b/src/config.rs index b3ded9215..9c2cda566 100644 --- a/src/config.rs +++ b/src/config.rs @@ -197,9 +197,14 @@ pub struct RedisCacheConfig { #[serde(deny_unknown_fields)] pub struct S3CacheConfig { pub bucket: String, - pub endpoint: String, - pub use_ssl: bool, - pub key_prefix: String, + #[serde(default)] + pub endpoint: Option, + #[serde(default)] + pub key_prefix: Option, + #[serde(default)] + pub region: Option, + #[serde(default)] + pub public: bool, } #[derive(Debug, PartialEq, Eq)] @@ -211,7 +216,7 @@ pub enum CacheType { S3(S3CacheConfig), } -#[derive(Debug, Default, Serialize, Deserialize)] +#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct CacheConfigs { pub azure: Option, @@ -400,7 +405,7 @@ impl Default for DistConfig { } // TODO: fields only pub for tests -#[derive(Debug, Default, Serialize, Deserialize)] +#[derive(Debug, Default, Serialize, Deserialize, Eq, PartialEq)] #[serde(default)] #[serde(deny_unknown_fields)] pub struct FileConfig { @@ -447,32 +452,22 @@ pub struct EnvConfig { fn config_from_env() -> EnvConfig { let s3 = env::var("SCCACHE_BUCKET").ok().map(|bucket| { - let endpoint = match env::var("SCCACHE_ENDPOINT") { - Ok(endpoint) => format!("{}/{}", endpoint, bucket), - _ => match env::var("SCCACHE_REGION") { - Ok(ref region) if region != "us-east-1" => { - format!("{}.s3-{}.amazonaws.com", bucket, region) - } - _ => format!("{}.s3.amazonaws.com", bucket), - }, - }; - let use_ssl = env::var("SCCACHE_S3_USE_SSL") - .ok() - .filter(|value| value != "off") - .is_some(); + let endpoint = env::var("SCCACHE_ENDPOINT").ok(); + let region = env::var("SCCACHE_REGION").ok(); let key_prefix = env::var("SCCACHE_S3_KEY_PREFIX") .ok() .as_ref() - .map(|s| s.trim_end_matches("/")) + .map(|s| s.trim_end_matches('/')) .filter(|s| !s.is_empty()) - .map(|s| s.to_owned() + "/") - .unwrap_or_default(); + .map(|s| s.to_owned() + "/"); + let public = env::var("SCCACHE_S3_PUBLIC").ok().is_some(); S3CacheConfig { bucket, endpoint, - use_ssl, key_prefix, + region, + public, } }); @@ -877,3 +872,99 @@ fn test_gcs_credentials_url() { None => unreachable!(), }; } + +#[test] +fn full_toml_parse() { + const CONFIG_STR: &str = r#" +[dist] +# where to find the scheduler +scheduler_url = "http://1.2.3.4:10600" +# a set of prepackaged toolchains +toolchains = [] +# the maximum size of the toolchain cache in bytes +toolchain_cache_size = 5368709120 +cache_dir = "/home/user/.cache/sccache-dist-client" + +[dist.auth] +type = "token" +token = "secrettoken" + + +#[cache.azure] +# does not work as it appears + +[cache.disk] +dir = "/tmp/.cache/sccache" +size = 7516192768 # 7 GiBytes + +[cache.gcs] +# optional url +url = "..." +rw_mode = "READ_ONLY" +# rw_mode = "READ_WRITE" +cred_path = "/psst/secret/cred" +bucket = "bucket" + +[cache.memcached] +url = "..." + +[cache.redis] +url = "redis://user:passwd@1.2.3.4:6379/1" + +[cache.s3] +bucket = "name" +endpoint = "s3-us-east-1.amazonaws.com" +key_prefix = "prefix" +public = true +"#; + + let file_config: FileConfig = toml::from_str(CONFIG_STR).expect("Is valid toml."); + assert_eq!( + file_config, + FileConfig { + cache: CacheConfigs { + azure: None, // TODO not sure how to represent a unit struct in TOML Some(AzureCacheConfig), + disk: Some(DiskCacheConfig { + dir: PathBuf::from("/tmp/.cache/sccache"), + size: 7 * 1024 * 1024 * 1024, + }), + gcs: Some(GCSCacheConfig { + url: Some("...".to_owned()), + bucket: "bucket".to_owned(), + cred_path: Some(PathBuf::from("/psst/secret/cred")), + rw_mode: GCSCacheRWMode::ReadOnly, + }), + redis: Some(RedisCacheConfig { + url: "redis://user:passwd@1.2.3.4:6379/1".to_owned(), + }), + memcached: Some(MemcachedCacheConfig { + url: "...".to_owned(), + }), + s3: Some(S3CacheConfig { + bucket: "name".to_owned(), + endpoint: Some("s3-us-east-1.amazonaws.com".to_owned()), + key_prefix: Some("prefix".to_owned()), + region: None, + public: true, + }), + }, + dist: DistConfig { + auth: DistAuth::Token { + token: "secrettoken".to_owned() + }, + #[cfg(any(feature = "dist-client", feature = "dist-server"))] + scheduler_url: Some( + parse_http_url("http://1.2.3.4:10600") + .map(|url| { HTTPUrl::from_url(url) }) + .expect("Scheduler url must be valid url str") + ), + #[cfg(not(any(feature = "dist-client", feature = "dist-server")))] + scheduler_url: Some("http://1.2.3.4:10600".to_owned()), + cache_dir: PathBuf::from("/home/user/.cache/sccache-dist-client"), + toolchains: vec![], + toolchain_cache_size: 5368709120, + rewrite_includes_only: false, + }, + } + ) +} diff --git a/src/dist/cache.rs b/src/dist/cache.rs index dabafadb6..07f04fe5f 100644 --- a/src/dist/cache.rs +++ b/src/dist/cache.rs @@ -1,4 +1,5 @@ use crate::dist::Toolchain; + use anyhow::{anyhow, Result}; use lru_disk_cache::Result as LruResult; use lru_disk_cache::{LruDiskCache, ReadSeek}; @@ -177,25 +178,25 @@ mod client { // If the toolchain doesn't already exist, create it and insert into the cache pub fn put_toolchain( &self, - compiler_path: &Path, - weak_key: &str, + compiler_path: PathBuf, + weak_key: String, toolchain_packager: Box, ) -> Result<(Toolchain, Option<(String, PathBuf)>)> { - if self.disabled_toolchains.contains(compiler_path) { + if self.disabled_toolchains.contains(&compiler_path) { bail!( "Toolchain distribution for {} is disabled", compiler_path.display() ) } - if let Some(tc_and_paths) = self.get_custom_toolchain(compiler_path) { - debug!("Using custom toolchain for {:?}", compiler_path); + if let Some(tc_and_paths) = self.get_custom_toolchain(&compiler_path) { + debug!("Using custom toolchain for {:?}", &compiler_path); let (tc, compiler_path, archive) = tc_and_paths?; return Ok((tc, Some((compiler_path, archive)))); } // Only permit one toolchain creation at a time. Not an issue if there are multiple attempts // to create the same toolchain, just a waste of time let mut cache = self.cache.lock().unwrap(); - if let Some(archive_id) = self.weak_to_strong(weak_key) { + if let Some(archive_id) = self.weak_to_strong(&weak_key) { debug!("Using cached toolchain {} -> {}", weak_key, archive_id); return Ok((Toolchain { archive_id }, None)); } @@ -211,7 +212,7 @@ mod client { pub fn get_custom_toolchain( &self, - compiler_path: &Path, + compiler_path: &PathBuf, ) -> Option> { match self .custom_toolchain_paths @@ -320,8 +321,8 @@ mod client { let (_tc, newpath) = client_toolchains .put_toolchain( - "/my/compiler".as_ref(), - "weak_key", + "/my/compiler".into(), + "weak_key".to_owned(), PanicToolchainPackager::new(), ) .unwrap(); @@ -365,24 +366,24 @@ mod client { let (_tc, newpath) = client_toolchains .put_toolchain( - "/my/compiler".as_ref(), - "weak_key", + "/my/compiler".into(), + "weak_key".to_owned(), PanicToolchainPackager::new(), ) .unwrap(); assert!(newpath.unwrap() == ("/my/compiler/in_archive".to_string(), ct1.clone())); let (_tc, newpath) = client_toolchains .put_toolchain( - "/my/compiler2".as_ref(), - "weak_key2", + "/my/compiler2".into(), + "weak_key2".to_owned(), PanicToolchainPackager::new(), ) .unwrap(); assert!(newpath.unwrap() == ("/my/compiler2/in_archive".to_string(), ct1.clone())); let (_tc, newpath) = client_toolchains .put_toolchain( - "/my/compiler3".as_ref(), - "weak_key2", + "/my/compiler3".into(), + "weak_key2".to_owned(), PanicToolchainPackager::new(), ) .unwrap(); @@ -407,8 +408,8 @@ mod client { assert!(client_toolchains .put_toolchain( - "/my/compiler".as_ref(), - "weak_key", + "/my/compiler".into(), + "weak_key".to_owned(), PanicToolchainPackager::new() ) .is_err()); diff --git a/src/dist/client_auth.rs b/src/dist/client_auth.rs index 9890b1d55..b2c0216e8 100644 --- a/src/dist/client_auth.rs +++ b/src/dist/client_auth.rs @@ -1,21 +1,16 @@ -use futures::future; -use futures::prelude::*; -use futures::sync::oneshot; +use futures::channel::oneshot; use http::StatusCode; -use hyper::body::Payload; use hyper::server::conn::AddrIncoming; -use hyper::service::Service; -use hyper::{Body, Request, Response, Server}; +use hyper::{Body, Response, Server}; use hyperx::header::{ContentLength, ContentType}; use serde::Serialize; use std::collections::HashMap; use std::error::Error as StdError; use std::io; -use std::marker::PhantomData; use std::net::{TcpStream, ToSocketAddrs}; use std::sync::mpsc; use std::time::Duration; -use tokio_compat::runtime::current_thread::Runtime; +use tokio::runtime::Runtime; use url::Url; use uuid::Uuid; @@ -29,43 +24,6 @@ pub const VALID_PORTS: &[u16] = &[12731, 32492, 56909]; const MIN_TOKEN_VALIDITY: Duration = Duration::from_secs(2 * 24 * 60 * 60); const MIN_TOKEN_VALIDITY_WARNING: &str = "two days"; -trait ServeFn: - Fn(Request) -> Box, Error = hyper::Error> + Send> - + Copy - + Send - + 'static -{ -} -impl ServeFn for T where - T: Fn(Request) -> Box, Error = hyper::Error> + Send> - + Copy - + Send - + 'static -{ -} - -fn serve_sfuture(serve: fn(Request) -> SFutureSend>) -> impl ServeFn { - move |req: Request| { - let uri = req.uri().to_owned(); - Box::new(serve(req).or_else(move |e| { - // `{:?}` prints the full cause chain and backtrace. - let body = format!("{:?}", e); - eprintln!( - "sccache: Error during a request to {} on the client auth web server\n{}", - uri, body - ); - let len = body.len(); - let mut builder = Response::builder(); - builder.status(StatusCode::INTERNAL_SERVER_ERROR); - Ok(builder - .set_header(ContentType::text()) - .set_header(ContentLength(len as u64)) - .body(body.into()) - .unwrap()) - })) as Box + Send> - } -} - fn query_pairs(url: &str) -> Result> { // Url::parse operates on absolute URLs, so ensure there's a prefix let url = Url::parse("http://unused_base") @@ -128,10 +86,9 @@ mod code_grant_pkce { html_response, json_response, query_pairs, MIN_TOKEN_VALIDITY, MIN_TOKEN_VALIDITY_WARNING, REDIRECT_WITH_AUTH_JSON, }; - use futures::future; - use futures::sync::oneshot; + use futures::channel::oneshot; use hyper::{Body, Method, Request, Response, StatusCode}; - use rand::RngCore; + use rand::{rngs::OsRng, RngCore}; use sha2::{Digest, Sha256}; use std::collections::HashMap; use std::sync::mpsc; @@ -187,13 +144,11 @@ mod code_grant_pkce { pub fn generate_verifier_and_challenge() -> Result<(String, String)> { let mut code_verifier_bytes = vec![0; NUM_CODE_VERIFIER_BYTES]; - let mut rng = - rand::OsRng::new().context("Failed to initialise a random number generator")?; - rng.fill_bytes(&mut code_verifier_bytes); + OsRng.fill_bytes(&mut code_verifier_bytes); let code_verifier = base64::encode_config(&code_verifier_bytes, base64::URL_SAFE_NO_PAD); let mut hasher = Sha256::new(); - hasher.input(&code_verifier); - let code_challenge = base64::encode_config(&hasher.result(), base64::URL_SAFE_NO_PAD); + hasher.update(&code_verifier); + let code_challenge = base64::encode_config(&hasher.finalize(), base64::URL_SAFE_NO_PAD); Ok((code_verifier, code_challenge)) } @@ -243,19 +198,19 @@ mod code_grant_pkce { "##; - pub fn serve(req: Request) -> SFutureSend> { + pub fn serve(req: Request) -> Result> { let mut state = STATE.lock().unwrap(); let state = state.as_mut().unwrap(); debug!("Handling {} {}", req.method(), req.uri()); let response = match (req.method(), req.uri().path()) { (&Method::GET, "/") => html_response(REDIRECT_WITH_AUTH_JSON), - (&Method::GET, "/auth_detail.json") => ftry_send!(json_response(&state.auth_url)), + (&Method::GET, "/auth_detail.json") => json_response(&state.auth_url)?, (&Method::GET, "/redirect") => { - let query_pairs = ftry_send!(query_pairs(&req.uri().to_string())); - let (code, auth_state) = ftry_send!(handle_code_response(query_pairs) - .context("Failed to handle response from redirect")); + let query_pairs = query_pairs(&req.uri().to_string())?; + let (code, auth_state) = handle_code_response(query_pairs) + .context("Failed to handle response from redirect")?; if auth_state != state.auth_state_value { - return ftry_send!(Err(anyhow!("Mismatched auth states after redirect"))); + return Err(anyhow!("Mismatched auth states after redirect")); } // Deliberately in reverse order for a 'happens-before' relationship state.code_tx.send(code).unwrap(); @@ -266,12 +221,11 @@ mod code_grant_pkce { warn!("Route not found"); Response::builder() .status(StatusCode::NOT_FOUND) - .body("".into()) - .unwrap() + .body("".into())? } }; - Box::new(future::ok(response)) + Ok(response) } pub fn code_to_token( @@ -288,8 +242,8 @@ mod code_grant_pkce { grant_type: GRANT_TYPE_PARAM_VALUE, redirect_uri, }; - let client = reqwest::Client::new(); - let mut res = client.post(token_url).json(&token_request).send()?; + let client = reqwest::blocking::Client::new(); + let res = client.post(token_url).json(&token_request).send()?; if !res.status().is_success() { bail!( "Sending code to {} failed, HTTP error: {}", @@ -321,8 +275,7 @@ mod implicit { html_response, json_response, query_pairs, MIN_TOKEN_VALIDITY, MIN_TOKEN_VALIDITY_WARNING, REDIRECT_WITH_AUTH_JSON, }; - use futures::future; - use futures::sync::oneshot; + use futures::channel::oneshot; use hyper::{Body, Method, Request, Response, StatusCode}; use std::collections::HashMap; use std::sync::mpsc; @@ -422,21 +375,20 @@ mod implicit { "##; - pub fn serve(req: Request) -> SFutureSend> { + pub fn serve(req: Request) -> Result> { let mut state = STATE.lock().unwrap(); let state = state.as_mut().unwrap(); debug!("Handling {} {}", req.method(), req.uri()); let response = match (req.method(), req.uri().path()) { (&Method::GET, "/") => html_response(REDIRECT_WITH_AUTH_JSON), - (&Method::GET, "/auth_detail.json") => ftry_send!(json_response(&state.auth_url)), + (&Method::GET, "/auth_detail.json") => json_response(&state.auth_url)?, (&Method::GET, "/redirect") => html_response(SAVE_AUTH_AFTER_REDIRECT), (&Method::POST, "/save_auth") => { - let query_pairs = ftry_send!(query_pairs(&req.uri().to_string())); - let (token, expires_at, auth_state) = ftry_send!( - handle_response(query_pairs).context("Failed to save auth after redirect") - ); + let query_pairs = query_pairs(&req.uri().to_string())?; + let (token, expires_at, auth_state) = + handle_response(query_pairs).context("Failed to save auth after redirect")?; if auth_state != state.auth_state_value { - return ftry_send!(Err(anyhow!("Mismatched auth states after redirect"))); + return Err(anyhow!("Mismatched auth states after redirect")); } if expires_at - Instant::now() < MIN_TOKEN_VALIDITY { warn!( @@ -451,7 +403,7 @@ mod implicit { // Deliberately in reverse order for a 'happens-before' relationship state.token_tx.send(token).unwrap(); state.shutdown_tx.take().unwrap().send(()).unwrap(); - ftry_send!(json_response(&"")) + json_response(&"")? } _ => { warn!("Route not found"); @@ -462,58 +414,51 @@ mod implicit { } }; - Box::new(future::ok(response)) + Ok(response) } } -fn service_fn(f: F) -> ServiceFn -where - F: Fn(Request) -> S, - S: IntoFuture, -{ - ServiceFn { - f, - _req: PhantomData, - } -} - -struct ServiceFn { - f: F, - _req: PhantomData, +// Typing out a hyper service is a major pain, so let's focus on our simple +// `fn(Request) -> Response` handler functions; to reduce repetition +// we create a relevant service using hyper's own helper factory functions. +macro_rules! make_service { + ($serve_fn: expr) => {{ + use core::convert::Infallible; + use hyper::{Body, Request}; + use hyper::server::conn::AddrStream; + use hyper::service::{make_service_fn, service_fn}; + + make_service_fn(|_socket: &AddrStream| async { + Ok::<_, Infallible>(service_fn(|req: Request| async move { + let uri = req.uri().clone(); + $serve_fn(req) + .or_else(|e| error_code_response(uri, e)) + })) + }) + }} } -impl Service for ServiceFn +fn error_code_response(uri: hyper::Uri, e: E) -> hyper::Result> where - F: Fn(Request) -> Ret, - ReqBody: Payload, - Ret: IntoFuture>, - Ret::Error: Into>, - ResBody: Payload, + E: std::fmt::Debug, { - type ReqBody = ReqBody; - type ResBody = ResBody; - type Error = Ret::Error; - type Future = Ret::Future; - - fn call(&mut self, req: Request) -> Self::Future { - (self.f)(req).into_future() - } -} - -impl IntoFuture for ServiceFn { - type Future = future::FutureResult; - type Item = Self; - type Error = hyper::Error; - - fn into_future(self) -> Self::Future { - future::ok(self) - } + let body = format!("{:?}", e); + eprintln!( + "sccache: Error during a request to {} on the client auth web server\n{}", + uri, body + ); + let len = body.len(); + let builder = Response::builder().status(StatusCode::INTERNAL_SERVER_ERROR); + let res = builder + .set_header(ContentType::text()) + .set_header(ContentLength(len as u64)) + .body(body.into()) + .unwrap(); + Ok::, hyper::Error>(res) } -fn try_serve(serve: T) -> Result ServiceFn>> -where - T: ServeFn, -{ +/// Try to bind a TCP stream to any of the available port out of [`VALID_PORTS`]. +fn try_bind() -> Result> { // Try all the valid ports for &port in VALID_PORTS { let mut addrs = ("localhost", port) @@ -535,9 +480,8 @@ where } } - let new_service = move || service_fn(serve); match Server::try_bind(&addr) { - Ok(s) => return Ok(s.serve(new_service)), + Ok(s) => return Ok(s), Err(ref err) if err .source() @@ -559,7 +503,7 @@ pub fn get_token_oauth2_code_grant_pkce( mut auth_url: Url, token_url: &str, ) -> Result { - let server = try_serve(serve_sfuture(code_grant_pkce::serve))?; + let server = try_bind()?.serve(make_service!(code_grant_pkce::serve)); let port = server.local_addr().port(); let redirect_uri = format!("http://localhost:{}/redirect", port); @@ -587,15 +531,16 @@ pub fn get_token_oauth2_code_grant_pkce( shutdown_tx: Some(shutdown_tx), }; *code_grant_pkce::STATE.lock().unwrap() = Some(state); - let shutdown_signal = shutdown_rx.map_err(|e| { - warn!( - "Something went wrong while waiting for auth server shutdown: {}", - e - ) - }); let mut runtime = Runtime::new()?; - runtime.block_on(server.with_graceful_shutdown(shutdown_signal))?; + runtime.block_on(server.with_graceful_shutdown(async move { + if let Err(e) = shutdown_rx.await { + warn!( + "Something went wrong while waiting for auth server shutdown: {}", + e + ) + } + }))?; info!("Server finished, using code to request token"); let code = code_rx @@ -607,7 +552,8 @@ pub fn get_token_oauth2_code_grant_pkce( // https://auth0.com/docs/api-auth/tutorials/implicit-grant pub fn get_token_oauth2_implicit(client_id: &str, mut auth_url: Url) -> Result { - let server = try_serve(serve_sfuture(implicit::serve))?; + let server = try_bind()?.serve(make_service!(implicit::serve)); + let port = server.local_addr().port(); let redirect_uri = format!("http://localhost:{}/redirect", port); @@ -628,15 +574,16 @@ pub fn get_token_oauth2_implicit(client_id: &str, mut auth_url: Url) -> Result) -> Self; fn bearer_auth(self, token: String) -> Self; } - impl ReqwestRequestBuilderExt for reqwest::RequestBuilder { + impl ReqwestRequestBuilderExt for reqwest::blocking::RequestBuilder { fn bincode(self, bincode: &T) -> Result { let bytes = bincode::serialize(bincode).context("Failed to serialize body to bincode")?; @@ -54,7 +53,7 @@ mod common { self.set_header(header::Authorization(header::Bearer { token })) } } - impl ReqwestRequestBuilderExt for reqwest::r#async::RequestBuilder { + impl ReqwestRequestBuilderExt for reqwest::RequestBuilder { fn bincode(self, bincode: &T) -> Result { let bytes = bincode::serialize(bincode).context("Failed to serialize body to bincode")?; @@ -71,7 +70,7 @@ mod common { } pub fn bincode_req( - req: reqwest::RequestBuilder, + req: reqwest::blocking::RequestBuilder, ) -> Result { let mut res = req.send()?; let status = res.status(); @@ -90,38 +89,27 @@ mod common { } } #[cfg(feature = "dist-client")] - pub fn bincode_req_fut( - req: reqwest::r#async::RequestBuilder, - ) -> SFuture { - Box::new( - req.send() - .map_err(Into::into) - .and_then(|res| { - let status = res.status(); - res.into_body() - .concat2() - .map(move |b| (status, b)) - .map_err(Into::into) - }) - .and_then(|(status, body)| { - if !status.is_success() { - let errmsg = format!( - "Error {}: {}", - status.as_u16(), - String::from_utf8_lossy(&body) - ); - if status.is_client_error() { - return f_err(HttpClientError(errmsg)); - } else { - return f_err(anyhow!(errmsg)); - } - } - match bincode::deserialize(&body) { - Ok(r) => f_ok(r), - Err(e) => f_err(e), - } - }), - ) + pub async fn bincode_req_fut( + req: reqwest::RequestBuilder, + ) -> Result { + let res = req.send().await?; + let status = res.status(); + let bytes = res.bytes().await?; + if !status.is_success() { + let errmsg = format!( + "Error {}: {}", + status.as_u16(), + String::from_utf8_lossy(&bytes) + ); + if status.is_client_error() { + anyhow::bail!(HttpClientError(errmsg)); + } else { + anyhow::bail!(errmsg); + } + } else { + let bc = bincode::deserialize(&*bytes)?; + Ok(bc) + } } #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] @@ -276,11 +264,10 @@ mod server { use crate::jwt; use byteorder::{BigEndian, ReadBytesExt}; use flate2::read::ZlibDecoder as ZlibReadDecoder; - use rand::RngCore; + use rand::{rngs::OsRng, RngCore}; use rouille::accept; use std::collections::HashMap; use std::io::Read; - use std::net::SocketAddr; use std::result::Result as StdResult; use std::sync::atomic; use std::sync::Mutex; @@ -304,83 +291,121 @@ mod server { const HEARTBEAT_ERROR_INTERVAL: Duration = Duration::from_secs(10); pub const HEARTBEAT_TIMEOUT: Duration = Duration::from_secs(90); - fn create_https_cert_and_privkey(addr: SocketAddr) -> Result<(Vec, Vec, Vec)> { - let rsa_key = openssl::rsa::Rsa::::generate(2048) - .context("failed to generate rsa privkey")?; - let privkey_pem = rsa_key - .private_key_to_pem() - .context("failed to create pem from rsa privkey")?; - let privkey: openssl::pkey::PKey = - openssl::pkey::PKey::from_rsa(rsa_key) - .context("failed to create openssl pkey from rsa privkey")?; - let mut builder = - openssl::x509::X509::builder().context("failed to create x509 builder")?; - - // Populate the certificate with the necessary parts, mostly from mkcert in openssl - builder - .set_version(2) - .context("failed to set x509 version")?; - let serial_number = openssl::bn::BigNum::from_u32(0) - .and_then(|bn| bn.to_asn1_integer()) - .context("failed to create openssl asn1 0")?; - builder - .set_serial_number(serial_number.as_ref()) - .context("failed to set x509 serial number")?; - let not_before = openssl::asn1::Asn1Time::days_from_now(0) - .context("failed to create openssl not before asn1")?; - builder - .set_not_before(not_before.as_ref()) - .context("failed to set not before on x509")?; - let not_after = openssl::asn1::Asn1Time::days_from_now(365) - .context("failed to create openssl not after asn1")?; - builder - .set_not_after(not_after.as_ref()) - .context("failed to set not after on x509")?; - builder - .set_pubkey(privkey.as_ref()) - .context("failed to set pubkey for x509")?; - - let mut name = openssl::x509::X509Name::builder()?; - name.append_entry_by_nid(openssl::nid::Nid::COMMONNAME, &addr.to_string())?; - let name = name.build(); - - builder - .set_subject_name(&name) - .context("failed to set subject name")?; - builder - .set_issuer_name(&name) - .context("failed to set issuer name")?; - - // Add the SubjectAlternativeName - let extension = openssl::x509::extension::SubjectAlternativeName::new() - .ip(&addr.ip().to_string()) - .build(&builder.x509v3_context(None, None)) - .context("failed to build SAN extension for x509")?; - builder - .append_extension(extension) - .context("failed to append SAN extension for x509")?; - - // Add ExtendedKeyUsage - let ext_key_usage = openssl::x509::extension::ExtendedKeyUsage::new() - .server_auth() - .build() - .context("failed to build EKU extension for x509")?; - builder - .append_extension(ext_key_usage) - .context("failes to append EKU extension for x509")?; - - // Finish the certificate - builder - .sign(&privkey, openssl::hash::MessageDigest::sha1()) - .context("failed to sign x509 with sha1")?; - let cert: openssl::x509::X509 = builder.build(); - let cert_pem = cert.to_pem().context("failed to create pem from x509")?; - let cert_digest = cert - .digest(openssl::hash::MessageDigest::sha256()) - .context("failed to create digest of x509 certificate")? - .as_ref() - .to_owned(); + use chrono::Datelike; + use chrono::Timelike; + use picky::x509::{ + certificate::CertificateBuilder, + date::UTCDate, + extension::ExtendedKeyUsage, + extension::KeyUsage, + key_id_gen_method::KeyIdGenMethod, + name::{DirectoryName, GeneralNames}, + Extensions, + }; + use picky::{ + hash::HashAlgorithm, + key::{PrivateKey, PublicKey}, + signature::SignatureAlgorithm, + }; + use rsa_pem::KeyExt; + use sha2::Digest; + use std::net::{IpAddr, SocketAddr}; + use std::ops::DerefMut; + + pub(crate) fn create_https_cert_and_privkey( + addr: SocketAddr, + ) -> Result<(Vec, Vec, Vec)> { + let mut rng = rand::rngs::OsRng; + let bits = 2048; + let rsa_key = rsa::RSAPrivateKey::new(&mut rng, bits)?; + + let sk_pkcs8 = ::to_pem_pkcs8(&rsa_key)?; + let pk_pkcs8 = ::to_pem_pkcs8(&rsa_key)?; + + // convert to picky + let sk = PrivateKey::from_pem_str(sk_pkcs8.as_str())?; + let pk = PublicKey::from_pem_str(pk_pkcs8.as_str())?; + + let today = chrono::Utc::now().naive_utc(); + let expires = today + chrono::Duration::days(365); + let start = UTCDate::new( + today.year() as u16, + today.month() as u8, + today.day() as u8, + today.time().hour() as u8, + today.time().minute() as u8, + today.time().second() as u8, + ) + .unwrap(); + let end = UTCDate::new( + expires.year() as u16, + expires.month() as u8, + expires.day() as u8, + expires.time().hour() as u8, + expires.time().minute() as u8, + expires.time().second() as u8, + ) + .unwrap(); + + let extended_key_usage = ExtendedKeyUsage::new(vec![picky::oids::kp_server_auth()]); + + let name = addr.to_string(); + let issuer_name = DirectoryName::new_common_name(name.clone()); + let subject_name = DirectoryName::new_common_name(name); + let octets = match addr.ip() { + IpAddr::V4(inner) => inner.octets().to_vec(), + IpAddr::V6(inner) => inner.octets().to_vec(), + }; + let subject_alt_name = GeneralNames::new(picky::x509::name::GeneralName::IpAddress(octets)); + + let cert = CertificateBuilder::new() + .validity(start, end) + .key_usage(KeyUsage::new(1)) + .subject(subject_name, pk) + .subject_alt_name(subject_alt_name.clone()) + .serial_number(vec![0]) + .signature_hash_type(SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA1)) + .key_id_gen_method(KeyIdGenMethod::SPKValueHashedLeftmost160( + HashAlgorithm::SHA2_256, + )) + .extended_key_usage(extended_key_usage.clone()) + .self_signed(issuer_name, &sk) + .build()?; + + // TODO exists to assure compat with the previously created cert + // TODO but imho this can be removed eventually + let cert = { + use picky_asn1_x509::certificate::Certificate; + + let mut certificate = Certificate::from(cert); + let inner = &mut certificate.tbs_certificate; + let extensions = inner.extensions.deref_mut(); + + // let basic = dbg!(picky::x509::Extension::new_key_usage(KeyUsage::new(0))); + let subject_alt_name = picky::x509::Extension::new_subject_alt_name(subject_alt_name); + let extended_key_usage = + picky::x509::Extension::new_extended_key_usage(extended_key_usage); + + *extensions = Extensions(vec![ + subject_alt_name.into_non_critical(), + extended_key_usage.into_non_critical(), + ]); + picky::x509::Cert::from(certificate) + }; + + let cert_digest = { + let der = cert.to_der()?; + let mut state = sha2::Sha256::new(); + state.update(&der); + state.finalize() + } + .as_slice() + .to_vec(); + + let cert_pem = cert.to_pem()?; + let cert_pem = cert_pem.to_string().as_bytes().to_vec(); + let privkey_pem = sk_pkcs8.as_bytes().to_vec(); Ok((cert_digest, cert_pem, privkey_pem)) } @@ -609,12 +634,14 @@ mod server { impl dist::JobAuthorizer for JWTJobAuthorizer { fn generate_token(&self, job_id: JobId) -> Result { let claims = JobJwt { job_id }; - jwt::encode(&JWT_HEADER, &claims, &self.server_key) + let key = jwt::EncodingKey::from_secret(&self.server_key); + jwt::encode(&JWT_HEADER, &claims, &key) .map_err(|e| anyhow!("Failed to create JWT for job: {}", e)) } fn verify_token(&self, job_id: JobId, token: &str) -> Result<()> { let valid_claims = JobJwt { job_id }; - jwt::decode(&token, &self.server_key, &JWT_VALIDATION) + let key = jwt::DecodingKey::from_secret(&self.server_key); + jwt::decode(&token, &key, &JWT_VALIDATION) .map_err(|e| anyhow!("JWT decode failed: {}", e)) .and_then(|res| { fn identical_t(_: &T, _: &T) {} @@ -684,7 +711,7 @@ mod server { check_server_auth, } = self; let requester = SchedulerRequester { - client: Mutex::new(reqwest::Client::new()), + client: Mutex::new(reqwest::blocking::Client::new()), }; macro_rules! check_server_auth_or_err { @@ -720,7 +747,7 @@ mod server { } fn maybe_update_certs( - client: &mut reqwest::Client, + client: &mut reqwest::blocking::Client, certs: &mut HashMap, Vec)>, server_id: ServerId, cert_digest: Vec, @@ -735,7 +762,7 @@ mod server { "Adding new certificate for {} to scheduler", server_id.addr() ); - let mut client_builder = reqwest::ClientBuilder::new(); + let mut client_builder = reqwest::blocking::ClientBuilder::new(); // Add all the certificates we know about client_builder = client_builder.add_root_certificate( reqwest::Certificate::from_pem(&cert_pem) @@ -849,7 +876,7 @@ mod server { } struct SchedulerRequester { - client: Mutex, + client: Mutex, } impl dist::SchedulerOutgoing for SchedulerRequester { @@ -893,10 +920,8 @@ mod server { create_https_cert_and_privkey(public_addr) .context("failed to create HTTPS certificate for server")?; let mut jwt_key = vec![0; JWT_KEY_LENGTH]; - let mut rng = - rand::OsRng::new().context("Failed to initialise a random number generator")?; - rng.fill_bytes(&mut jwt_key); - let server_nonce = ServerNonce::from_rng(&mut rng); + OsRng.fill_bytes(&mut jwt_key); + let server_nonce = ServerNonce::new(); Ok(Self { public_addr, @@ -933,14 +958,14 @@ mod server { let job_authorizer = JWTJobAuthorizer::new(jwt_key); let heartbeat_url = urls::scheduler_heartbeat_server(&scheduler_url); let requester = ServerRequester { - client: reqwest::Client::new(), + client: reqwest::blocking::Client::new(), scheduler_url, scheduler_auth: scheduler_auth.clone(), }; // TODO: detect if this panics thread::spawn(move || { - let client = reqwest::Client::new(); + let client = reqwest::blocking::Client::new(); loop { trace!("Performing heartbeat"); match bincode_req( @@ -1026,7 +1051,7 @@ mod server { } struct ServerRequester { - client: reqwest::Client, + client: reqwest::blocking::Client, scheduler_url: reqwest::Url, scheduler_auth: String, } @@ -1058,12 +1083,10 @@ mod client { self, AllocJobResult, CompileCommand, JobAlloc, PathTransformer, RunJobResult, SchedulerStatusResult, SubmitToolchainResult, Toolchain, }; - use crate::util::SpawnExt; + use byteorder::{BigEndian, WriteBytesExt}; use flate2::write::ZlibEncoder as ZlibWriteEncoder; use flate2::Compression; - use futures::Future; - use futures_03::executor::ThreadPool; use std::collections::HashMap; use std::io::Write; use std::path::{Path, PathBuf}; @@ -1087,16 +1110,16 @@ mod client { server_certs: Arc, Vec>>>, // TODO: this should really only use the async client, but reqwest async bodies are extremely limited // and only support owned bytes, which means the whole toolchain would end up in memory - client: Arc>, - client_async: Arc>, - pool: ThreadPool, + client: Arc>, + client_async: Arc>, + pool: tokio::runtime::Handle, tc_cache: Arc, rewrite_includes_only: bool, } impl Client { pub fn new( - pool: &ThreadPool, + pool: &tokio::runtime::Handle, scheduler_url: reqwest::Url, cache_dir: &Path, cache_size: u64, @@ -1106,12 +1129,12 @@ mod client { ) -> Result { let timeout = Duration::new(REQUEST_TIMEOUT_SECS, 0); let connect_timeout = Duration::new(CONNECT_TIMEOUT_SECS, 0); - let client = reqwest::ClientBuilder::new() + let client = reqwest::blocking::ClientBuilder::new() .timeout(timeout) .connect_timeout(connect_timeout) .build() .context("failed to create a HTTP client")?; - let client_async = reqwest::r#async::ClientBuilder::new() + let client_async = reqwest::ClientBuilder::new() .timeout(timeout) .connect_timeout(connect_timeout) .build() @@ -1132,14 +1155,14 @@ mod client { } fn update_certs( - client: &mut reqwest::Client, - client_async: &mut reqwest::r#async::Client, + client: &mut reqwest::blocking::Client, + client_async: &mut reqwest::Client, certs: &mut HashMap, Vec>, cert_digest: Vec, cert_pem: Vec, ) -> Result<()> { - let mut client_builder = reqwest::ClientBuilder::new(); - let mut client_async_builder = reqwest::r#async::ClientBuilder::new(); + let mut client_builder = reqwest::blocking::ClientBuilder::new(); + let mut client_async_builder = reqwest::ClientBuilder::new(); // Add all the certificates we know about client_builder = client_builder.add_root_certificate( reqwest::Certificate::from_pem(&cert_pem) @@ -1175,24 +1198,26 @@ mod client { } } + #[async_trait::async_trait] impl dist::Client for Client { - fn do_alloc_job(&self, tc: Toolchain) -> SFuture { + async fn do_alloc_job(&self, tc: Toolchain) -> Result { let scheduler_url = self.scheduler_url.clone(); let url = urls::scheduler_alloc_job(&scheduler_url); let mut req = self.client_async.lock().unwrap().post(url); - req = ftry!(req.bearer_auth(self.auth_token.clone()).bincode(&tc)); + req = req.bearer_auth(self.auth_token.clone()).bincode(&tc)?; let client = self.client.clone(); let client_async = self.client_async.clone(); let server_certs = self.server_certs.clone(); - Box::new(bincode_req_fut(req).and_then(move |res| match res { + + match bincode_req_fut(req).await? { AllocJobHttpResponse::Success { job_alloc, need_toolchain, cert_digest, } => { let server_id = job_alloc.server_id; - let alloc_job_res = f_ok(AllocJobResult::Success { + let alloc_job_res = Ok(AllocJobResult::Success { job_alloc, need_toolchain, }); @@ -1205,101 +1230,109 @@ mod client { ); let url = urls::scheduler_server_certificate(&scheduler_url, server_id); let req = client_async.lock().unwrap().get(url); - Box::new( - bincode_req_fut(req) - .map_err(|e| e.context("GET to scheduler server_certificate failed")) - .and_then(move |res: ServerCertificateHttpResponse| { - ftry!(Self::update_certs( - &mut client.lock().unwrap(), - &mut client_async.lock().unwrap(), - &mut server_certs.lock().unwrap(), - res.cert_digest, - res.cert_pem, - )); - alloc_job_res - }), - ) + let res: ServerCertificateHttpResponse = bincode_req_fut(req) + .await + .context("GET to scheduler server_certificate failed")?; + + Self::update_certs( + &mut client.lock().unwrap(), + &mut client_async.lock().unwrap(), + &mut server_certs.lock().unwrap(), + res.cert_digest, + res.cert_pem, + ).context("Failed to update certificate").unwrap_or_else(|e| { warn!("Failed to update certificate: {:?}", e) }); + + alloc_job_res } - AllocJobHttpResponse::Fail { msg } => f_ok(AllocJobResult::Fail { msg }), - })) + AllocJobHttpResponse::Fail { msg } => Ok(AllocJobResult::Fail { msg }), + } } - fn do_get_status(&self) -> SFuture { + + async fn do_get_status(&self) -> Result { let scheduler_url = self.scheduler_url.clone(); let url = urls::scheduler_status(&scheduler_url); let req = self.client.lock().unwrap().get(url); - Box::new(self.pool.spawn_fn(move || bincode_req(req))) + + self.pool.spawn_blocking(move || bincode_req(req)).await? } - fn do_submit_toolchain( + + async fn do_submit_toolchain( &self, job_alloc: JobAlloc, tc: Toolchain, - ) -> SFuture { + ) -> Result { match self.tc_cache.get_toolchain(&tc) { Ok(Some(toolchain_file)) => { let url = urls::server_submit_toolchain(job_alloc.server_id, job_alloc.job_id); let req = self.client.lock().unwrap().post(url); - - Box::new(self.pool.spawn_fn(move || { - let req = req.bearer_auth(job_alloc.auth.clone()).body(toolchain_file); + self.pool.spawn_blocking(move || { + let toolchain_file_size = toolchain_file.metadata()?.len(); + let body = + reqwest::blocking::Body::sized(toolchain_file, toolchain_file_size); + let req = req.bearer_auth(job_alloc.auth.clone()).body(body); bincode_req(req) - })) + }) + .await? } - Ok(None) => f_err(anyhow!("couldn't find toolchain locally")), - Err(e) => f_err(e), + Ok(None) => Err(anyhow!("couldn't find toolchain locally")), + Err(e) => Err(e), } } - fn do_run_job( + + async fn do_run_job( &self, job_alloc: JobAlloc, command: CompileCommand, outputs: Vec, inputs_packager: Box, - ) -> SFuture<(RunJobResult, PathTransformer)> { + ) -> Result<(RunJobResult, PathTransformer)> { let url = urls::server_run_job(job_alloc.server_id, job_alloc.job_id); let mut req = self.client.lock().unwrap().post(url); - Box::new(self.pool.spawn_fn(move || { - let bincode = bincode::serialize(&RunJobHttpRequest { command, outputs }) - .context("failed to serialize run job request")?; - let bincode_length = bincode.len(); - - let mut body = vec![]; - body.write_u32::(bincode_length as u32) - .expect("Infallible write of bincode length to vec failed"); - body.write_all(&bincode) - .expect("Infallible write of bincode body to vec failed"); - let path_transformer; - { - let mut compressor = ZlibWriteEncoder::new(&mut body, Compression::fast()); - path_transformer = inputs_packager - .write_inputs(&mut compressor) - .context("Could not write inputs for compilation")?; - compressor.flush().context("failed to flush compressor")?; - trace!( - "Compressed inputs from {} -> {}", - compressor.total_in(), - compressor.total_out() - ); - compressor.finish().context("failed to finish compressor")?; - } + self.pool.spawn_blocking(move || { + let bincode = bincode::serialize(&RunJobHttpRequest { command, outputs }) + .context("failed to serialize run job request")?; + let bincode_length = bincode.len(); + + let mut body = vec![]; + body.write_u32::(bincode_length as u32) + .expect("Infallible write of bincode length to vec failed"); + body.write_all(&bincode) + .expect("Infallible write of bincode body to vec failed"); + let path_transformer; + { + let mut compressor = ZlibWriteEncoder::new(&mut body, Compression::fast()); + path_transformer = inputs_packager + .write_inputs(&mut compressor) + .context("Could not write inputs for compilation")?; + compressor.flush().context("failed to flush compressor")?; + trace!( + "Compressed inputs from {} -> {}", + compressor.total_in(), + compressor.total_out() + ); + compressor.finish().context("failed to finish compressor")?; + } - req = req.bearer_auth(job_alloc.auth.clone()).bytes(body); - bincode_req(req).map(|res| (res, path_transformer)) - })) + req = req.bearer_auth(job_alloc.auth.clone()).bytes(body); + bincode_req(req).map(|res| (res, path_transformer)) + }) + .await? } - fn put_toolchain( + async fn put_toolchain( &self, - compiler_path: &Path, - weak_key: &str, + compiler_path: PathBuf, + weak_key: String, toolchain_packager: Box, - ) -> SFuture<(Toolchain, Option<(String, PathBuf)>)> { + ) -> Result<(Toolchain, Option<(String, PathBuf)>)> { let compiler_path = compiler_path.to_owned(); let weak_key = weak_key.to_owned(); let tc_cache = self.tc_cache.clone(); - Box::new(self.pool.spawn_fn(move || { - tc_cache.put_toolchain(&compiler_path, &weak_key, toolchain_packager) - })) + + self.pool.spawn_blocking(move || { + tc_cache.put_toolchain(compiler_path, weak_key, toolchain_packager) + }).await? } fn rewrite_includes_only(&self) -> bool { @@ -1313,3 +1346,152 @@ mod client { } } } + +#[cfg(all(test, feature = "vs_openssl"))] +mod tests { + + use crate::dist::http::server::create_https_cert_and_privkey; + use crate::dist::SocketAddr; + use anyhow::{Context, Result}; + + #[test] + fn create_cert_and_sk() { + let addr = "242.11.9.38:29114".parse().unwrap(); + + fn legacy_create_https_cert_and_privkey( + addr: SocketAddr, + ) -> Result<(Vec, Vec, Vec)> { + let rsa_key = openssl::rsa::Rsa::::generate(2048) + .context("failed to generate rsa privkey")?; + let privkey_pem = rsa_key + .private_key_to_pem() + .context("failed to create pem from rsa privkey")?; + let privkey: openssl::pkey::PKey = + openssl::pkey::PKey::from_rsa(rsa_key) + .context("failed to create openssl pkey from rsa privkey")?; + let mut builder = + openssl::x509::X509::builder().context("failed to create x509 builder")?; + + // Populate the certificate with the necessary parts, mostly from + // mkcert in openssl + builder + .set_version(2) + .context("failed to set x509 version")?; + let serial_number = openssl::bn::BigNum::from_u32(0) + .and_then(|bn| bn.to_asn1_integer()) + .context("failed to create openssl asn1 0")?; + builder + .set_serial_number(serial_number.as_ref()) + .context("failed to set x509 serial number")?; + let not_before = openssl::asn1::Asn1Time::days_from_now(0) + .context("failed to create openssl not before asn1")?; + builder + .set_not_before(not_before.as_ref()) + .context("failed to set not before on x509")?; + let not_after = openssl::asn1::Asn1Time::days_from_now(365) + .context("failed to create openssl not after asn1")?; + builder + .set_not_after(not_after.as_ref()) + .context("failed to set not after on x509")?; + builder + .set_pubkey(privkey.as_ref()) + .context("failed to set pubkey for x509")?; + + let mut name = openssl::x509::X509Name::builder()?; + name.append_entry_by_nid(openssl::nid::Nid::COMMONNAME, &addr.to_string())?; + let name = name.build(); + + builder + .set_subject_name(&name) + .context("failed to set subject name")?; + builder + .set_issuer_name(&name) + .context("failed to set issuer name")?; + + // Add the SubjectAlternativeName + let extension = openssl::x509::extension::SubjectAlternativeName::new() + .ip(&addr.ip().to_string()) + .build(&builder.x509v3_context(None, None)) + .context("failed to build SAN extension for x509")?; + builder + .append_extension(extension) + .context("failed to append SAN extension for x509")?; + + // Add ExtendedKeyUsage + let ext_key_usage = openssl::x509::extension::ExtendedKeyUsage::new() + .server_auth() + .build() + .context("failed to build EKU extension for x509")?; + builder + .append_extension(ext_key_usage) + .context("failes to append EKU extension for x509")?; + + // Finish the certificate + builder + .sign(&privkey, openssl::hash::MessageDigest::sha1()) + .context("failed to sign x509 with sha1")?; + let cert: openssl::x509::X509 = builder.build(); + let cert_pem = cert.to_pem().context("failed to create pem from x509")?; + let cert_digest = cert + .digest(openssl::hash::MessageDigest::sha256()) + .context("failed to create digest of x509 certificate")? + .as_ref() + .to_owned(); + + Ok((cert_digest, cert_pem, privkey_pem)) + } + + struct Triple { + pub cert_digest: Vec, + pub cert_pem: Vec, + pub privkey_pem: Vec, + }; + + impl From<(Vec, Vec, Vec)> for Triple { + fn from((cert_digest, cert_pem, privkey_pem): (Vec, Vec, Vec)) -> Self { + Self { + cert_digest, + cert_pem, + privkey_pem, + } + } + } + + use std::io::Write; + + let convert = |tag: &'static str, data: &[u8]| { + let mut bufread = std::io::BufReader::new(data); + let pem = picky::pem::Pem::read_from(&mut bufread).expect("PEM must be valid. Q.E.D."); + println!("{} {}", tag, &pem); + let mut f = std::fs::OpenOptions::new() + .truncate(true) + .create(true) + .write(true) + .open(format!("./{}.cert.pem", tag)) + .unwrap(); + f.write_all(pem.to_string().as_bytes()).unwrap(); + let cert = picky::x509::Cert::from_pem(&pem).expect("Cert from PEM must be ok. Q.E.D."); + cert + }; + + let generated: Triple = create_https_cert_and_privkey(addr).unwrap().into(); + let expected: Triple = legacy_create_https_cert_and_privkey(addr).unwrap().into(); + // cert + { + let expected_cert = convert("exp", &expected.cert_pem); + let generated_cert = convert("gen", &generated.cert_pem); + assert_eq!(expected_cert.ty(), generated_cert.ty()); + assert_eq!( + expected_cert.serial_number(), + generated_cert.serial_number() + ); + assert_eq!( + expected_cert.signature_algorithm(), + generated_cert.signature_algorithm() + ); + assert_eq!(expected_cert.subject_name(), generated_cert.subject_name()); + assert_eq!(expected_cert.issuer_name(), generated_cert.issuer_name()); + assert_eq!(expected_cert.extensions(), generated_cert.extensions()); + } + } +} diff --git a/src/dist/mod.rs b/src/dist/mod.rs index 81b968808..ad19716ea 100644 --- a/src/dist/mod.rs +++ b/src/dist/mod.rs @@ -13,12 +13,12 @@ // limitations under the License. use crate::compiler; -use rand::RngCore; +use rand::{rngs::OsRng, RngCore}; use std::ffi::OsString; use std::fmt; use std::io::{self, Read}; use std::net::SocketAddr; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::process; use std::str::FromStr; #[cfg(feature = "dist-server")] @@ -370,8 +370,8 @@ impl FromStr for ServerId { #[serde(deny_unknown_fields)] pub struct ServerNonce(u64); impl ServerNonce { - pub fn from_rng(rng: &mut rand::OsRng) -> Self { - ServerNonce(rng.next_u64()) + pub fn new() -> Self { + ServerNonce(OsRng.next_u64()) } } @@ -710,32 +710,32 @@ pub trait BuilderIncoming: Send + Sync { } ///////// - -pub trait Client { +#[async_trait::async_trait] +pub trait Client: Send + Sync { // To Scheduler - fn do_alloc_job(&self, tc: Toolchain) -> SFuture; + async fn do_alloc_job(&self, tc: Toolchain) -> Result; // To Scheduler - fn do_get_status(&self) -> SFuture; + async fn do_get_status(&self) -> Result; // To Server - fn do_submit_toolchain( + async fn do_submit_toolchain( &self, job_alloc: JobAlloc, tc: Toolchain, - ) -> SFuture; + ) -> Result; // To Server - fn do_run_job( + async fn do_run_job( &self, job_alloc: JobAlloc, command: CompileCommand, outputs: Vec, inputs_packager: Box, - ) -> SFuture<(RunJobResult, PathTransformer)>; - fn put_toolchain( + ) -> Result<(RunJobResult, PathTransformer)>; + async fn put_toolchain( &self, - compiler_path: &Path, - weak_key: &str, + compiler_path: PathBuf, + weak_key: String, toolchain_packager: Box, - ) -> SFuture<(Toolchain, Option<(String, PathBuf)>)>; + ) -> Result<(Toolchain, Option<(String, PathBuf)>)>; fn rewrite_includes_only(&self) -> bool; fn get_custom_toolchain(&self, exe: &PathBuf) -> Option; } diff --git a/src/dist/pkg.rs b/src/dist/pkg.rs index e01ef88c6..616c5d3f5 100644 --- a/src/dist/pkg.rs +++ b/src/dist/pkg.rs @@ -64,6 +64,7 @@ mod toolchain_imp { use crate::errors::*; + #[derive(Default, Debug)] pub struct ToolchainPackageBuilder { // Put dirs and file in a deterministic order (map from tar_path -> real_path) dir_set: BTreeMap, @@ -83,7 +84,7 @@ mod toolchain_imp { } pub fn add_executable_and_deps(&mut self, executable: PathBuf) -> Result<()> { - let mut remaining = vec![executable.to_owned()]; + let mut remaining = vec![executable]; while let Some(obj_path) = remaining.pop() { assert!(obj_path.is_absolute()); let tar_path = tarify_path(&obj_path)?; diff --git a/src/errors.rs b/src/errors.rs index 4673da95e..9b5ed961d 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -13,10 +13,6 @@ // limitations under the License. pub use anyhow::{anyhow, bail, Context, Error}; -use futures::future; -use futures::Future; -use std::boxed::Box; -use std::fmt::Display; use std::process; // We use `anyhow` for error handling. @@ -28,13 +24,13 @@ use std::process; // - There are some combinators below for working with futures. #[cfg(feature = "hyper")] -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct BadHttpStatusError(pub hyper::StatusCode); -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct HttpClientError(pub String); -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ProcessError(pub process::Output); #[cfg(feature = "hyper")] @@ -64,73 +60,3 @@ impl std::fmt::Display for ProcessError { } pub type Result = anyhow::Result; - -pub type SFuture = Box>; -pub type SFutureSend = Box + Send>; - -pub trait FutureContext { - fn fcontext(self, context: C) -> SFuture - where - C: Display + Send + Sync + 'static; - - fn fwith_context(self, callback: CB) -> SFuture - where - CB: FnOnce() -> C + 'static, - C: Display + Send + Sync + 'static; -} - -impl FutureContext for F -where - F: Future + 'static, - F::Error: Into + Send + Sync, -{ - fn fcontext(self, context: C) -> SFuture - where - C: Display + Send + Sync + 'static, - { - Box::new(self.then(|r| r.map_err(F::Error::into).context(context))) - } - - fn fwith_context(self, callback: CB) -> SFuture - where - CB: FnOnce() -> C + 'static, - C: Display + Send + Sync + 'static, - { - Box::new(self.then(|r| r.map_err(F::Error::into).context(callback()))) - } -} - -/// Like `try`, but returns an SFuture instead of a Result. -macro_rules! ftry { - ($e:expr) => { - match $e { - Ok(v) => v, - Err(e) => return Box::new($crate::futures::future::err(e.into())) as SFuture<_>, - } - }; -} - -#[cfg(any(feature = "dist-client", feature = "dist-server"))] -macro_rules! ftry_send { - ($e:expr) => { - match $e { - Ok(v) => v, - Err(e) => return Box::new($crate::futures::future::err(e)) as SFutureSend<_>, - } - }; -} - -pub fn f_ok(t: T) -> SFuture -where - T: 'static, -{ - Box::new(future::ok(t)) -} - -pub fn f_err(e: E) -> SFuture -where - T: 'static, - E: Into, -{ - Box::new(future::err(e.into())) -} diff --git a/src/jobserver.rs b/src/jobserver.rs index 6d5f1aa42..2f2ae6b49 100644 --- a/src/jobserver.rs +++ b/src/jobserver.rs @@ -2,10 +2,9 @@ use std::io; use std::process::Command; use std::sync::Arc; -use futures::future; -use futures::prelude::*; -use futures::sync::mpsc; -use futures::sync::oneshot; +use futures::channel::mpsc; +use futures::channel::oneshot; +use futures::StreamExt; use crate::errors::*; @@ -38,14 +37,19 @@ impl Client { let (helper, tx) = if inherited { (None, None) } else { - let (tx, rx) = mpsc::unbounded::>(); - let mut rx = rx.wait(); + let (tx, mut rx) = mpsc::unbounded::>(); let helper = inner .clone() .into_helper_thread(move |token| { - if let Some(Ok(sender)) = rx.next() { - drop(sender.send(token)); - } + let mut rt = tokio::runtime::Builder::new() + .basic_scheduler() + .build() + .unwrap(); + rt.block_on(async { + if let Some(sender) = rx.next().await { + drop(sender.send(token)); + } + }); }) .expect("failed to spawn helper thread"); (Some(Arc::new(helper)), Some(tx)) @@ -64,18 +68,20 @@ impl Client { /// This should be invoked before any "work" is spawned (for whatever the /// definition of "work" is) to ensure that the system is properly /// rate-limiting itself. - pub fn acquire(&self) -> SFuture { + pub async fn acquire(&self) -> Result { let (helper, tx) = match (self.helper.as_ref(), self.tx.as_ref()) { (Some(a), Some(b)) => (a, b), - _ => return Box::new(future::ok(Acquired { _token: None })), + _ => return Ok(Acquired { _token: None }), }; let (mytx, myrx) = oneshot::channel(); helper.request_token(); tx.unbounded_send(mytx).unwrap(); - Box::new( - myrx.fcontext("jobserver helper panicked") - .and_then(|t| t.context("failed to acquire jobserver token")) - .map(|t| Acquired { _token: Some(t) }), - ) + + let acquired = myrx + .await + .context("jobserver helper panicked")? + .context("failed to acquire jobserver token")?; + + Ok(Acquired { _token: Some(acquired) }) } } diff --git a/src/lib.rs b/src/lib.rs index 48922cc81..658f072e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,15 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(clippy::complexity)] +#![deny(clippy::perf)] #![deny(rust_2018_idioms)] #![recursion_limit = "256"] +#[macro_use] +extern crate async_trait; #[macro_use] extern crate clap; #[macro_use] extern crate counted_array; -#[macro_use] -extern crate futures; #[cfg(feature = "jsonwebtoken")] use jsonwebtoken as jwt; #[macro_use] @@ -54,8 +56,6 @@ mod jobserver; mod mock_command; mod protocol; pub mod server; -#[cfg(feature = "simple-s3")] -mod simples3; #[doc(hidden)] pub mod util; diff --git a/src/main.rs b/src/main.rs index c40b80c3e..36354b45f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -extern crate sccache; +#![allow(clippy::complexity)] +#![deny(clippy::perf)] fn main() { sccache::main(); diff --git a/src/mock_command.rs b/src/mock_command.rs index f431f81e2..59f6112c5 100644 --- a/src/mock_command.rs +++ b/src/mock_command.rs @@ -47,7 +47,6 @@ use crate::errors::*; use crate::jobserver::{Acquired, Client}; -use futures::future::{self, Future}; use std::boxed::Box; use std::ffi::{OsStr, OsString}; use std::fmt; @@ -55,17 +54,18 @@ use std::io; use std::path::Path; use std::process::{Command, ExitStatus, Output, Stdio}; use std::sync::{Arc, Mutex}; -use tokio_io::{AsyncRead, AsyncWrite}; -use tokio_process::{self, ChildStderr, ChildStdin, ChildStdout, CommandExt}; +use tokio::io::{AsyncRead, AsyncWrite}; +use tokio::process::{ChildStderr, ChildStdin, ChildStdout}; /// A trait that provides a subset of the methods of `std::process::Child`. +#[async_trait::async_trait] pub trait CommandChild { /// The type of the process' standard input. - type I: AsyncWrite + Sync + Send + 'static; + type I: AsyncWrite + Unpin + Sync + Send + 'static; /// The type of the process' standard output. - type O: AsyncRead + Sync + Send + 'static; + type O: AsyncRead + Unpin + Sync + Send + 'static; /// The type of the process' standard error. - type E: AsyncRead + Sync + Send + 'static; + type E: AsyncRead + Unpin + Sync + Send + 'static; /// Take the stdin object from the process, if available. fn take_stdin(&mut self) -> Option; @@ -74,15 +74,16 @@ pub trait CommandChild { /// Take the stderr object from the process, if available. fn take_stderr(&mut self) -> Option; /// Wait for the process to complete and return its exit status. - fn wait(self) -> Box>; + async fn wait(self) -> io::Result; /// Wait for the process to complete and return its output. - fn wait_with_output(self) -> Box>; + async fn wait_with_output(self) -> io::Result; } /// A trait that provides a subset of the methods of `std::process::Command`. -pub trait RunCommand: fmt::Debug { +#[async_trait::async_trait] +pub trait RunCommand: fmt::Debug + Send { /// The type returned by `spawn`. - type C: CommandChild + 'static; + type C: CommandChild + Send + 'static; /// Append `arg` to the process commandline. fn arg>(&mut self, arg: S) -> &mut Self; @@ -112,7 +113,7 @@ pub trait RunCommand: fmt::Debug { /// Set the process' stderr from `cfg`. fn stderr(&mut self, cfg: Stdio) -> &mut Self; /// Execute the process and return a process object. - fn spawn(&mut self) -> SFuture; + async fn spawn(&mut self) -> Result; } /// A trait that provides a means to create objects implementing `RunCommand`. @@ -132,7 +133,7 @@ pub trait CommandCreator { } /// A trait for simplifying the normal case while still allowing the mock case requiring mutability. -pub trait CommandCreatorSync: Clone + 'static { +pub trait CommandCreatorSync: Clone + Send + Sync + 'static { type Cmd: RunCommand; fn new(client: &Client) -> Self; @@ -141,40 +142,41 @@ pub trait CommandCreatorSync: Clone + 'static { } pub struct Child { - inner: tokio_process::Child, + inner: tokio::process::Child, token: Acquired, } /// Trivial implementation of `CommandChild` for `std::process::Child`. +#[async_trait::async_trait] impl CommandChild for Child { type I = ChildStdin; type O = ChildStdout; type E = ChildStderr; fn take_stdin(&mut self) -> Option { - self.inner.stdin().take() + self.inner.stdin.take() } fn take_stdout(&mut self) -> Option { - self.inner.stdout().take() + self.inner.stdout.take() } fn take_stderr(&mut self) -> Option { - self.inner.stderr().take() + self.inner.stderr.take() } - fn wait(self) -> Box> { + async fn wait(self) -> io::Result { let Child { inner, token } = self; - Box::new(inner.map(|ret| { + inner.await.map(|ret| { drop(token); ret - })) + }) } - fn wait_with_output(self) -> Box> { + async fn wait_with_output(self) -> io::Result { let Child { inner, token } = self; - Box::new(inner.wait_with_output().map(|ret| { + inner.wait_with_output().await.map(|ret| { drop(token); ret - })) + }) } } @@ -197,6 +199,7 @@ impl AsyncCommand { } /// Trivial implementation of `RunCommand` for `std::process::Command`. +#[async_trait::async_trait] impl RunCommand for AsyncCommand { type C = Child; @@ -259,21 +262,23 @@ impl RunCommand for AsyncCommand { self.inner().stderr(cfg); self } - fn spawn(&mut self) -> SFuture { + async fn spawn(&mut self) -> Result { let mut inner = self.inner.take().unwrap(); inner.env_remove("MAKEFLAGS"); inner.env_remove("MFLAGS"); inner.env_remove("CARGO_MAKEFLAGS"); self.jobserver.configure(&mut inner); - Box::new(self.jobserver.acquire().and_then(move |token| { - let child = inner - .spawn_async() - .with_context(|| format!("failed to spawn {:?}", inner))?; - Ok(Child { - inner: child, - token, - }) - })) + + let token = self.jobserver.acquire().await?; + let mut inner = tokio::process::Command::from(inner); + let child = inner + .spawn() + .with_context(|| format!("failed to spawn {:?}", inner))?; + + Ok(Child { + inner: child, + token, + }) } } @@ -377,6 +382,7 @@ impl MockChild { } } +#[async_trait::async_trait] impl CommandChild for MockChild { type I = io::Cursor>; type O = io::Cursor>; @@ -392,25 +398,23 @@ impl CommandChild for MockChild { self.stderr.take() } - fn wait(mut self) -> Box> { - Box::new(future::result(self.wait_result.take().unwrap())) + async fn wait(mut self) -> io::Result { + self.wait_result.take().unwrap() } - fn wait_with_output(self) -> Box> { + async fn wait_with_output(self) -> io::Result { let MockChild { stdout, stderr, wait_result, .. } = self; - let result = wait_result.unwrap().and_then(|status| { - Ok(Output { - status, - stdout: stdout.map(|c| c.into_inner()).unwrap_or_else(|| vec![]), - stderr: stderr.map(|c| c.into_inner()).unwrap_or_else(|| vec![]), - }) + let result = wait_result.unwrap().map(|status| Output { + status, + stdout: stdout.map(|c| c.into_inner()).unwrap_or_else(Vec::new), + stderr: stderr.map(|c| c.into_inner()).unwrap_or_else(Vec::new), }); - Box::new(future::result(result)) + result } } @@ -436,6 +440,7 @@ pub struct MockCommand { pub args: Vec, } +#[async_trait::async_trait] impl RunCommand for MockCommand { type C = MockChild; @@ -481,10 +486,10 @@ impl RunCommand for MockCommand { fn stderr(&mut self, _cfg: Stdio) -> &mut MockCommand { self } - fn spawn(&mut self) -> SFuture { + async fn spawn(&mut self) -> Result { match self.child.take().unwrap() { - ChildOrCall::Child(c) => Box::new(future::result(c)), - ChildOrCall::Call(f) => Box::new(future::result(f(&self.args))), + ChildOrCall::Child(c) => c, + ChildOrCall::Call(f) => f(&self.args), } } } @@ -534,7 +539,7 @@ impl CommandCreator for MockCommandCreator { } /// To simplify life for using a `CommandCreator` across multiple threads. -impl CommandCreatorSync for Arc> { +impl CommandCreatorSync for Arc> { type Cmd = T::Cmd; fn new(client: &Client) -> Arc> { @@ -551,7 +556,6 @@ mod test { use super::*; use crate::jobserver::Client; use crate::test::utils::*; - use futures::Future; use std::ffi::OsStr; use std::io; use std::process::{ExitStatus, Output}; diff --git a/src/server.rs b/src/server.rs index 39a5f1663..127b5cfac 100644 --- a/src/server.rs +++ b/src/server.rs @@ -14,6 +14,7 @@ // For tokio_io::codec::length_delimited::Framed; #![allow(deprecated)] +#![allow(clippy::complexity)] use crate::cache::{storage_from_config, Storage}; use crate::compiler::{ @@ -28,40 +29,43 @@ use crate::jobserver::Client; use crate::mock_command::{CommandCreatorSync, ProcessCommandCreator}; use crate::protocol::{Compile, CompileFinished, CompileResponse, Request, Response}; use crate::util; +#[cfg(feature = "dist-client")] use anyhow::Context as _; +use bytes::{buf::ext::BufMutExt, Bytes, BytesMut}; use filetime::FileTime; -use futures::sync::mpsc; -use futures::{future, stream, Async, AsyncSink, Future, Poll, Sink, StartSend, Stream}; -use futures_03::compat::Compat; -use futures_03::executor::ThreadPool; +use futures::{channel::mpsc, future, prelude::*, stream}; +use futures::future::FutureExt; +use futures_locks::RwLock; use number_prefix::{binary_prefix, Prefixed, Standalone}; -use std::cell::RefCell; use std::collections::HashMap; use std::env; use std::ffi::{OsStr, OsString}; use std::fs::metadata; +use std::future::Future; use std::io::{self, Write}; +use std::marker::Unpin; #[cfg(feature = "dist-client")] use std::mem; use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; use std::path::PathBuf; use std::pin::Pin; use std::process::{ExitStatus, Output}; -use std::rc::Rc; use std::sync::Arc; #[cfg(feature = "dist-client")] use std::sync::Mutex; -use std::task::{Context, Waker}; +use std::task::{Context, Poll, Waker}; use std::time::Duration; +#[cfg(feature = "dist-client")] use std::time::Instant; use std::u64; -use tokio_compat::runtime::current_thread::Runtime; -use tokio_io::codec::length_delimited; -use tokio_io::codec::length_delimited::Framed; -use tokio_io::{AsyncRead, AsyncWrite}; -use tokio_serde_bincode::{ReadBincode, WriteBincode}; -use tokio_tcp::TcpListener; -use tokio_timer::{Delay, Timeout}; +use tokio::{ + io::{AsyncRead, AsyncWrite}, + net::TcpListener, + time::{self, delay_for, Delay}, +}; +use tokio::runtime::Runtime; +use tokio_serde::Framed; +use tokio_util::codec::{length_delimited, LengthDelimitedCodec}; use tower::Service; use crate::errors::*; @@ -127,11 +131,13 @@ fn notify_server_startup(name: &Option, status: ServerStartup) -> Resu #[cfg(unix)] fn get_signal(status: ExitStatus) -> i32 { use std::os::unix::prelude::*; - status.signal().expect("must have signal") + status + .signal() + .expect("Signals must exist on unix platforms. Q.E.D.") } #[cfg(windows)] fn get_signal(_status: ExitStatus) -> i32 { - panic!("no signals on windows") + unreachable!("Signals do not exists on windows. Q.E.D.") } pub struct DistClientContainer { @@ -143,7 +149,7 @@ pub struct DistClientContainer { #[cfg(feature = "dist-client")] struct DistClientConfig { // Reusable items tied to an SccacheServer instance - pool: ThreadPool, + pool: tokio::runtime::Handle, // From the static dist configuration scheduler_url: Option, @@ -168,7 +174,7 @@ enum DistClientState { #[cfg(not(feature = "dist-client"))] impl DistClientContainer { #[cfg(not(feature = "dist-client"))] - fn new(config: &Config, _: &ThreadPool) -> Self { + fn new(config: &Config, _: &tokio::runtime::Handle) -> Self { if config.dist.scheduler_url.is_some() { warn!("Scheduler address configured but dist feature disabled, disabling distributed sccache") } @@ -181,7 +187,7 @@ impl DistClientContainer { pub fn reset_state(&self) {} - pub fn get_status(&self) -> DistInfo { + pub async fn get_status(&self) -> DistInfo { DistInfo::Disabled("dist-client feature not selected".to_string()) } @@ -192,7 +198,7 @@ impl DistClientContainer { #[cfg(feature = "dist-client")] impl DistClientContainer { - fn new(config: &Config, pool: &ThreadPool) -> Self { + fn new(config: &Config, pool: &tokio::runtime::Handle) -> Self { let config = DistClientConfig { pool: pool.clone(), scheduler_url: config.dist.scheduler_url.clone(), @@ -230,28 +236,41 @@ impl DistClientContainer { } } - pub fn get_status(&self) -> DistInfo { + pub fn get_status(&self) -> impl Future { + // This function can't be wholly async because we can't hold mutex guard + // across the yield point - instead, either return an immediately ready + // future or perform async query with the client cloned beforehand. let mut guard = self.state.lock(); let state = guard.as_mut().unwrap(); let state: &mut DistClientState = &mut **state; - match state { - DistClientState::Disabled => DistInfo::Disabled("disabled".to_string()), - DistClientState::FailWithMessage(cfg, _) => DistInfo::NotConnected( - cfg.scheduler_url.clone(), - "enabled, auth not configured".to_string(), + let (client, scheduler_url) = match state { + DistClientState::Disabled => return Either::Left(future::ready( + DistInfo::Disabled("disabled".to_string())) ), - DistClientState::RetryCreateAt(cfg, _) => DistInfo::NotConnected( - cfg.scheduler_url.clone(), - "enabled, not connected, will retry".to_string(), - ), - DistClientState::Some(cfg, client) => match client.do_get_status().wait() { - Ok(res) => DistInfo::SchedulerStatus(cfg.scheduler_url.clone(), res), - Err(_) => DistInfo::NotConnected( + DistClientState::FailWithMessage(cfg, _) => return Either::Left(future::ready( + DistInfo::NotConnected( cfg.scheduler_url.clone(), + "enabled, auth not configured".to_string(), + ) + )), + DistClientState::RetryCreateAt(cfg, _) => return Either::Left(future::ready( + DistInfo::NotConnected( + cfg.scheduler_url.clone(), + "enabled, not connected, will retry".to_string(), + ) + )), + DistClientState::Some(cfg, client) => (Arc::clone(client), cfg.scheduler_url.clone()), + }; + + Either::Right(Box::pin(async move { + match client.do_get_status().await { + Ok(res) => DistInfo::SchedulerStatus(scheduler_url.clone(), res), + Err(_) => DistInfo::NotConnected( + scheduler_url.clone(), "could not communicate with scheduler".to_string(), - ), - }, - } + ) + } + })) } fn get_client(&self) -> Result>> { @@ -349,8 +368,7 @@ impl DistClientContainer { ); let dist_client = try_or_retry_later!(dist_client.context("failure during dist client creation")); - use crate::dist::Client; - match dist_client.do_get_status().wait() { + match config.pool.block_on(dist::Client::do_get_status(&dist_client)) { Ok(res) => { info!( "Successfully created dist client with {:?} cores across {:?} servers", @@ -389,15 +407,16 @@ impl DistClientContainer { pub fn start_server(config: &Config, port: u16) -> Result<()> { info!("start_server: port: {}", port); let client = unsafe { Client::new() }; - let runtime = Runtime::new()?; - let pool = ThreadPool::builder() - .pool_size(std::cmp::max(20, 2 * num_cpus::get())) - .create()?; + let runtime = tokio::runtime::Builder::new() + .enable_all() + .threaded_scheduler() + .core_threads(std::cmp::max(20, 2 * num_cpus::get())) + .build()?; + let pool = runtime.handle().clone(); let dist_client = DistClientContainer::new(config, &pool); - let storage = storage_from_config(config, &pool); + let storage = storage_from_config(config, runtime.handle()); let res = SccacheServer::::new( port, - pool, runtime, client, dist_client, @@ -409,7 +428,7 @@ pub fn start_server(config: &Config, port: u16) -> Result<()> { let port = srv.port(); info!("server started, listening on port {}", port); notify_server_startup(¬ify, ServerStartup::Ok { port })?; - srv.run(future::empty::<(), ()>())?; + srv.run(future::pending::<()>())?; Ok(()) } Err(e) => { @@ -440,20 +459,20 @@ pub struct SccacheServer { impl SccacheServer { pub fn new( port: u16, - pool: ThreadPool, - runtime: Runtime, + mut runtime: Runtime, client: Client, dist_client: DistClientContainer, - storage: Arc, + storage: Arc, ) -> Result> { let addr = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), port); - let listener = TcpListener::bind(&SocketAddr::V4(addr))?; + let listener = runtime.block_on(TcpListener::bind(&SocketAddr::V4(addr)))?; // Prepare the service which we'll use to service all incoming TCP // connections. let (tx, rx) = mpsc::channel(1); let (wait, info) = WaitUntilZero::new(); - let service = SccacheService::new(dist_client, storage, &client, pool, tx, info); + let rt_handle = runtime.handle().clone(); + let service = SccacheService::new(dist_client, storage, &client, rt_handle, tx, info); Ok(SccacheServer { runtime, @@ -473,14 +492,14 @@ impl SccacheServer { /// Set the storage this server will use. #[allow(dead_code)] - pub fn set_storage(&mut self, storage: Arc) { + pub fn set_storage(&mut self, storage: Arc) { self.service.storage = storage; } /// Returns a reference to a thread pool to run work on #[allow(dead_code)] - pub fn pool(&self) -> &ThreadPool { - &self.service.pool + pub fn pool(&self) -> &tokio::runtime::Handle { + &self.service.rt } /// Returns a reference to the command creator this server will use @@ -502,15 +521,12 @@ impl SccacheServer { /// long anyway. pub fn run(self, shutdown: F) -> io::Result<()> where - F: Future, + F: Future + Send + 'static, + C: Send, { - self._run(Box::new(shutdown.then(|_| Ok(())))) - } - - fn _run<'a>(self, shutdown: Box + 'a>) -> io::Result<()> { let SccacheServer { mut runtime, - listener, + mut listener, rx, service, timeout, @@ -519,14 +535,16 @@ impl SccacheServer { // Create our "server future" which will simply handle all incoming // connections in separate tasks. - let server = listener.incoming().for_each(move |socket| { + let server = listener.incoming().try_for_each(move |socket| { trace!("incoming connection"); - tokio_compat::runtime::current_thread::TaskExecutor::current() - .spawn_local(Box::new(service.clone().bind(socket).map_err(|err| { - error!("{}", err); - }))) - .unwrap(); - Ok(()) + let conn = service.clone().bind(socket).map_err(|res| { + error!("Failed to bind socket: {}", res); + }); + + // We're not interested if the task panicked; immediately process + // another connection + let _ = tokio::spawn(conn); + async { Ok(()) } }); // Right now there's a whole bunch of ways to shut down this server for @@ -541,39 +559,37 @@ impl SccacheServer { // inactivity, and this is then select'd with the `shutdown` future // passed to this function. - let shutdown = shutdown.map(|a| { + let shutdown = shutdown.map(|_| { info!("shutting down due to explicit signal"); - a }); - let mut futures = vec![ - Box::new(server) as Box>, - Box::new( - shutdown - .map_err(|()| io::Error::new(io::ErrorKind::Other, "shutdown signal failed")), - ), - ]; - - let shutdown_idle = ShutdownOrInactive { - rx, - timeout: if timeout != Duration::new(0, 0) { - Some(Delay::new(Instant::now() + timeout)) - } else { - None - }, - timeout_dur: timeout, - }; - futures.push(Box::new(shutdown_idle.map(|a| { + let shutdown_idle = async { + ShutdownOrInactive { + rx, + timeout: if timeout != Duration::new(0, 0) { + Some(delay_for(timeout)) + } else { + None + }, + timeout_dur: timeout, + } + .await; info!("shutting down due to being idle or request"); - a - }))); + }; - let server = future::select_all(futures); - runtime.block_on(server).map_err(|p| p.0)?; + runtime.block_on(async { + futures::select! { + server = server.fuse() => server, + _res = shutdown.fuse() => Ok(()), + _res = shutdown_idle.fuse() => Ok(()), + } + })?; + const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(30); info!( - "moving into the shutdown phase now, waiting at most 10 seconds \ - for all client requests to complete" + "moving into the shutdown phase now, waiting at most {} seconds \ + for all client requests to complete", + SHUTDOWN_TIMEOUT.as_secs() ); // Once our server has shut down either due to inactivity or a manual @@ -584,13 +600,10 @@ impl SccacheServer { // Note that we cap the amount of time this can take, however, as we // don't want to wait *too* long. runtime - .block_on(Timeout::new(Compat::new(wait), Duration::new(30, 0))) - .map_err(|e| { - if e.is_inner() { - e.into_inner().unwrap() - } else { - io::Error::new(io::ErrorKind::Other, e) - } + .block_on(async { + time::timeout(SHUTDOWN_TIMEOUT, wait) + .await + .unwrap_or_else(|e| Err(io::Error::new(io::ErrorKind::Other, e))) })?; info!("ok, fully shutting down now"); @@ -599,12 +612,16 @@ impl SccacheServer { } } +/// maps a compiler proxy path to a compiler proxy and it's last modification time +type CompilerProxyMap = HashMap>, FileTime)>; + +/// maps a compiler path to a compiler cache entry type CompilerMap = HashMap>>; /// entry of the compiler cache -struct CompilerCacheEntry { +struct CompilerCacheEntry { /// compiler argument trait obj - pub compiler: Box>, + pub compiler: Box + 'static>, /// modification time of the compilers executable file pub mtime: FileTime, /// distributed compilation extra info @@ -612,11 +629,9 @@ struct CompilerCacheEntry { } impl CompilerCacheEntry -where - C: CommandCreatorSync, { fn new( - compiler: Box>, + compiler: Box + 'static>, mtime: FileTime, dist_info: Option<(PathBuf, FileTime)>, ) -> Self { @@ -629,28 +644,29 @@ where } /// Service implementation for sccache #[derive(Clone)] -struct SccacheService { +struct SccacheService where C: Send { /// Server statistics. - stats: Rc>, + stats: Arc>, /// Distributed sccache client - dist_client: Rc, + dist_client: Arc, /// Cache storage. - storage: Arc, + storage: Arc, /// A cache of known compiler info. - compilers: Rc>>, + compilers: Arc>>, /// map the cwd with compiler proxy path to a proxy resolver, which /// will dynamically resolve the input compiler for the current context /// (usually file or current working directory) /// the associated `FileTime` is the modification time of /// the compiler proxy, in order to track updates of the proxy itself - compiler_proxies: Rc>, FileTime)>>>, + compiler_proxies: Arc>>, - /// Thread pool to execute work in - pool: ThreadPool, + /// Task pool for blocking (used mostly for disk I/O-bound tasks) and + // non-blocking tasks + rt: tokio::runtime::Handle, /// An object for creating commands. /// @@ -684,13 +700,13 @@ pub enum ServerMessage { Shutdown, } -impl Service for SccacheService +impl Service for Arc> where - C: CommandCreatorSync + 'static, + C: CommandCreatorSync + Send + Sync + 'static, { type Response = SccacheResponse; type Error = Error; - type Future = SFuture; + type Future = Pin> + Send + 'static>>; fn call(&mut self, req: SccacheRequest) -> Self::Future { trace!("handle_client"); @@ -700,75 +716,95 @@ where // that every message is received. drop(self.tx.clone().start_send(ServerMessage::Request)); - let res: SFuture = match req.into_inner() { - Request::Compile(compile) => { - debug!("handle_client: compile"); - self.stats.borrow_mut().compile_requests += 1; - return self.handle_compile(compile); - } - Request::GetStats => { - debug!("handle_client: get_stats"); - Box::new(self.get_info().map(|i| Response::Stats(Box::new(i)))) - } - Request::DistStatus => { - debug!("handle_client: dist_status"); - Box::new(self.get_dist_status().map(Response::DistStatus)) - } - Request::ZeroStats => { - debug!("handle_client: zero_stats"); - self.zero_stats(); - Box::new(self.get_info().map(|i| Response::Stats(Box::new(i)))) - } - Request::Shutdown => { - debug!("handle_client: shutdown"); - let future = self - .tx - .clone() - .send(ServerMessage::Shutdown) - .then(|_| Ok(())); - let info_future = self.get_info(); - return Box::new(future.join(info_future).map(move |(_, info)| { - Message::WithoutBody(Response::ShuttingDown(Box::new(info))) - })); + let me = self.clone(); + Box::pin(async move { + match req.into_inner() { + Request::Compile(compile) => { + debug!("handle_client: compile"); + me.stats.write().await.compile_requests += 1; + me.handle_compile(compile).await + } + Request::GetStats => { + debug!("handle_client: get_stats"); + me + .get_info() + .await + .map(|i| Response::Stats(Box::new(i))) + .map(Message::WithoutBody) + } + Request::DistStatus => { + debug!("handle_client: dist_status"); + me + .get_dist_status() + .await + .map(Response::DistStatus) + .map(Message::WithoutBody) + } + Request::ZeroStats => { + debug!("handle_client: zero_stats"); + me.zero_stats().await; + me + .get_info() + .await + .map(|i| Response::Stats(Box::new(i))) + .map(Message::WithoutBody) + } + Request::Shutdown => { + debug!("handle_client: shutdown"); + let mut tx = me.tx.clone(); + future::try_join( + async { + let _ = tx.send(ServerMessage::Shutdown).await; + Ok(()) + }, + me.get_info(), + ) + .await + .map(move |(_, info)| { + Message::WithoutBody(Response::ShuttingDown(Box::new(info))) + }) + } } - }; - - Box::new(res.map(Message::WithoutBody)) + }) } - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) } -} + } + +use futures::future::Either; +use futures::TryStreamExt; impl SccacheService where - C: CommandCreatorSync, + C: CommandCreatorSync + Clone + Send + Sync + 'static, { pub fn new( dist_client: DistClientContainer, - storage: Arc, + storage: Arc, client: &Client, - pool: ThreadPool, + rt: tokio::runtime::Handle, tx: mpsc::Sender, info: ActiveInfo, ) -> SccacheService { SccacheService { - stats: Rc::new(RefCell::new(ServerStats::default())), - dist_client: Rc::new(dist_client), + stats: Arc::new(RwLock::new(ServerStats::default())), + dist_client: Arc::new(dist_client), storage, - compilers: Rc::new(RefCell::new(HashMap::new())), - compiler_proxies: Rc::new(RefCell::new(HashMap::new())), - pool, + compilers: Arc::new(RwLock::new(HashMap::new())), + compiler_proxies: Arc::new(RwLock::new(HashMap::new())), + rt, creator: C::new(client), tx, info, } } - fn bind(mut self, socket: T) -> impl Future + // Cannot use `async fn` here, the bounds would be not sufficient + fn bind(self, socket: T) -> impl Future> + Send + Sized + 'static where - T: AsyncRead + AsyncWrite + 'static, + T: AsyncRead + AsyncWrite + Unpin + Send + 'static, { let mut builder = length_delimited::Builder::new(); if let Ok(max_frame_length_str) = env::var("SCCACHE_MAX_FRAME_LENGTH") { @@ -781,61 +817,59 @@ where let io = builder.new_framed(socket); let (sink, stream) = SccacheTransport { - inner: WriteBincode::new(ReadBincode::new(io)), + inner: Framed::new(io.sink_err_into().err_into(), BincodeCodec), } .split(); - let sink = sink.sink_from_err::(); + let sink = sink.sink_err_into::(); + let me = Arc::new(self); stream - .from_err::() - .and_then(move |input| self.call(input)) - .and_then(|message| { - let f: Box> = match message { - Message::WithoutBody(message) => Box::new(stream::once(Ok(Frame::Message { - message, - body: false, - }))), - Message::WithBody(message, body) => Box::new( - stream::once(Ok(Frame::Message { - message, - body: true, - })) - .chain(Compat::new(body).map(|chunk| Frame::Body { chunk: Some(chunk) })) - .chain(stream::once(Ok(Frame::Body { chunk: None }))), - ), - }; - Ok(f.from_err::()) - }) - .flatten() - .forward(sink) - .map(|_| ()) + .err_into::() + .and_then(move |input| me.clone().call(input)) + .and_then(move |message| async move { + let fut = match message { + Message::WithoutBody(message) => { + let stream = + stream::once(async move { + Ok(Frame::Message { message }) + }); + Either::Left(stream) + } + Message::WithBody(message, body) => { + let stream = stream::once(async move { Ok(Frame::Message { message }) }) + .chain(body.map_ok(|chunk| Frame::Body { chunk: Some(chunk) })) + .chain(stream::once(async move { Ok(Frame::Body { chunk: None }) })); + Either::Right(stream) + } + }; + Ok(Box::pin(fut)) + }) + .try_flatten() + .forward(sink) } /// Get dist status. - fn get_dist_status(&self) -> SFuture { - f_ok(self.dist_client.get_status()) + async fn get_dist_status(&self) -> Result { + Ok(self.dist_client.get_status().await) } /// Get info and stats about the cache. - fn get_info(&self) -> SFuture { - let stats = self.stats.borrow().clone(); + async fn get_info(&self) -> Result { + let stats = self.stats.read().await.clone(); let cache_location = self.storage.location(); - Box::new( - self.storage - .current_size() - .join(self.storage.max_size()) - .map(move |(cache_size, max_cache_size)| ServerInfo { + futures::try_join!(self.storage.current_size(), self.storage.max_size()) + .map(move |(cache_size, max_cache_size)| ServerInfo { stats, cache_location, cache_size, max_cache_size, - }), - ) + }, + ) } /// Zero stats about the cache. - fn zero_stats(&self) { - *self.stats.borrow_mut() = ServerStats::default(); + async fn zero_stats(&self) { + *self.stats.write().await = ServerStats::default(); } /// Handle a compile request from a client. @@ -843,199 +877,172 @@ where /// This will handle a compile request entirely, generating a response with /// the inital information and an optional body which will eventually /// contain the results of the compilation. - fn handle_compile(&self, compile: Compile) -> SFuture { + async fn handle_compile(&self, compile: Compile) -> Result { let exe = compile.exe; let cmd = compile.args; let cwd: PathBuf = compile.cwd.into(); let env_vars = compile.env_vars; let me = self.clone(); - Box::new( - self.compiler_info(exe.into(), cwd.clone(), &env_vars) - .map(move |info| me.check_compiler(info, cmd, cwd, env_vars)), - ) + let info = self.compiler_info(exe.into(), cwd.clone(), &env_vars).await; + Ok(me.check_compiler(info, cmd, cwd, env_vars).await) } /// Look up compiler info from the cache for the compiler `path`. /// If not cached, determine the compiler type and cache the result. - fn compiler_info( + async fn compiler_info( &self, path: PathBuf, cwd: PathBuf, env: &[(OsString, OsString)], - ) -> SFuture>>> { + ) -> Result>> { trace!("compiler_info"); let me = self.clone(); let me1 = self.clone(); - + let creator = self.creator.clone(); // lookup if compiler proxy exists for the current compiler path let path2 = path.clone(); let path1 = path.clone(); - let env = env - .into_iter() - .cloned() - .collect::>(); - - let resolve_w_proxy = { - let compiler_proxies_borrow = self.compiler_proxies.borrow(); - - if let Some((compiler_proxy, _filetime)) = compiler_proxies_borrow.get(&path) { - let fut = compiler_proxy.resolve_proxied_executable( - self.creator.clone(), - cwd.clone(), - env.as_slice(), + let env = env.to_vec(); + + let resolved_with_proxy = { + let compiler_proxies_borrow = self.compiler_proxies.read().await; + // Create an owned future - compiler proxy is not Send so we can't + // really await while borrowing the proxy since rustc is too conservative + let resolve_proxied_executable = compiler_proxies_borrow.get(&path) + .map(|(compiler_proxy, _filetime)| + compiler_proxy.resolve_proxied_executable( + creator, + cwd.clone(), + env.as_slice(), + ) ); - Box::new(fut.then(|res: Result<_>| Ok(res.ok()))) - } else { - f_ok(None) + + match resolve_proxied_executable { + Some(fut) => fut.await.ok(), + None => None, } }; // use the supplied compiler path as fallback, lookup its modification time too - let w_fallback = resolve_w_proxy.then(move |res: Result>| { - let opt = match res { - Ok(Some(x)) => Some(x), // TODO resolve the path right away - _ => { - // fallback to using the path directly - metadata(&path2) - .map(|attr| FileTime::from_last_modification_time(&attr)) - .ok() - .map(move |filetime| (path2.clone(), filetime)) - } - }; - f_ok(opt) - }); + let (resolved_compiler_path, mtime) = match resolved_with_proxy { + Some(x) => x, // TODO resolve the path right away + _ => { + // fallback to using the path directly + metadata(&path2) + .map(|attr| FileTime::from_last_modification_time(&attr)) + .ok() + .map(move |filetime| (path2, filetime)) + .expect("Must contain sane data, otherwise mtime is not avail") + } + }; - let lookup_compiler = w_fallback.and_then(move |opt: Option<(PathBuf, FileTime)>| { - let (resolved_compiler_path, mtime) = - opt.expect("Must contain sane data, otherwise mtime is not avail"); - - let dist_info = match me1.dist_client.get_client() { - Ok(Some(ref client)) => { - if let Some(archive) = client.get_custom_toolchain(&resolved_compiler_path) { - match metadata(&archive) - .map(|attr| FileTime::from_last_modification_time(&attr)) - { - Ok(mtime) => Some((archive, mtime)), - _ => None, - } - } else { - None + let dist_info = match me1.dist_client.get_client() { + Ok(Some(ref client)) => { + if let Some(archive) = client.get_custom_toolchain(&resolved_compiler_path) { + match metadata(&archive) + .map(|attr| FileTime::from_last_modification_time(&attr)) + { + Ok(mtime) => Some((archive, mtime)), + _ => None, } + } else { + None } - _ => None, - }; + } + _ => None, + }; - let opt = match me1.compilers.borrow().get(&resolved_compiler_path) { - // It's a hit only if the mtime and dist archive data matches. - Some(&Some(ref entry)) => { - if entry.mtime == mtime && entry.dist_info == dist_info { - Some(entry.compiler.clone()) - } else { - None - } + let opt = match me1.compilers.read().await.get(&resolved_compiler_path) { + // It's a hit only if the mtime and dist archive data matches. + Some(&Some(ref entry)) => { + if entry.mtime == mtime && entry.dist_info == dist_info { + Some(entry.compiler.box_clone()) + } else { + None } - _ => None, - }; - f_ok((resolved_compiler_path, mtime, opt, dist_info)) - }); + } + _ => None, + }; - let obtain = lookup_compiler.and_then( - move |(resolved_compiler_path, mtime, opt, dist_info): ( - PathBuf, - FileTime, - Option>>, - Option<(PathBuf, FileTime)>, - )| { - match opt { - Some(info) => { - trace!("compiler_info cache hit"); - f_ok(Ok(info)) + match opt { + Some(info) => { + trace!("compiler_info cache hit"); + Ok(info) + } + None => { + trace!("compiler_info cache miss"); + // Check the compiler type and return the result when + // finished. This generally involves invoking the compiler, + // so do it asynchronously. + + // the compiler path might be compiler proxy, so it is important to use + // `path` (or its clone `path1`) to resolve using that one, not using `resolved_compiler_path` + let info = get_compiler_info::( + me.creator.clone(), + &path1, + &cwd, + env.as_slice(), + &me.rt, + dist_info.clone().map(|(p, _)| p), + ) + .await; + + let (c, proxy) = match info { + Ok((c, proxy)) => (c.clone(), proxy.clone()), + Err(err) => { + trace!("Inserting PLAIN cache map info for {:?}", &path); + me.compilers.write().await.insert(path, None); + + return Err(err); } - None => { - trace!("compiler_info cache miss"); - // Check the compiler type and return the result when - // finished. This generally involves invoking the compiler, - // so do it asynchronously. - - // the compiler path might be compiler proxy, so it is important to use - // `path` (or its clone `path1`) to resolve using that one, not using `resolved_compiler_path` - let x = get_compiler_info::( - me.creator.clone(), - &path1, - &cwd, - env.as_slice(), - &me.pool, - dist_info.clone().map(|(p, _)| p), - ); + }; - Box::new(x.then( - move |info: Result<( - Box>, - Option>>, - )>| { - match info { - Ok((ref c, ref proxy)) => { - // register the proxy for this compiler, so it will be used directly from now on - // and the true/resolved compiler will create table hits in the hash map - // based on the resolved path - if let Some(proxy) = proxy { - trace!( - "Inserting new path proxy {:?} @ {:?} -> {:?}", - &path, - &cwd, - resolved_compiler_path - ); - let proxy: Box> = - proxy.box_clone(); - me.compiler_proxies - .borrow_mut() - .insert(path, (proxy, mtime.clone())); - } - // TODO add some safety checks in case a proxy exists, that the initial `path` is not - // TODO the same as the resolved compiler binary - - // cache - let map_info = - CompilerCacheEntry::new(c.clone(), mtime, dist_info); - trace!( - "Inserting POSSIBLY PROXIED cache map info for {:?}", - &resolved_compiler_path - ); - me.compilers - .borrow_mut() - .insert(resolved_compiler_path, Some(map_info)); - } - Err(_) => { - trace!("Inserting PLAIN cache map info for {:?}", &path); - me.compilers.borrow_mut().insert(path, None); - } - } - // drop the proxy information, response is compiler only - let r: Result>> = info.map(|info| info.0); - f_ok(r) - }, - )) - } + // register the proxy for this compiler, so it will be used directly from now on + // and the true/resolved compiler will create table hits in the hash map + // based on the resolved path + if let Some(proxy) = proxy { + trace!( + "Inserting new path proxy {:?} @ {:?} -> {:?}", + &path, + &cwd, + resolved_compiler_path + ); + me.compiler_proxies + .write().await + .insert(path, (proxy, mtime)); } - }, - ); + // TODO add some safety checks in case a proxy exists, that the initial `path` is not + // TODO the same as the resolved compiler binary + + // cache + let map_info = CompilerCacheEntry::new(c.clone(), mtime, dist_info); + trace!( + "Inserting POSSIBLY PROXIED cache map info for {:?}", + &resolved_compiler_path + ); + me.compilers + .write().await + .insert(resolved_compiler_path, Some(map_info)); - return Box::new(obtain); + // drop the proxy information, response is compiler only + Ok(c) + } + } } /// Check that we can handle and cache `cmd` when run with `compiler`. /// If so, run `start_compile_task` to execute it. - fn check_compiler( + async fn check_compiler( &self, compiler: Result>>, cmd: Vec, cwd: PathBuf, env_vars: Vec<(OsString, OsString)>, ) -> SccacheResponse { - let mut stats = self.stats.borrow_mut(); + let mut stats = self.stats.write().await; match compiler { Err(e) => { debug!("check_compiler: Unsupported compiler: {}", e.to_string()); @@ -1091,7 +1098,7 @@ where arguments: Vec, cwd: PathBuf, env_vars: Vec<(OsString, OsString)>, - tx: mpsc::Sender>, + mut tx: mpsc::Sender>, ) { let force_recache = env_vars .iter() @@ -1103,25 +1110,37 @@ where }; let out_pretty = hasher.output_pretty().into_owned(); let color_mode = hasher.color_mode(); - let result = hasher.get_cached_or_compile( - self.dist_client.get_client(), - self.creator.clone(), - self.storage.clone(), - arguments, - cwd, - env_vars, - cache_control, - self.pool.clone(), - ); let me = self.clone(); let kind = compiler.kind(); - let task = result.then(move |result| { + let dist_client = self.dist_client.get_client(); + let creator = self.creator.clone(); + let storage = self.storage.clone(); + let pool = self.rt.clone(); + + let task = async move { + let result = match dist_client { + Ok(client) => { + hasher.get_cached_or_compile( + client, + creator, + storage, + arguments, + cwd, + env_vars, + cache_control, + pool, + ).await + } + Err(e) => Err(e), + }; let mut cache_write = None; - let mut stats = me.stats.borrow_mut(); - let mut res = CompileFinished::default(); - res.color_mode = color_mode; + let mut res = CompileFinished { + color_mode, + ..CompileFinished::default() + }; match result { Ok((compiled, out)) => { + let mut stats = me.stats.write().await; match compiled { CompileResult::Error => { stats.cache_errors.increment(&kind); @@ -1179,6 +1198,7 @@ where res.stderr = stderr; } Err(err) => { + let mut stats = me.stats.write().await; match err.downcast::() { Ok(ProcessError(output)) => { debug!("Compilation failed: {:?}", output); @@ -1219,37 +1239,39 @@ where } } }; - let send = tx.send(Ok(Response::CompileFinished(res))); + let send = tx.send(Ok(Response::CompileFinished(res))) + .map_err(|e| anyhow!("send on finish failed").context(e)); let me = me.clone(); - let cache_write = cache_write.then(move |result| { - match result { - Err(e) => { - debug!("Error executing cache write: {}", e); - me.stats.borrow_mut().cache_write_errors += 1; - } - //TODO: save cache stats! - Ok(Some(info)) => { - debug!( - "[{}]: Cache write finished in {}", - info.object_file_pretty, - util::fmt_duration_as_secs(&info.duration) - ); - me.stats.borrow_mut().cache_writes += 1; - me.stats.borrow_mut().cache_write_duration += info.duration; + let cache_write = async move { + if let Some(cache_write) = cache_write { + match cache_write.await { + Err(e) => { + debug!("Error executing cache write: {}", e); + me.stats.write().await.cache_write_errors += 1; + } + //TODO: save cache stats! + Ok(info) => { + debug!( + "[{}]: Cache write finished in {}", + info.object_file_pretty, + util::fmt_duration_as_secs(&info.duration) + ); + let mut stats = me.stats.write().await; + stats.cache_writes += 1; + stats.cache_write_duration += info.duration; + } } - - Ok(None) => {} } Ok(()) - }); + }; - send.join(cache_write).then(|_| Ok(())) - }); + futures::future::try_join(send, cache_write).await?; - tokio_compat::runtime::current_thread::TaskExecutor::current() - .spawn_local(Box::new(task)) - .unwrap(); + Ok::<_, Error>(()) + }; + + self.rt.spawn(async move { task.await.unwrap_or_else(|e| { warn!("Failed to execute task: {:?}", e) }); } ); } } @@ -1279,7 +1301,7 @@ impl PerLanguageCount { } /// Statistics about the server. -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug, Default)] pub struct ServerStats { /// The count of client compile requests. pub compile_requests: u64, @@ -1345,34 +1367,6 @@ pub enum DistInfo { SchedulerStatus(Option, dist::SchedulerStatusResult), } -impl Default for ServerStats { - fn default() -> ServerStats { - ServerStats { - compile_requests: u64::default(), - requests_unsupported_compiler: u64::default(), - requests_not_compile: u64::default(), - requests_not_cacheable: u64::default(), - requests_executed: u64::default(), - cache_errors: PerLanguageCount::new(), - cache_hits: PerLanguageCount::new(), - cache_misses: PerLanguageCount::new(), - cache_timeouts: u64::default(), - cache_read_errors: u64::default(), - non_cacheable_compilations: u64::default(), - forced_recaches: u64::default(), - cache_write_errors: u64::default(), - cache_writes: u64::default(), - cache_write_duration: Duration::new(0, 0), - cache_read_hit_duration: Duration::new(0, 0), - cache_read_miss_duration: Duration::new(0, 0), - compile_fails: u64::default(), - not_cached: HashMap::new(), - dist_compiles: HashMap::new(), - dist_errors: u64::default(), - } - } -} - impl ServerStats { /// Print stats to stdout in a human-readable format. /// @@ -1553,7 +1547,7 @@ impl ServerInfo { enum Frame { Body { chunk: Option }, - Message { message: R, body: bool }, + Message { message: R }, } struct Body { @@ -1567,16 +1561,13 @@ impl Body { } } -impl futures_03::Stream for Body { +impl futures::Stream for Body { type Item = Result; fn poll_next( mut self: Pin<&mut Self>, - _cx: &mut Context<'_>, + cx: &mut Context<'_>, ) -> std::task::Poll> { - match Pin::new(&mut self.receiver).poll().unwrap() { - Async::Ready(item) => std::task::Poll::Ready(item), - Async::NotReady => std::task::Poll::Pending, - } + Pin::new(&mut self.receiver).poll_next(cx) } } @@ -1594,6 +1585,32 @@ impl Message { } } +struct BincodeCodec; +impl tokio_serde::Serializer for BincodeCodec +where + T: serde::Serialize, +{ + type Error = Error; + + fn serialize(self: Pin<&mut Self>, item: &T) -> std::result::Result { + let mut bytes = BytesMut::new(); + bincode::serialize_into((&mut bytes).writer(), item)?; + Ok(bytes.freeze()) + } +} + +impl tokio_serde::Deserializer for BincodeCodec +where + T: serde::de::DeserializeOwned, +{ + type Error = Error; + + fn deserialize(self: Pin<&mut Self>, buf: &BytesMut) -> std::result::Result { + let ret = bincode::deserialize(buf)?; + Ok(ret) + } +} + /// Implementation of `Stream + Sink` that tokio-proto is expecting /// /// This type is composed of a few layers: @@ -1609,51 +1626,53 @@ impl Message { /// `Sink` implementation to switch from `BytesMut` to `Response` meaning that /// all `Response` types pushed in will be converted to `BytesMut` and pushed /// below. -struct SccacheTransport { - inner: WriteBincode, Request>, Response>, +struct SccacheTransport { + inner: Framed< + futures::stream::ErrInto< + futures::sink::SinkErrInto< + tokio_util::codec::Framed, + Bytes, + Error, + >, + Error, + >, + Request, + Response, + BincodeCodec, + >, } -impl Stream for SccacheTransport { - type Item = Message>; - type Error = io::Error; +impl Stream for SccacheTransport { + type Item = Result>>; - fn poll(&mut self) -> Poll, io::Error> { - let msg = try_ready!(self.inner.poll().map_err(|e| { - error!("SccacheTransport::poll failed: {}", e); - io::Error::new(io::ErrorKind::Other, e) - })); - Ok(msg.map(Message::WithoutBody).into()) + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.inner) + .poll_next(cx) + .map(|r| r.map(|s| s.map(Message::WithoutBody))) } } -impl Sink for SccacheTransport { - type SinkItem = Frame; - type SinkError = io::Error; +impl Sink> for SccacheTransport { + type Error = Error; + + fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.inner).poll_ready(cx) + } - fn start_send(&mut self, item: Self::SinkItem) -> StartSend { + fn start_send(mut self: Pin<&mut Self>, item: Frame) -> Result<()> { match item { - Frame::Message { message, body } => match self.inner.start_send(message)? { - AsyncSink::Ready => Ok(AsyncSink::Ready), - AsyncSink::NotReady(message) => { - Ok(AsyncSink::NotReady(Frame::Message { message, body })) - } - }, - Frame::Body { chunk: Some(chunk) } => match self.inner.start_send(chunk)? { - AsyncSink::Ready => Ok(AsyncSink::Ready), - AsyncSink::NotReady(chunk) => { - Ok(AsyncSink::NotReady(Frame::Body { chunk: Some(chunk) })) - } - }, - Frame::Body { chunk: None } => Ok(AsyncSink::Ready), + Frame::Message { message } => Pin::new(&mut self.inner).start_send(message), + Frame::Body { chunk: Some(chunk) } => Pin::new(&mut self.inner).start_send(chunk), + Frame::Body { chunk: None } => Ok(()), } } - fn poll_complete(&mut self) -> Poll<(), io::Error> { - self.inner.poll_complete() + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.inner).poll_flush(cx) } - fn close(&mut self) -> Poll<(), io::Error> { - self.inner.close() + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.inner).poll_close(cx) } } @@ -1664,54 +1683,55 @@ struct ShutdownOrInactive { } impl Future for ShutdownOrInactive { - type Item = (); - type Error = io::Error; + type Output = (); - fn poll(&mut self) -> Poll<(), io::Error> { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { loop { - match self.rx.poll().unwrap() { - Async::NotReady => break, + match Pin::new(&mut self.rx).poll_next(cx) { + Poll::Pending => break, // Shutdown received! - Async::Ready(Some(ServerMessage::Shutdown)) => return Ok(().into()), - Async::Ready(Some(ServerMessage::Request)) => { + Poll::Ready(Some(ServerMessage::Shutdown)) => return Poll::Ready(()), + Poll::Ready(Some(ServerMessage::Request)) => { if self.timeout_dur != Duration::new(0, 0) { - self.timeout = Some(Delay::new(Instant::now() + self.timeout_dur)); + self.timeout = Some(delay_for(self.timeout_dur)); } } // All services have shut down, in theory this isn't possible... - Async::Ready(None) => return Ok(().into()), + Poll::Ready(None) => return Poll::Ready(()), } } match self.timeout { - None => Ok(Async::NotReady), - Some(ref mut timeout) => timeout - .poll() - .map_err(|err| io::Error::new(io::ErrorKind::Other, err)), + None => Poll::Pending, + Some(ref mut timeout) => Pin::new(timeout).poll(cx), } } } + +use std::sync::atomic::{AtomicUsize,Ordering}; + /// Helper future which tracks the `ActiveInfo` below. This future will resolve /// once all instances of `ActiveInfo` have been dropped. struct WaitUntilZero { - info: Rc>, + info: Arc, } struct ActiveInfo { - info: Rc>, + info: Arc, } + struct Info { - active: usize, + active: AtomicUsize, waker: Option, } impl WaitUntilZero { fn new() -> (WaitUntilZero, ActiveInfo) { - let info = Rc::new(RefCell::new(Info { - active: 1, + let info = Arc::new(Info { + active: AtomicUsize::from(1_usize), waker: None, - })); + }); (WaitUntilZero { info: info.clone() }, ActiveInfo { info }) } @@ -1719,7 +1739,7 @@ impl WaitUntilZero { impl Clone for ActiveInfo { fn clone(&self) -> ActiveInfo { - self.info.borrow_mut().active += 1; + self.info.active.fetch_add(1_usize, Ordering::SeqCst); ActiveInfo { info: self.info.clone(), } @@ -1728,11 +1748,12 @@ impl Clone for ActiveInfo { impl Drop for ActiveInfo { fn drop(&mut self) { - let mut info = self.info.borrow_mut(); - info.active -= 1; - if info.active == 0 { - if let Some(waker) = info.waker.take() { - waker.wake(); + if self.info.active.fetch_sub(1_usize, Ordering::SeqCst) == 0 { + // TODO use a mutex here for info I guess + if let Some(info) = Arc::get_mut(&mut self.info) { + if let Some(waker) = info.waker.take() { + waker.wake(); + } } } } @@ -1741,13 +1762,16 @@ impl Drop for ActiveInfo { impl std::future::Future for WaitUntilZero { type Output = io::Result<()>; - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> std::task::Poll { - let mut info = self.info.borrow_mut(); - if info.active == 0 { - std::task::Poll::Ready(Ok(())) - } else { - info.waker = Some(cx.waker().clone()); - std::task::Poll::Pending + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> std::task::Poll { + loop { + if self.info.active.load(Ordering::SeqCst) == 0 { + return std::task::Poll::Ready(Ok(())) + } + else if let Some(info) = Arc::get_mut(&mut self.info) { + info.waker = Some(cx.waker().clone()); + // Could stall, figure out a way to be better! Use future_util::Mutex" + return std::task::Poll::Pending + } } } } diff --git a/src/simples3/credential.rs b/src/simples3/credential.rs deleted file mode 100644 index bc7c3adff..000000000 --- a/src/simples3/credential.rs +++ /dev/null @@ -1,530 +0,0 @@ -// Originally from https://github.com/rusoto/rusoto/blob/master/src/credential.rs -//! Types for loading and managing AWS access credentials for API requests. -#![allow(dead_code)] - -use chrono::{offset, DateTime, Duration}; -use directories::UserDirs; -use futures::future::{self, Shared}; -use futures::{Async, Future, Stream}; -use hyper::client::HttpConnector; -use hyper::{Client, Request}; -use hyperx::header::Connection; -use regex::Regex; -use serde_json::{from_str, Value}; -#[allow(unused_imports, deprecated)] -use std::ascii::AsciiExt; -use std::cell::RefCell; -use std::collections::HashMap; -use std::env::*; -use std::fs::{self, File}; -use std::io::prelude::*; -use std::io::BufReader; -use std::path::{Path, PathBuf}; -use std::time::Duration as StdDuration; -use tokio_timer::Timeout; - -use crate::errors::*; -use crate::util::RequestExt; - -/// AWS API access credentials, including access key, secret key, token (for IAM profiles), and -/// expiration timestamp. -#[derive(Clone, Debug)] -pub struct AwsCredentials { - key: String, - secret: String, - token: Option, - expires_at: DateTime, -} - -impl AwsCredentials { - /// Create a new `AwsCredentials` from a key ID, secret key, optional access token, and expiry - /// time. - pub fn new( - key: K, - secret: S, - token: Option, - expires_at: DateTime, - ) -> AwsCredentials - where - K: Into, - S: Into, - { - AwsCredentials { - key: key.into(), - secret: secret.into(), - token, - expires_at, - } - } - - /// Get a reference to the access key ID. - pub fn aws_access_key_id(&self) -> &str { - &self.key - } - - /// Get a reference to the secret access key. - pub fn aws_secret_access_key(&self) -> &str { - &self.secret - } - - /// Get a reference to the expiry time. - pub fn expires_at(&self) -> &DateTime { - &self.expires_at - } - - /// Get a reference to the access token. - pub fn token(&self) -> &Option { - &self.token - } - - /// Determine whether or not the credentials are expired. - fn credentials_are_expired(&self) -> bool { - // This is a rough hack to hopefully avoid someone requesting creds then sitting on them - // before issuing the request: - self.expires_at < offset::Utc::now() + Duration::seconds(20) - } -} - -/// A trait for types that produce `AwsCredentials`. -pub trait ProvideAwsCredentials { - /// Produce a new `AwsCredentials`. - fn credentials(&self) -> SFuture; -} - -/// Provides AWS credentials from environment variables. -pub struct EnvironmentProvider; - -impl ProvideAwsCredentials for EnvironmentProvider { - fn credentials(&self) -> SFuture { - Box::new(future::result(credentials_from_environment())) - } -} - -fn credentials_from_environment() -> Result { - let env_key = var("AWS_ACCESS_KEY_ID").context("No AWS_ACCESS_KEY_ID in environment")?; - let env_secret = - var("AWS_SECRET_ACCESS_KEY").context("No AWS_SECRET_ACCESS_KEY in environment")?; - - if env_key.is_empty() || env_secret.is_empty() { - bail!( - "Couldn't find either AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY or both in environment." - ) - } - - // Present when using temporary credentials, e.g. on Lambda with IAM roles - let token = match var("AWS_SESSION_TOKEN") { - Ok(val) => { - if val.is_empty() { - None - } else { - Some(val) - } - } - Err(_) => None, - }; - - Ok(AwsCredentials::new( - env_key, - env_secret, - token, - in_ten_minutes(), - )) -} - -/// Provides AWS credentials from a profile in a credentials file. -#[derive(Clone, Debug)] -pub struct ProfileProvider { - credentials: Option, - file_path: PathBuf, - profile: String, -} - -impl ProfileProvider { - /// Create a new `ProfileProvider` for the default credentials file path and profile name. - pub fn new() -> Result { - // Default credentials file location: - // ~/.aws/credentials (Linux/Mac) - // %USERPROFILE%\.aws\credentials (Windows) - let profile_location = UserDirs::new() - .map(|d| d.home_dir().join(".aws").join("credentials")) - .context("Couldn't get user directories")?; - - Ok(ProfileProvider { - credentials: None, - file_path: profile_location, - profile: "default".to_owned(), - }) - } - - /// Create a new `ProfileProvider` for the credentials file at the given path, using - /// the given profile. - pub fn with_configuration(file_path: F, profile: P) -> ProfileProvider - where - F: Into, - P: Into, - { - ProfileProvider { - credentials: None, - file_path: file_path.into(), - profile: profile.into(), - } - } - - /// Get a reference to the credentials file path. - pub fn file_path(&self) -> &Path { - self.file_path.as_ref() - } - - /// Get a reference to the profile name. - pub fn profile(&self) -> &str { - &self.profile - } - - /// Set the credentials file path. - pub fn set_file_path(&mut self, file_path: F) - where - F: Into, - { - self.file_path = file_path.into(); - } - - /// Set the profile name. - pub fn set_profile

(&mut self, profile: P) - where - P: Into, - { - self.profile = profile.into(); - } -} - -impl ProvideAwsCredentials for ProfileProvider { - fn credentials(&self) -> SFuture { - let result = parse_credentials_file(self.file_path()); - let result = result - .and_then(|mut profiles| profiles.remove(self.profile()).context("profile not found")); - Box::new(future::result(result)) - } -} - -fn parse_credentials_file(file_path: &Path) -> Result> { - let metadata = fs::metadata(file_path).context("couldn't stat credentials file")?; - if !metadata.is_file() { - bail!("Couldn't open file."); - } - - let file = File::open(file_path)?; - - let profile_regex = Regex::new(r"^\[([^\]]+)\]$").unwrap(); - let mut profiles: HashMap = HashMap::new(); - let mut access_key: Option = None; - let mut secret_key: Option = None; - let mut profile_name: Option = None; - - let file_lines = BufReader::new(&file); - for line in file_lines.lines() { - let unwrapped_line: String = line.unwrap(); - - // skip comments - if unwrapped_line.starts_with('#') { - continue; - } - - // handle the opening of named profile blocks - if profile_regex.is_match(&unwrapped_line) { - if let (Some(profile_name), Some(access_key), Some(secret_key)) = - (profile_name, access_key, secret_key) - { - let creds = AwsCredentials::new(access_key, secret_key, None, in_ten_minutes()); - profiles.insert(profile_name, creds); - } - - access_key = None; - secret_key = None; - - let caps = profile_regex.captures(&unwrapped_line).unwrap(); - profile_name = Some(caps.get(1).unwrap().as_str().to_string()); - continue; - } - - // otherwise look for key=value pairs we care about - let lower_case_line = unwrapped_line.to_ascii_lowercase().to_string(); - - if lower_case_line.contains("aws_access_key_id") && access_key.is_none() { - let v: Vec<&str> = unwrapped_line.split('=').collect(); - if !v.is_empty() { - access_key = Some(v[1].trim_matches(' ').to_string()); - } - } else if lower_case_line.contains("aws_secret_access_key") && secret_key.is_none() { - let v: Vec<&str> = unwrapped_line.split('=').collect(); - if !v.is_empty() { - secret_key = Some(v[1].trim_matches(' ').to_string()); - } - } - - // we could potentially explode here to indicate that the file is invalid - } - - if let (Some(profile_name), Some(access_key), Some(secret_key)) = - (profile_name, access_key, secret_key) - { - let creds = AwsCredentials::new(access_key, secret_key, None, in_ten_minutes()); - profiles.insert(profile_name, creds); - } - - if profiles.is_empty() { - bail!("No credentials found.") - } - - Ok(profiles) -} - -/// Provides AWS credentials from a resource's IAM role. -pub struct IamProvider { - client: Client, -} - -impl IamProvider { - pub fn new() -> IamProvider { - IamProvider { - client: Client::new(), - } - } - - fn iam_role(&self) -> SFuture { - // First get the IAM role - let address = "http://169.254.169.254/latest/meta-data/iam/security-credentials/"; - let req = Request::get(address) - .set_header(Connection::close()) - .body("".into()) - .unwrap(); - let response = self.client.request(req).and_then(|response| { - response.into_body().fold(Vec::new(), |mut body, chunk| { - body.extend_from_slice(&chunk); - Ok::<_, hyper::Error>(body) - }) - }); - - Box::new( - response - .then(|res| { - let bytes = res.context("couldn't connect to metadata service")?; - String::from_utf8(bytes) - .context("Didn't get a parsable response body from metadata service") - }) - .map(move |body| { - let mut address = address.to_string(); - address.push_str(&body); - address - }), - ) - } -} - -impl ProvideAwsCredentials for IamProvider { - fn credentials(&self) -> SFuture { - let url = match var("AWS_IAM_CREDENTIALS_URL") { - Ok(url) => f_ok(url), - Err(_) => self.iam_role(), - }; - let url = url.and_then(|url| { - url.parse::() - .with_context(|| format!("failed to parse `{}` as url", url)) - }); - - let client = self.client.clone(); - let response = url.and_then(move |address| { - debug!("Attempting to fetch credentials from {}", address); - let req = Request::get(address) - .set_header(Connection::close()) - .body("".into()) - .unwrap(); - client.request(req).fcontext("failed to send http request") - }); - let body = response.and_then(|response| { - response - .into_body() - .fold(Vec::new(), |mut body, chunk| { - body.extend_from_slice(&chunk); - Ok::<_, hyper::Error>(body) - }) - .fcontext("failed to read http body") - }); - let body = body - .map_err(|e| anyhow!("Failed to get IAM credentials: {}", e)) - .and_then(|body| String::from_utf8(body).context("failed to read iam role response")); - - let creds = body.and_then(|body| { - let json_object: Value; - match from_str(&body) { - Err(_) => bail!("Couldn't parse metadata response body."), - Ok(val) => json_object = val, - }; - - let access_key; - match json_object.get("AccessKeyId") { - None => bail!("Couldn't find AccessKeyId in response."), - Some(val) => { - access_key = val - .as_str() - .expect("AccessKeyId value was not a string") - .to_owned() - .replace("\"", "") - } - }; - - let secret_key; - match json_object.get("SecretAccessKey") { - None => bail!("Couldn't find SecretAccessKey in response."), - Some(val) => { - secret_key = val - .as_str() - .expect("SecretAccessKey value was not a string") - .to_owned() - .replace("\"", "") - } - }; - - let expiration; - match json_object.get("Expiration") { - None => bail!("Couldn't find Expiration in response."), - Some(val) => { - expiration = val - .as_str() - .expect("Expiration value was not a string") - .to_owned() - .replace("\"", "") - } - }; - - let expiration_time = expiration - .parse() - .context("failed to parse expiration time")?; - - let token_from_response; - match json_object.get("Token") { - None => bail!("Couldn't find Token in response."), - Some(val) => { - token_from_response = val - .as_str() - .expect("Token value was not a string") - .to_owned() - .replace("\"", "") - } - }; - - Ok(AwsCredentials::new( - access_key, - secret_key, - Some(token_from_response), - expiration_time, - )) - }); - - //XXX: this is crappy, but this blocks on non-EC2 machines like - // our mac builders. - let timeout = Timeout::new(creds, StdDuration::from_secs(2)); - - Box::new(timeout.then(|result| match result { - Ok(creds) => Ok(creds), - Err(err) => match err.into_inner() { - None => bail!("took too long to fetch credentials"), - Some(e) => { - warn!("Failed to fetch IAM credentials: {}", e); - Err(e) - } - }, - })) - } -} - -/// Wrapper for ProvideAwsCredentials that caches the credentials returned by the -/// wrapped provider. Each time the credentials are accessed, they are checked to see if -/// they have expired, in which case they are retrieved from the wrapped provider again. -pub struct AutoRefreshingProvider

{ - credentials_provider: P, - cached_credentials: RefCell>>, -} - -impl AutoRefreshingProvider

{ - pub fn new(provider: P) -> AutoRefreshingProvider

{ - AutoRefreshingProvider { - cached_credentials: RefCell::new(provider.credentials().shared()), - credentials_provider: provider, - } - } -} - -impl ProvideAwsCredentials for AutoRefreshingProvider

{ - fn credentials(&self) -> SFuture { - let mut future = self.cached_credentials.borrow_mut(); - if let Ok(Async::Ready(creds)) = future.poll() { - if creds.credentials_are_expired() { - *future = self.credentials_provider.credentials().shared(); - } - } - Box::new(future.clone().then(|result| match result { - Ok(e) => Ok((*e).clone()), - Err(e) => Err(anyhow!(e.to_string())), - })) - } -} - -/// Provides AWS credentials from multiple possible sources using a priority order. -/// -/// The following sources are checked in order for credentials when calling `credentials`: -/// -/// 1. Environment variables: `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` -/// 2. AWS credentials file. Usually located at `~/.aws/credentials`. -/// 3. IAM instance profile. Will only work if running on an EC2 instance with an instance profile/role. -/// -/// If the sources are exhausted without finding credentials, an error is returned. -#[derive(Clone)] -pub struct ChainProvider { - profile_providers: Vec, -} - -impl ProvideAwsCredentials for ChainProvider { - fn credentials(&self) -> SFuture { - let creds = EnvironmentProvider.credentials().map(|c| { - debug!("Using AWS credentials from environment"); - c - }); - let mut creds = Box::new(creds) as SFuture<_>; - for provider in self.profile_providers.iter() { - let alternate = provider.credentials(); - creds = Box::new(creds.or_else(|_| alternate)); - } - Box::new( - creds - .or_else(move |_| { - IamProvider::new().credentials().map(|c| { - debug!("Using AWS credentials from IAM"); - c - }) - }) - .map_err(|_| { - anyhow!( - "Couldn't find AWS credentials in environment, credentials file, or IAM role." - ) - }), - ) - } -} - -impl ChainProvider { - /// Create a new `ChainProvider` using a `ProfileProvider` with the default settings. - pub fn new() -> ChainProvider { - ChainProvider { - profile_providers: ProfileProvider::new().into_iter().collect(), - } - } - - /// Create a new `ChainProvider` using the provided `ProfileProvider`s. - pub fn with_profile_providers(profile_providers: Vec) -> ChainProvider { - ChainProvider { profile_providers } - } -} - -fn in_ten_minutes() -> DateTime { - offset::Utc::now() + Duration::seconds(600) -} diff --git a/src/simples3/mod.rs b/src/simples3/mod.rs deleted file mode 100644 index 2c0205393..000000000 --- a/src/simples3/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2016 Mozilla Foundation -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -mod credential; -mod s3; - -pub use crate::simples3::credential::*; -pub use crate::simples3::s3::*; diff --git a/src/simples3/s3.rs b/src/simples3/s3.rs deleted file mode 100644 index f7e193ae9..000000000 --- a/src/simples3/s3.rs +++ /dev/null @@ -1,251 +0,0 @@ -// Originally from https://github.com/rust-lang/crates.io/blob/master/src/s3/lib.rs -//#![deny(warnings)] - -#[allow(unused_imports, deprecated)] -use std::ascii::AsciiExt; -use std::fmt; - -use crate::simples3::credential::*; -use futures::{Future, Stream}; -use hmac::{Hmac, Mac}; -use hyper::header::HeaderValue; -use hyper::Method; -use hyperx::header; -use reqwest::r#async::{Client, Request}; -use sha1::Sha1; - -use crate::errors::*; -use crate::util::HeadersExt; - -#[derive(Debug, Copy, Clone)] -#[allow(dead_code)] -/// Whether or not to use SSL. -pub enum Ssl { - /// Use SSL. - Yes, - /// Do not use SSL. - No, -} - -fn base_url(endpoint: &str, ssl: Ssl) -> String { - format!( - "{}://{}/", - match ssl { - Ssl::Yes => "https", - Ssl::No => "http", - }, - endpoint - ) -} - -fn hmac(key: &[u8], data: &[u8]) -> Vec { - let mut hmac = Hmac::::new_varkey(key).expect("HMAC can take key of any size"); - hmac.input(data); - hmac.result().code().iter().copied().collect::>() -} - -fn signature(string_to_sign: &str, signing_key: &str) -> String { - let s = hmac(signing_key.as_bytes(), string_to_sign.as_bytes()); - base64::encode_config::>(&s, base64::STANDARD) -} - -/// An S3 bucket. -pub struct Bucket { - name: String, - base_url: String, - client: Client, -} - -impl fmt::Display for Bucket { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Bucket(name={}, base_url={})", self.name, self.base_url) - } -} - -impl Bucket { - pub fn new(name: &str, endpoint: &str, ssl: Ssl) -> Result { - let base_url = base_url(&endpoint, ssl); - Ok(Bucket { - name: name.to_owned(), - base_url, - client: Client::new(), - }) - } - - pub fn get(&self, key: &str, creds: Option<&AwsCredentials>) -> SFuture> { - let url = format!("{}{}", self.base_url, key); - debug!("GET {}", url); - let url2 = url.clone(); - let mut request = Request::new(Method::GET, url.parse().unwrap()); - if let Some(creds) = creds { - let mut canonical_headers = String::new(); - - if let Some(token) = creds.token().as_ref().map(|s| s.as_str()) { - request.headers_mut().insert( - "x-amz-security-token", - HeaderValue::from_str(token).expect("Invalid `x-amz-security-token` header"), - ); - canonical_headers - .push_str(format!("{}:{}\n", "x-amz-security-token", token).as_ref()); - } - let date = time::now_utc().rfc822().to_string(); - let auth = self.auth("GET", &date, key, "", &canonical_headers, "", creds); - request.headers_mut().insert( - "Date", - HeaderValue::from_str(&date).expect("Invalid date header"), - ); - request.headers_mut().insert( - "Authorization", - HeaderValue::from_str(&auth).expect("Invalid authentication"), - ); - } - - Box::new( - self.client - .execute(request) - .fwith_context(move || format!("failed GET: {}", url)) - .and_then(|res| { - if res.status().is_success() { - let content_length = res - .headers() - .get_hyperx::() - .map(|header::ContentLength(len)| len); - Ok((res.into_body(), content_length)) - } else { - Err(BadHttpStatusError(res.status()).into()) - } - }) - .and_then(|(body, content_length)| { - body.fold(Vec::new(), |mut body, chunk| { - body.extend_from_slice(&chunk); - Ok::<_, reqwest::Error>(body) - }) - .fcontext("failed to read HTTP body") - .and_then(move |bytes| { - if let Some(len) = content_length { - if len != bytes.len() as u64 { - bail!(format!( - "Bad HTTP body size read: {}, expected {}", - bytes.len(), - len - )); - } else { - info!("Read {} bytes from {}", bytes.len(), url2); - } - } - Ok(bytes) - }) - }), - ) - } - - pub fn put(&self, key: &str, content: Vec, creds: &AwsCredentials) -> SFuture<()> { - let url = format!("{}{}", self.base_url, key); - debug!("PUT {}", url); - let mut request = Request::new(Method::PUT, url.parse().unwrap()); - - let content_type = "application/octet-stream"; - let date = time::now_utc().rfc822().to_string(); - let mut canonical_headers = String::new(); - let token = creds.token().as_ref().map(|s| s.as_str()); - // Keep the list of header values sorted! - for (header, maybe_value) in &[("x-amz-security-token", token)] { - if let Some(ref value) = maybe_value { - request.headers_mut().insert( - *header, - HeaderValue::from_str(value) - .unwrap_or_else(|_| panic!("Invalid `{}` header", header)), - ); - canonical_headers - .push_str(format!("{}:{}\n", header.to_ascii_lowercase(), value).as_ref()); - } - } - let auth = self.auth( - "PUT", - &date, - key, - "", - &canonical_headers, - content_type, - creds, - ); - request.headers_mut().insert( - "Date", - HeaderValue::from_str(&date).expect("Invalid date header"), - ); - request - .headers_mut() - .set(header::ContentType(content_type.parse().unwrap())); - request - .headers_mut() - .set(header::ContentLength(content.len() as u64)); - request.headers_mut().set(header::CacheControl(vec![ - // Two weeks - header::CacheDirective::MaxAge(1_296_000), - ])); - request.headers_mut().insert( - "Authorization", - HeaderValue::from_str(&auth).expect("Invalid authentication"), - ); - *request.body_mut() = Some(content.into()); - - Box::new(self.client.execute(request).then(|result| match result { - Ok(res) => { - if res.status().is_success() { - trace!("PUT succeeded"); - Ok(()) - } else { - trace!("PUT failed with HTTP status: {}", res.status()); - Err(BadHttpStatusError(res.status()).into()) - } - } - Err(e) => { - trace!("PUT failed with error: {:?}", e); - Err(e.into()) - } - })) - } - - // http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html - #[allow(clippy::too_many_arguments)] - fn auth( - &self, - verb: &str, - date: &str, - path: &str, - md5: &str, - headers: &str, - content_type: &str, - creds: &AwsCredentials, - ) -> String { - let string = format!( - "{verb}\n{md5}\n{ty}\n{date}\n{headers}{resource}", - verb = verb, - md5 = md5, - ty = content_type, - date = date, - headers = headers, - resource = format!("/{}/{}", self.name, path) - ); - let signature = signature(&string, creds.aws_secret_access_key()); - format!("AWS {}:{}", creds.aws_access_key_id(), signature) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_signature() { - assert_eq!( - signature("/foo/bar\nbar", "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"), - "mwbstmHPMEJjTe2ksXi5H5f0c8U=" - ); - - assert_eq!( - signature("/bar/foo\nbaz", "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"), - "F9gZMso3+P+QTEyRKQ6qhZ1YM6o=" - ); - } -} diff --git a/src/test/mock_storage.rs b/src/test/mock_storage.rs index 9a7875509..4a7ae52e8 100644 --- a/src/test/mock_storage.rs +++ b/src/test/mock_storage.rs @@ -14,48 +14,50 @@ use crate::cache::{Cache, CacheWrite, Storage}; use crate::errors::*; -use futures::future; -use std::cell::RefCell; +use futures::channel::mpsc; +use futures_locks::Mutex; use std::time::Duration; +use std::sync::Arc; /// A mock `Storage` implementation. pub struct MockStorage { - gets: RefCell>>, + rx: Arc>>>, + tx: mpsc::UnboundedSender>, } impl MockStorage { /// Create a new `MockStorage`. - pub fn new() -> MockStorage { - MockStorage { - gets: RefCell::new(vec![]), + pub(crate) fn new() -> MockStorage { + let (tx, rx) = mpsc::unbounded(); + Self { + tx, + rx: Arc::new(Mutex::new(rx)), } } /// Queue up `res` to be returned as the next result from `Storage::get`. - pub fn next_get(&self, res: SFuture) { - self.gets.borrow_mut().push(res) + pub(crate) fn next_get(&self, res: Result) { + self.tx.unbounded_send(res).unwrap(); } } +#[async_trait::async_trait] impl Storage for MockStorage { - fn get(&self, _key: &str) -> SFuture { - let mut g = self.gets.borrow_mut(); - assert!( - g.len() > 0, - "MockStorage get called, but no get results available" - ); - g.remove(0) + async fn get(&self, _key: &str) -> Result { + let next = self.rx.lock().await.try_next().unwrap(); + + next.expect("MockStorage get called but no get results available") } - fn put(&self, _key: &str, _entry: CacheWrite) -> SFuture { - f_ok(Duration::from_secs(0)) + async fn put(&self, _key: &str, _entry: CacheWrite) -> Result { + Ok(Duration::from_secs(0)) } fn location(&self) -> String { "Mock Storage".to_string() } - fn current_size(&self) -> SFuture> { - Box::new(future::ok(None)) + async fn current_size(&self) -> Result> { + Ok(None) } - fn max_size(&self) -> SFuture> { - Box::new(future::ok(None)) + async fn max_size(&self) -> Result> { + Ok(None) } } diff --git a/src/test/tests.rs b/src/test/tests.rs index a36e232c2..7e47072b1 100644 --- a/src/test/tests.rs +++ b/src/test/tests.rs @@ -19,8 +19,7 @@ use crate::jobserver::Client; use crate::mock_command::*; use crate::server::{DistClientContainer, SccacheServer, ServerMessage}; use crate::test::utils::*; -use futures::sync::oneshot::{self, Sender}; -use futures_03::executor::ThreadPool; +use futures::channel::oneshot::{self, Sender}; use std::fs::File; use std::io::{Cursor, Write}; #[cfg(not(target_os = "macos"))] @@ -32,7 +31,7 @@ use std::sync::{mpsc, Arc, Mutex}; use std::thread; use std::time::Duration; use std::u64; -use tokio_compat::runtime::current_thread::Runtime; +use tokio::runtime::Runtime; /// Options for running the server in tests. #[derive(Default)] @@ -75,13 +74,12 @@ where let (tx, rx) = mpsc::channel(); let (shutdown_tx, shutdown_rx) = oneshot::channel(); let handle = thread::spawn(move || { - let pool = ThreadPool::sized(1); + let runtime = Runtime::new().unwrap(); let dist_client = DistClientContainer::new_disabled(); - let storage = Arc::new(DiskCache::new(&cache_dir, cache_size, &pool)); + let storage = Arc::new(DiskCache::new(&cache_dir, cache_size, runtime.handle())); - let runtime = Runtime::new().unwrap(); let client = unsafe { Client::new() }; - let srv = SccacheServer::new(0, pool, runtime, client, dist_client, storage).unwrap(); + let srv = SccacheServer::new(0, runtime, client, dist_client, storage).unwrap(); let mut srv: SccacheServer>> = srv; assert!(srv.port() > 0); if let Some(options) = options { @@ -211,7 +209,7 @@ fn test_server_unsupported_compiler() { #[test] fn test_server_compile() { - let _ = env_logger::try_init(); + let _ = env_logger::Builder::new().is_test(true).try_init(); let f = TestFixture::new(); let (port, sender, server_creator, child) = run_server_thread(&f.tempdir.path(), None); // Connect to the server. diff --git a/src/test/utils.rs b/src/test/utils.rs index 4c81d045d..c82d788bb 100644 --- a/src/test/utils.rs +++ b/src/test/utils.rs @@ -17,10 +17,10 @@ use std::collections::HashMap; use std::env; use std::ffi::OsString; use std::fs::{self, File}; +use std::future::Future; use std::io; use std::path::{Path, PathBuf}; -use futures_03::executor::ThreadPool; use std::sync::{Arc, Mutex}; use tempfile::TempDir; @@ -108,10 +108,7 @@ pub fn find_sccache_binary() -> PathBuf { .map(|d| d.join("sccache").with_extension(env::consts::EXE_EXTENSION)) .filter_map(|d| fs::metadata(&d).ok().map(|_| d)) .next() - .expect(&format!( - "Error: sccache binary not found, looked in `{:?}`. Do you need to run `cargo build`?", - dirs - )) + .unwrap_or_else(|| panic!("Error: sccache binary not found, looked in `{:?}`. Do you need to run `cargo build`?", dirs)) } pub struct TestFixture { @@ -228,6 +225,32 @@ impl TestFixture { } } +pub fn single_threaded_runtime() -> tokio::runtime::Runtime { + tokio::runtime::Builder::new() + .enable_all() + .basic_scheduler() + .core_threads(1) + .build() + .unwrap() +} + +/// An add on trait, to allow calling `.wait()` for `futures::Future` +/// as it was possible for `futures` at `0.1`. +/// +/// Intended for test only! +#[cfg(test)] +pub(crate) trait Waiter { + fn wait(self) -> R; +} + +#[cfg(test)] +impl Waiter for T where T: Future { + fn wait(self) -> O { + let mut rt = single_threaded_runtime(); + rt.block_on(self) + } +} + #[test] fn test_map_contains_ok() { let mut m = HashMap::new(); @@ -261,16 +284,3 @@ fn test_map_contains_wrong_value() { m.insert("b", 3); assert_map_contains!(m, ("a", 1), ("b", 2)); } - -pub trait ThreadPoolExt { - fn sized(size: usize) -> Self; -} - -impl ThreadPoolExt for ThreadPool { - fn sized(size: usize) -> Self { - ThreadPool::builder() - .pool_size(size) - .create() - .expect("Failed to start thread pool") - } -} diff --git a/src/util.rs b/src/util.rs index 0eafcf8f4..ce08cf957 100644 --- a/src/util.rs +++ b/src/util.rs @@ -15,10 +15,6 @@ use crate::mock_command::{CommandChild, RunCommand}; use blake3::Hasher as blake3_Hasher; use byteorder::{BigEndian, ByteOrder}; -use futures::{future, Future}; -use futures_03::executor::ThreadPool; -use futures_03::future::TryFutureExt; -use futures_03::task; use serde::Serialize; use std::ffi::{OsStr, OsString}; use std::fs::File; @@ -31,19 +27,6 @@ use std::time::Duration; use crate::errors::*; -pub trait SpawnExt: task::SpawnExt { - fn spawn_fn(&self, f: F) -> SFuture - where - F: FnOnce() -> Result + std::marker::Send + 'static, - T: std::marker::Send + 'static, - { - self.spawn_with_handle(async move { f() }) - .map(|f| Box::new(f.compat()) as _) - .unwrap_or_else(|e| f_err(e)) - } -} - -impl SpawnExt for S {} #[derive(Clone)] pub struct Digest { @@ -59,11 +42,11 @@ impl Digest { /// Calculate the BLAKE3 digest of the contents of `path`, running /// the actual hash computation on a background thread in `pool`. - pub fn file(path: T, pool: &ThreadPool) -> SFuture + pub async fn file(path: T, pool: &tokio::runtime::Handle) -> Result where T: AsRef, { - Self::reader(path.as_ref().to_owned(), pool) + Self::reader(path.as_ref().to_owned(), pool).await } /// Calculate the BLAKE3 digest of the contents read from `reader`. @@ -84,12 +67,13 @@ impl Digest { /// Calculate the BLAKE3 digest of the contents of `path`, running /// the actual hash computation on a background thread in `pool`. - pub fn reader(path: PathBuf, pool: &ThreadPool) -> SFuture { - Box::new(pool.spawn_fn(move || -> Result<_> { + pub async fn reader(path: PathBuf, pool: &tokio::runtime::Handle) -> Result { + pool.spawn_blocking(move || { let reader = File::open(&path) .with_context(|| format!("Failed to open file for hashing: {:?}", path))?; Digest::reader_sync(reader) - })) + }) + .await? } pub fn update(&mut self, bytes: &[u8]) { @@ -125,26 +109,19 @@ pub fn hex(bytes: &[u8]) -> String { /// Calculate the digest of each file in `files` on background threads in /// `pool`. -pub fn hash_all(files: &[PathBuf], pool: &ThreadPool) -> SFuture> { +pub async fn hash_all(files: &[PathBuf], pool: &tokio::runtime::Handle) -> Result> { let start = time::Instant::now(); let count = files.len(); - let pool = pool.clone(); - Box::new( - future::join_all( - files - .iter() - .map(move |f| Digest::file(f, &pool)) - .collect::>(), - ) - .map(move |hashes| { - trace!( - "Hashed {} files in {}", - count, - fmt_duration_as_secs(&start.elapsed()) - ); - hashes - }), - ) + let iter = files + .iter() + .map(move |f| Digest::file(f, &pool)); + let hashes = futures::future::try_join_all(iter).await?; + trace!( + "Hashed {} files in {}", + count, + fmt_duration_as_secs(&start.elapsed()) + ); + Ok(hashes) } /// Format `duration` as seconds with a fractional component. @@ -156,48 +133,67 @@ pub fn fmt_duration_as_secs(duration: &Duration) -> String { /// /// This was lifted from `std::process::Child::wait_with_output` and modified /// to also write to stdin. -fn wait_with_input_output(mut child: T, input: Option>) -> SFuture +async fn wait_with_input_output(mut child: T, input: Option>) -> Result where T: CommandChild + 'static, { - use tokio_io::io::{read_to_end, write_all}; + use tokio::io::{AsyncReadExt, AsyncWriteExt}; let stdin = input.and_then(|i| { - child - .take_stdin() - .map(|stdin| write_all(stdin, i).fcontext("failed to write stdin")) + child.take_stdin().map(|mut stdin| { + async move { stdin.write_all(&i).await.context("failed to write stdin") } + }) }); - let stdout = child - .take_stdout() - .map(|io| read_to_end(io, Vec::new()).fcontext("failed to read stdout")); - let stderr = child - .take_stderr() - .map(|io| read_to_end(io, Vec::new()).fcontext("failed to read stderr")); + let stdout = child.take_stdout(); + let stdout = async move { + match stdout { + Some(mut stdout) => { + let mut buf = Vec::new(); + stdout.read_to_end(&mut buf) + .await + .context("failed to read stdout")?; + Result::Ok(Some(buf)) + } + None => Ok(None) + } + }; + + let stderr = child.take_stderr(); + let stderr = async move { + match stderr { + Some(mut stderr) => { + let mut buf = Vec::new(); + stderr.read_to_end(&mut buf) + .await + .context("failed to read stderr")?; + Result::Ok(Some(buf)) + } + None => Ok(None) + } + }; // Finish writing stdin before waiting, because waiting drops stdin. - let status = Future::and_then(stdin, |io| { - drop(io); - child.wait().fcontext("failed to wait for child") - }); - - Box::new(status.join3(stdout, stderr).map(|(status, out, err)| { - let stdout = out.map(|p| p.1); - let stderr = err.map(|p| p.1); - process::Output { - status, - stdout: stdout.unwrap_or_default(), - stderr: stderr.unwrap_or_default(), + let status = async move { + if let Some(stdin) = stdin { + let _ = stdin.await; } - })) + + child.wait().await.context("failed to wait for child") + }; + + let (status, stdout, stderr) = futures::future::try_join3(status, stdout, stderr).await?; + + Ok(process::Output { + status, + stdout: stdout.unwrap_or_default(), + stderr: stderr.unwrap_or_default(), + }) } /// Run `command`, writing `input` to its stdin if it is `Some` and return the exit status and output. /// /// If the command returns a non-successful exit status, an error of `SccacheError::ProcessError` /// will be returned containing the process output. -pub fn run_input_output( - mut command: C, - input: Option>, -) -> impl Future +pub async fn run_input_output(mut command: C, input: Option>) -> Result where C: RunCommand, { @@ -210,17 +206,18 @@ where }) .stdout(Stdio::piped()) .stderr(Stdio::piped()) - .spawn(); + .spawn() + .await?; - child.and_then(|child| { - wait_with_input_output(child, input).and_then(|output| { + wait_with_input_output(child, input) + .await + .and_then(|output| { if output.status.success() { - f_ok(output) + Ok(output) } else { - f_err(ProcessError(output)) + Err(ProcessError(output).into()) } }) - }) } /// Write `data` to `writer` with bincode serialization, prefixed by a `u32` length. @@ -364,7 +361,7 @@ pub use self::http_extension::{HeadersExt, RequestExt}; #[cfg(feature = "hyperx")] mod http_extension { - use http::header::HeaderValue; + use reqwest::header::{HeaderMap, HeaderValue}; use std::fmt; pub trait HeadersExt { @@ -377,14 +374,15 @@ mod http_extension { H: hyperx::header::Header; } - impl HeadersExt for http::HeaderMap { + impl HeadersExt for HeaderMap { fn set(&mut self, header: H) where H: hyperx::header::Header + fmt::Display, { + let h = header.to_string(); self.insert( H::header_name(), - HeaderValue::from_shared(header.to_string().into()).unwrap(), + HeaderValue::from_bytes(h.as_bytes()).unwrap(), ); } @@ -392,8 +390,10 @@ mod http_extension { where H: hyperx::header::Header, { - http::HeaderMap::get(self, H::header_name()) - .and_then(|header| H::parse_header(&header.as_bytes().into()).ok()) + HeaderMap::get(self, H::header_name()).and_then(|header| { + let s = header.as_bytes().into(); + H::parse_header(&s).ok() + }) } } @@ -404,53 +404,55 @@ mod http_extension { } impl RequestExt for http::request::Builder { - fn set_header(mut self, header: H) -> Self + fn set_header(self, header: H) -> Self where H: hyperx::header::Header + fmt::Display, { + let h = header.to_string(); self.header( H::header_name(), - HeaderValue::from_shared(header.to_string().into()).unwrap(), - ); - self + HeaderValue::from_bytes(h.as_bytes()).unwrap(), + ) } } impl RequestExt for http::response::Builder { - fn set_header(mut self, header: H) -> Self + fn set_header(self, header: H) -> Self where H: hyperx::header::Header + fmt::Display, { + let h = header.to_string(); self.header( H::header_name(), - HeaderValue::from_shared(header.to_string().into()).unwrap(), - ); - self + HeaderValue::from_bytes(h.as_bytes()).unwrap(), + ) } } #[cfg(feature = "reqwest")] - impl RequestExt for ::reqwest::r#async::RequestBuilder { + impl RequestExt for ::reqwest::RequestBuilder { fn set_header(self, header: H) -> Self where H: hyperx::header::Header + fmt::Display, { + let h = header.to_string(); self.header( H::header_name(), - HeaderValue::from_shared(header.to_string().into()).unwrap(), + HeaderValue::from_bytes(h.as_bytes()).unwrap(), ) } } #[cfg(feature = "reqwest")] - impl RequestExt for ::reqwest::RequestBuilder { + impl RequestExt for ::reqwest::blocking::RequestBuilder { fn set_header(self, header: H) -> Self where H: hyperx::header::Header + fmt::Display, { + let h = header.to_string(); self.header( H::header_name(), - HeaderValue::from_shared(header.to_string().into()).unwrap(), + HeaderValue::from_bytes(h.as_bytes()).unwrap(), ) } } diff --git a/systemd/config/scheduler.conf b/systemd/config/scheduler.conf new file mode 100644 index 000000000..0e8c727b7 --- /dev/null +++ b/systemd/config/scheduler.conf @@ -0,0 +1,9 @@ +public_addr = "127.0.0.1:10600" + +[server_auth] +type = "token" +token = "server_xxxxxxxxxxxxxx" + +[client_auth] +type = "token" +token = "client_yyyyyyyyyyyyyy" \ No newline at end of file diff --git a/systemd/config/server.conf b/systemd/config/server.conf new file mode 100644 index 000000000..6e7dbc842 --- /dev/null +++ b/systemd/config/server.conf @@ -0,0 +1,19 @@ +cache_dir="/media/supersonic1t/sccache-cache/cache" +# The maximum size of the toolchain cache, in bytes. +# If unspecified the default is 10GB. +# toolchain_cache_size = 10737418240 +# A public IP address and port that clients will use to connect to this builder. +public_addr = "127.0.0.1:10501" +# The URL used to connect to the scheduler (should use https, given an ideal +# setup of a HTTPS server in front of the scheduler) +scheduler_url = "http://127.0.0.1:10600" + +[builder] +type = "overlay" +build_dir = "/media/supersonic1t/sccache-cache/build" +# The path to the bubblewrap version 0.3.0+ `bwrap` binary. +bwrap_path = "/usr/bin/bwrap" + +[scheduler_auth] +type = "token" +token = "server_xxxxxxxxxxxxxx" \ No newline at end of file diff --git a/systemd/sccache-scheduler.service b/systemd/sccache-scheduler.service new file mode 100644 index 000000000..963bd3242 --- /dev/null +++ b/systemd/sccache-scheduler.service @@ -0,0 +1,26 @@ +[Unit] +Description=sccache scheduler + +After=suspend.target +After=hibernate.target +After=hybrid-sleep.target +After=network.target +Requires=network.target +RequiresMountsFor=/mnt/cache-dir + +[Service] +Type=simple +Restart=always +RestartSec=20s +LimitNOFILE=5000 +TasksMax=1000 +User=root +Group=root +Environment=SCCACHE_NO_DAEMON=1 +Environment=RUST_LOG=sccache=trace,sccache-dist=trace +ExecStartPre=-/usr/bin/mkdir /mnt/cache-dir +ExecStart=/usr/local/bin/sccache-dist scheduler --config /etc/sccache/scheduler.conf +SELinuxContext=system_u:object_r:unreserved_port_t:s0 + +[Install] +WantedBy=multi-user.target diff --git a/systemd/sccache-server.service b/systemd/sccache-server.service new file mode 100644 index 000000000..df72fb4ed --- /dev/null +++ b/systemd/sccache-server.service @@ -0,0 +1,27 @@ +[Unit] +Description=sccache server + +After=suspend.target +After=hibernate.target +After=hybrid-sleep.target +After=network.target +Requires=network.target +RequiresMountsFor=/mnt/cache-dir +Wants=sccache-scheduler.service + +[Service] +Type=simple +Restart=always +RestartSec=20s +LimitNOFILE=50000 +TasksMax=1000 +User=root +Group=root +Environment=SCCACHE_NO_DAEMON=1 +Environment=RUST_LOG=sccache=trace,sccache-dist=trace +ExecPreStart=-/mnt/cache-dir +ExecStart=/usr/local/bin/sccache-dist server --config /etc/sccache/server.conf +SELinuxContext=system_u:object_r:unreserved_port_t:s0 + +[Install] +WantedBy=multi-user.target diff --git a/tests/harness/mod.rs b/tests/harness/mod.rs index 0943f74d6..fc3576ba5 100644 --- a/tests/harness/mod.rs +++ b/tests/harness/mod.rs @@ -22,6 +22,7 @@ use nix::{ }, unistd::{ForkResult, Pid}, }; +use bytes::buf::ext::BufExt; use predicates::prelude::*; use serde::Serialize; use uuid::Uuid; @@ -111,7 +112,7 @@ pub fn write_source(path: &Path, filename: &str, contents: &str) { // Override any environment variables that could adversely affect test execution. pub fn sccache_command() -> Command { - let mut cmd = Command::new(assert_cmd::cargo::cargo_bin("sccache")); + let mut cmd = Command::new(assert_cmd::cargo::cargo_bin(env!("CARGO_PKG_NAME"))); cmd.env("SCCACHE_CONF", "nonexistent_conf_path") .env("SCCACHE_CACHED_CONF", "nonexistent_cached_conf_path"); cmd @@ -128,8 +129,10 @@ pub fn sccache_client_cfg(tmpdir: &Path) -> sccache::config::FileConfig { fs::create_dir(tmpdir.join(cache_relpath)).unwrap(); fs::create_dir(tmpdir.join(dist_cache_relpath)).unwrap(); - let mut disk_cache: sccache::config::DiskCacheConfig = Default::default(); - disk_cache.dir = tmpdir.join(cache_relpath); + let disk_cache = sccache::config::DiskCacheConfig { + dir: tmpdir.join(cache_relpath), + ..Default::default() + }; sccache::config::FileConfig { cache: sccache::config::CacheConfigs { azure: None, @@ -293,8 +296,9 @@ impl DistSystem { wait_for_http(scheduler_url, Duration::from_millis(100), MAX_STARTUP_WAIT); wait_for( || { - let status = self.scheduler_status(); - if matches!(self.scheduler_status(), SchedulerStatusResult { num_servers: 0, num_cpus: _, in_progress: 0 }) + let mut runtime = tokio::runtime::Runtime::new().unwrap(); + let status = runtime.block_on (async { self.scheduler_status().await }); + if matches!(status, SchedulerStatusResult { num_servers: 0, num_cpus: _, in_progress: 0 }) { Ok(()) } else { @@ -427,8 +431,9 @@ impl DistSystem { wait_for_http(url, Duration::from_millis(100), MAX_STARTUP_WAIT); wait_for( || { - let status = self.scheduler_status(); - if matches!(self.scheduler_status(), SchedulerStatusResult { num_servers: 1, num_cpus: _, in_progress: 0 }) + let mut rt = tokio::runtime::Runtime::new().unwrap(); + let status = rt.block_on(async { self.scheduler_status().await }); + if matches!(status, SchedulerStatusResult { num_servers: 1, num_cpus: _, in_progress: 0 }) { Ok(()) } else { @@ -446,13 +451,13 @@ impl DistSystem { HTTPUrl::from_url(reqwest::Url::parse(&url).unwrap()) } - fn scheduler_status(&self) -> SchedulerStatusResult { + async fn scheduler_status(&self) -> SchedulerStatusResult { let res = reqwest::get(dist::http::urls::scheduler_status( - &self.scheduler_url().to_url(), - )) - .unwrap(); + &{self.scheduler_url().to_url()}, + )).await.unwrap(); assert!(res.status().is_success()); - bincode::deserialize_from(res).unwrap() + let bytes = res.bytes().await.unwrap(); + bincode::deserialize_from(bytes.reader()).unwrap() } fn container_ip(&self, name: &str) -> IpAddr { @@ -629,11 +634,11 @@ fn check_output(output: &Output) { #[cfg(feature = "dist-server")] fn wait_for_http(url: HTTPUrl, interval: Duration, max_wait: Duration) { - // TODO: after upgrading to reqwest >= 0.9, use 'danger_accept_invalid_certs' and stick with that rather than tcp + let url = url.to_url(); wait_for( || { - //match reqwest::get(url.to_url()) { - match net::TcpStream::connect(url.to_url()) { + let client = reqwest::blocking::Client::builder().danger_accept_invalid_certs(true).build().unwrap(); + match client.get(url.clone()).send() { Ok(_) => Ok(()), Err(e) => Err(e.to_string()), } diff --git a/tests/oauth.rs b/tests/oauth.rs index bbcb4b8bd..30f721a2c 100755 --- a/tests/oauth.rs +++ b/tests/oauth.rs @@ -63,7 +63,7 @@ fn config_with_dist_auth( } fn sccache_command() -> Command { - Command::new(assert_cmd::cargo::cargo_bin("sccache")) + Command::new(assert_cmd::cargo::cargo_bin(env!("CARGO_PKG_NAME"))) } fn retry Option, T>(interval: Duration, until: Duration, mut f: F) -> Option { diff --git a/tests/sccache_cargo.rs b/tests/sccache_cargo.rs index 40cae42dc..d77c34605 100644 --- a/tests/sccache_cargo.rs +++ b/tests/sccache_cargo.rs @@ -4,6 +4,11 @@ //! http://creativecommons.org/publicdomain/zero/1.0/ #![deny(rust_2018_idioms)] +#![allow(dead_code, unused_imports)] + +mod harness; + +use crate::harness::get_stats; #[cfg(all(not(target_os = "windows"), not(target_os = "macos")))] #[macro_use] @@ -14,23 +19,40 @@ extern crate log; #[test] #[cfg(all(not(target_os = "windows"), not(target_os = "macos")))] fn test_rust_cargo() { + use chrono::Local; + use std::io::Write; + let _ = env_logger::Builder::new() + .format(|f, record| { + writeln!( + f, + "{} [{}] - {}", + Local::now().format("%Y-%m-%dT%H:%M:%S%.3f"), + record.level(), + record.args() + ) + }) + .is_test(true) + .filter_level(log::LevelFilter::Trace) + .try_init(); + + trace!("cargo check"); test_rust_cargo_cmd("check"); + + trace!("cargo build"); test_rust_cargo_cmd("build"); } #[cfg(all(not(target_os = "windows"), not(target_os = "macos")))] fn test_rust_cargo_cmd(cmd: &str) { use assert_cmd::prelude::*; - use chrono::Local; use predicates::prelude::*; use std::env; use std::fs; - use std::io::Write; use std::path::Path; use std::process::{Command, Stdio}; fn sccache_command() -> Command { - Command::new(assert_cmd::cargo::cargo_bin("sccache")) + Command::new(assert_cmd::cargo::cargo_bin(env!("CARGO_PKG_NAME"))) } fn stop() { @@ -44,23 +66,9 @@ fn test_rust_cargo_cmd(cmd: &str) { ); } - drop( - env_logger::Builder::new() - .format(|f, record| { - write!( - f, - "{} [{}] - {}", - Local::now().format("%Y-%m-%dT%H:%M:%S%.3f"), - record.level(), - record.args() - ) - }) - .parse(&env::var("RUST_LOG").unwrap_or_default()) - .try_init(), - ); let cargo = env!("CARGO"); debug!("cargo: {}", cargo); - let sccache = assert_cmd::cargo::cargo_bin("sccache"); + let sccache = assert_cmd::cargo::cargo_bin(env!("CARGO_PKG_NAME")); debug!("sccache: {:?}", sccache); let crate_dir = Path::new(file!()).parent().unwrap().join("test-crate"); // Ensure there's no existing sccache server running. @@ -88,14 +96,14 @@ fn test_rust_cargo_cmd(cmd: &str) { ]; Command::new(&cargo) .args(&["clean"]) - .envs(envs.iter().map(|v| *v)) + .envs(envs.iter().copied()) .current_dir(&crate_dir) .assert() .success(); // Now build the crate with cargo. Command::new(&cargo) .args(&[cmd, "--color=never"]) - .envs(envs.iter().map(|v| *v)) + .envs(envs.iter().copied()) .current_dir(&crate_dir) .assert() .stderr(predicates::str::contains("\x1b[").from_utf8().not()) @@ -103,13 +111,13 @@ fn test_rust_cargo_cmd(cmd: &str) { // Clean it so we can build it again. Command::new(&cargo) .args(&["clean"]) - .envs(envs.iter().map(|v| *v)) + .envs(envs.iter().copied()) .current_dir(&crate_dir) .assert() .success(); Command::new(&cargo) .args(&[cmd, "--color=always"]) - .envs(envs.iter().map(|v| *v)) + .envs(envs.iter().copied()) .current_dir(&crate_dir) .assert() .stderr(predicates::str::contains("\x1b[").from_utf8()) @@ -119,10 +127,15 @@ fn test_rust_cargo_cmd(cmd: &str) { // so there are two separate compilations, but cargo will build the test crate with // incremental compilation enabled, so sccache will not cache it. trace!("sccache --show-stats"); - sccache_command() - .args(&["--show-stats", "--stats-format=json"]) - .assert() - .stdout(predicates::str::contains(r#""cache_hits":{"counts":{"Rust":1}}"#).from_utf8()) - .success(); + get_stats(|info: sccache::server::ServerInfo| { + dbg!(&info.stats); + // FIXME differs between CI and local execution + let expected = match std::env::var_os("CI") { + Some(var) if !var.is_empty() => Some(&2), + _ => Some(&1) + }; + assert_eq!(expected, info.stats.cache_hits.get("Rust")); + }); + stop(); } diff --git a/tests/system.rs b/tests/system.rs index 70ed5246d..f6112a8f3 100644 --- a/tests/system.rs +++ b/tests/system.rs @@ -111,12 +111,7 @@ fn test_basic_compile(compiler: Compiler, tempdir: &Path) { .envs(env_vars.clone()) .assert() .success(); - assert_eq!( - true, - fs::metadata(&out_file) - .and_then(|m| Ok(m.len() > 0)) - .unwrap() - ); + assert_eq!(true, fs::metadata(&out_file).map(|m| m.len() > 0).unwrap()); trace!("request stats"); get_stats(|info| { assert_eq!(1, info.stats.compile_requests); @@ -133,12 +128,7 @@ fn test_basic_compile(compiler: Compiler, tempdir: &Path) { .envs(env_vars) .assert() .success(); - assert_eq!( - true, - fs::metadata(&out_file) - .and_then(|m| Ok(m.len() > 0)) - .unwrap() - ); + assert_eq!(true, fs::metadata(&out_file).map(|m| m.len() > 0).unwrap()); trace!("request stats"); get_stats(|info| { assert_eq!(2, info.stats.compile_requests); @@ -160,7 +150,7 @@ fn test_noncacheable_stats(compiler: Compiler, tempdir: &Path) { copy_to_tempdir(&[INPUT], tempdir); trace!("compile"); - Command::new(assert_cmd::cargo::cargo_bin("sccache")) + Command::new(assert_cmd::cargo::cargo_bin(env!("CARGO_PKG_NAME"))) .arg(&exe) .arg("-E") .arg(INPUT) @@ -234,6 +224,7 @@ fn test_gcc_mp_werror(compiler: Compiler, tempdir: &Path) { ); } +/// For more details visit the [gnu compiler collection manual](https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html) fn test_gcc_fprofile_generate_source_changes(compiler: Compiler, tempdir: &Path) { let Compiler { name, @@ -286,7 +277,8 @@ int main(int argc, char** argv) { }); // Now write out a slightly different source file that will preprocess to the same thing, // modulo line numbers. This should not be a cache hit because line numbers are important - // with -fprofile-generate. + // with -fprofile-generate. But that behaviour changed at some point + // before gcc 10.2.1 and now it produces a cache hit. write_source( &tempdir, SRC, @@ -305,7 +297,7 @@ int main(int argc, char** argv) { sccache_command() .args(&args) .current_dir(tempdir) - .envs(env_vars) + .envs(env_vars.clone()) .assert() .success(); get_stats(|info| { @@ -314,6 +306,24 @@ int main(int argc, char** argv) { assert_eq!(&1, info.stats.cache_hits.get("C/C++").unwrap()); assert_eq!(&2, info.stats.cache_misses.get("C/C++").unwrap()); }); + + // Now doing the same again with `UNDEFINED` defined + // should produce a cache hit too, after preproc + // it's still the same source file + args.extend(vec_from!(OsString, "-DUNDEFINED")); + trace!("compile source.c (4)"); + sccache_command() + .args(&args) + .current_dir(tempdir) + .envs(env_vars) + .assert() + .success(); + get_stats(|info| { + assert_eq!(1, info.stats.cache_hits.all()); + assert_eq!(3, info.stats.cache_misses.all()); + assert_eq!(&1, info.stats.cache_hits.get("C/C++").unwrap()); + assert_eq!(&3, info.stats.cache_misses.get("C/C++").unwrap()); + }); } fn test_gcc_clang_no_warnings_from_macro_expansion(compiler: Compiler, tempdir: &Path) { @@ -371,15 +381,19 @@ fn test_compile_with_define(compiler: Compiler, tempdir: &Path) { fn run_sccache_command_tests(compiler: Compiler, tempdir: &Path) { test_basic_compile(compiler.clone(), tempdir); test_compile_with_define(compiler.clone(), tempdir); - if compiler.name == "cl.exe" { - test_msvc_deps(compiler.clone(), tempdir); - } - if compiler.name == "gcc" { - test_gcc_mp_werror(compiler.clone(), tempdir); - test_gcc_fprofile_generate_source_changes(compiler.clone(), tempdir); - } - if compiler.name == "clang" || compiler.name == "gcc" { - test_gcc_clang_no_warnings_from_macro_expansion(compiler, tempdir); + match compiler.name { + "cl.exe" => { + test_msvc_deps(compiler.clone(), tempdir); + } + "gcc" => { + test_gcc_mp_werror(compiler.clone(), tempdir); + test_gcc_fprofile_generate_source_changes(compiler.clone(), tempdir); + test_gcc_clang_no_warnings_from_macro_expansion(compiler, tempdir); + } + "clang" => { + test_gcc_clang_no_warnings_from_macro_expansion(compiler, tempdir); + } + _ => {} } } @@ -424,7 +438,11 @@ fn find_compilers() -> Vec { #[test] #[cfg(any(unix, target_env = "msvc"))] fn test_sccache_command() { - let _ = env_logger::try_init(); + use log; + let _ = env_logger::Builder::new() + .filter_level(log::LevelFilter::Trace) + .is_test(true) + .try_init(); let tempdir = tempfile::Builder::new() .prefix("sccache_system_test") .tempdir() @@ -445,6 +463,7 @@ fn test_sccache_command() { &tempdir.path().join("sccache-cfg.json"), &sccache_cached_cfg_path, ); + for compiler in compilers { run_sccache_command_tests(compiler, tempdir.path()); zero_stats();