name: CICD

# spell-checker:ignore (acronyms) CICD MSVC musl
# spell-checker:ignore (env/flags) Awarnings Ccodegen Coverflow Cpanic Dwarnings RUSTDOCFLAGS RUSTFLAGS Zpanic
# spell-checker:ignore (jargon) SHAs deps dequote softprops subshell toolchain
# spell-checker:ignore (names) CodeCOV MacOS MinGW Peltoche rivy
# spell-checker:ignore (shell/tools) choco clippy dmake dpkg esac fakeroot gmake grcov halium lcov libssl mkdir popd printf pushd rsync rustc rustfmt rustup shopt xargs
# spell-checker:ignore (misc) aarch alnum armhf bindir busytest coreutils gnueabihf issuecomment maint nullglob onexitbegin onexitend pell runtest tempfile testsuite uutils DESTDIR sizemulti Swatinem

# ToDO: [2021-06; rivy] change from `cargo-tree` to `cargo tree` once MSRV is >= 1.45

env:
  PROJECT_NAME: coreutils
  PROJECT_DESC: "Core universal (cross-platform) utilities"
  PROJECT_AUTH: "uutils"
  RUST_MIN_SRV: "1.54.0" ## MSRV v1.54.0
  # * style job configuration
  STYLE_FAIL_ON_FAULT: true ## (bool) fail the build if a style job contains a fault (error or warning); may be overridden on a per-job basis

on: [push, pull_request]

jobs:
  style_deps:
    ## ToDO: [2021-11-10; rivy] 'Style/deps' needs more informative output and better integration of results into the GHA dashboard
    name: Style/deps
    runs-on: ${{ matrix.job.os }}
    # env:
    #   STYLE_FAIL_ON_FAULT: false # overrides workflow default
    strategy:
      fail-fast: false
      matrix:
        job:
          # note: `cargo-udeps` panics when processing stdbuf/libstdbuf ("uu_stdbuf_libstdbuf"); either b/c of the 'cpp' crate or 'libstdbuf' itself
          #   ... b/c of the panic, a more limited feature set is tested (though only excluding `stdbuf`)
          - { os: ubuntu-latest  , features: "feat_Tier1,feat_require_unix,feat_require_unix_utmpx" }
          - { os: macos-latest   , features: "feat_Tier1,feat_require_unix,feat_require_unix_utmpx" }
          - { os: windows-latest , features: feat_os_windows }
    steps:
    - uses: actions/checkout@v2
    - uses: Swatinem/rust-cache@v1
    - name: Initialize workflow variables
      id: vars
      shell: bash
      run: |
        ## VARs setup
        outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
        # failure mode
        unset FAIL_ON_FAULT ; case '${{ env.STYLE_FAIL_ON_FAULT }}' in
          ''|0|f|false|n|no|off) FAULT_TYPE=warning ;;
          *) FAIL_ON_FAULT=true ; FAULT_TYPE=error ;;
        esac;
        outputs FAIL_ON_FAULT FAULT_TYPE
        # target-specific options
        # * CARGO_FEATURES_OPTION
        CARGO_FEATURES_OPTION='' ;
        if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi
        outputs CARGO_FEATURES_OPTION
    ## note: requires 'nightly' toolchain b/c `cargo-udeps` uses the `rustc` '-Z save-analysis' option
    ## * ... ref: <https://github.com/est31/cargo-udeps/issues/73>
    - name: Install `rust` toolchain
      uses: actions-rs/toolchain@v1
      with:
        toolchain: nightly
        default: true
        profile: minimal
    - name: Install `cargo-udeps`
      uses: actions-rs/install@v0.1
      with:
        crate: cargo-udeps
        version: latest
        use-tool-cache: false
      env:
        RUSTUP_TOOLCHAIN: stable
    - name: Detect unused dependencies
      shell: bash
      run: |
        ## Detect unused dependencies
        unset fault
        fault_type="${{ steps.vars.outputs.FAULT_TYPE }}"
        fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]')
        #
        cargo +nightly udeps ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --all-targets &> udeps.log || cat udeps.log
        grep --ignore-case "all deps seem to have been used" udeps.log || { printf "%s\n" "::${fault_type} ::${fault_prefix}: \`cargo udeps\`: style violation (unused dependency found)" ; fault=true ; }
        if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi

  style_format:
    name: Style/format
    runs-on: ${{ matrix.job.os }}
    # env:
    #   STYLE_FAIL_ON_FAULT: false # overrides workflow default
    strategy:
      fail-fast: false
      matrix:
        job:
          - { os: ubuntu-latest , features: feat_os_unix }
    steps:
    - uses: actions/checkout@v2
    - uses: Swatinem/rust-cache@v1
    - name: Initialize workflow variables
      id: vars
      shell: bash
      run: |
        ## VARs setup
        outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
        # failure mode
        unset FAIL_ON_FAULT ; case '${{ env.STYLE_FAIL_ON_FAULT }}' in
          ''|0|f|false|n|no|off) FAULT_TYPE=warning ;;
          *) FAIL_ON_FAULT=true ; FAULT_TYPE=error ;;
        esac;
        outputs FAIL_ON_FAULT FAULT_TYPE
        # target-specific options
        # * CARGO_FEATURES_OPTION
        CARGO_FEATURES_OPTION='' ;
        if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi
        outputs CARGO_FEATURES_OPTION
    - name: Install `rust` toolchain
      uses: actions-rs/toolchain@v1
      with:
        toolchain: stable
        default: true
        profile: minimal # minimal component installation (ie, no documentation)
        components: rustfmt
    - name: "`cargo fmt` testing"
      shell: bash
      run: |
        ## `cargo fmt` testing
        unset fault
        fault_type="${{ steps.vars.outputs.FAULT_TYPE }}"
        fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]')
        # * convert any errors/warnings to GHA UI annotations; ref: <https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message>
        S=$(cargo fmt -- --check) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s\n" "$S" | sed -E -n -e "s/^Diff[[:space:]]+in[[:space:]]+${PWD//\//\\/}\/(.*)[[:space:]]+at[[:space:]]+[^0-9]+([0-9]+).*$/::${fault_type} file=\1,line=\2::${fault_prefix}: \`cargo fmt\`: style violation (file:'\1', line:\2; use \`cargo fmt -- \"\1\"\`)/p" ; fault=true ; }
        if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi
    - name: "`cargo fmt` testing of integration tests"
      if: success() || failure() # run regardless of prior step success/failure
      shell: bash
      run: |
        ## `cargo fmt` testing of integration tests
        unset fault
        fault_type="${{ steps.vars.outputs.FAULT_TYPE }}"
        fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]')
        # 'tests' is the standard/usual integration test directory
        if [ -d tests ]; then
          # * convert any errors/warnings to GHA UI annotations; ref: <https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message>
          S=$(find tests -name "*.rs" -print0 | xargs -0 cargo fmt -- --check) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s\n" "$S" | sed -E -n "s/^Diff[[:space:]]+in[[:space:]]+${PWD//\//\\/}\/(.*)[[:space:]]+at[[:space:]]+[^0-9]+([0-9]+).*$/::${fault_type} file=\1,line=\2::${fault_prefix}: \`cargo fmt\`: style violation (file:'\1', line:\2; use \`cargo fmt \"\1\"\`)/p" ; fault=true ; }
        fi
        if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi

  style_lint:
    name: Style/lint
    runs-on: ${{ matrix.job.os }}
    # env:
    #   STYLE_FAIL_ON_FAULT: false # overrides workflow default
    strategy:
      fail-fast: false
      matrix:
        job:
          - { os: ubuntu-latest  , features: feat_os_unix }
          - { os: macos-latest   , features: feat_os_macos }
          - { os: windows-latest , features: feat_os_windows }
    steps:
    - uses: actions/checkout@v2
    - uses: Swatinem/rust-cache@v1
    - name: Initialize workflow variables
      id: vars
      shell: bash
      run: |
        ## VARs setup
        outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
        # failure mode
        unset FAIL_ON_FAULT ; case '${{ env.STYLE_FAIL_ON_FAULT }}' in
          ''|0|f|false|n|no|off) FAULT_TYPE=warning ;;
          *) FAIL_ON_FAULT=true ; FAULT_TYPE=error ;;
        esac;
        outputs FAIL_ON_FAULT FAULT_TYPE
        # target-specific options
        # * CARGO_FEATURES_OPTION
        CARGO_FEATURES_OPTION='--all-features' ;
        if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi
        outputs CARGO_FEATURES_OPTION
        # * determine sub-crate utility list
        UTILITY_LIST="$(./util/show-utils.sh ${CARGO_FEATURES_OPTION})"
        echo UTILITY_LIST=${UTILITY_LIST}
        CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo "-puu_${u}"; done;)"
        outputs CARGO_UTILITY_LIST_OPTIONS
    - name: Install/setup prerequisites
      shell: bash
      run: |
        case '${{ matrix.job.os }}' in
          macos-latest) brew install coreutils ;; # needed for show-utils.sh
        esac
    - name: Install `rust` toolchain
      uses: actions-rs/toolchain@v1
      with:
        toolchain: stable
        default: true
        profile: minimal # minimal component installation (ie, no documentation)
        components: clippy
    - name: "`cargo clippy` lint testing"
      shell: bash
      run: |
        ## `cargo clippy` lint testing
        unset fault
        fault_type="${{ steps.vars.outputs.FAULT_TYPE }}"
        fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]')
        # * convert any warnings to GHA UI annotations; ref: <https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message>
        S=$(cargo clippy --all-targets ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} ${{ steps.vars.outputs.CARGO_UTILITY_LIST_OPTIONS }} -- -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+(.*):([0-9]+):([0-9]+).*$/::${fault_type} file=\2,line=\3,col=\4::${fault_prefix}: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; fault=true ; }
        if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi

  style_spellcheck:
    name: Style/spelling
    runs-on: ${{ matrix.job.os }}
    # env:
    #   STYLE_FAIL_ON_FAULT: false # overrides workflow default
    strategy:
      matrix:
        job:
          - { os: ubuntu-latest , features: feat_os_unix }
    steps:
    - uses: actions/checkout@v2
    - uses: Swatinem/rust-cache@v1
    - name: Initialize workflow variables
      id: vars
      shell: bash
      run: |
        ## VARs setup
        outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
        # failure mode
        unset FAIL_ON_FAULT ; case '${{ env.STYLE_FAIL_ON_FAULT }}' in
          ''|0|f|false|n|no|off) FAULT_TYPE=warning ;;
          *) FAIL_ON_FAULT=true ; FAULT_TYPE=error ;;
        esac;
        outputs FAIL_ON_FAULT FAULT_TYPE
    - name: Install/setup prerequisites
      shell: bash
      run: |
        ## Install/setup prerequisites
        # * pin installed cspell to v4.2.8 (cspell v5+ is broken for NodeJS < v12)
        ## maint: [2021-11-10; rivy] `cspell` version may be advanced to v5 when used with NodeJS >= v12
        sudo apt-get -y update ; sudo apt-get -y install npm ; sudo npm install cspell@4.2.8 -g ;
    - name: Run `cspell`
      shell: bash
      run: |
        ## Run `cspell`
        unset fault
        fault_type="${{ steps.vars.outputs.FAULT_TYPE }}"
        fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]')
        # * find cspell configuration ; note: avoid quotes around ${cfg_file} b/c `cspell` (v4) doesn't correctly dequote the config argument (or perhaps a subshell expansion issue?)
        cfg_files=($(shopt -s nullglob ; echo {.vscode,.}/{,.}c[sS]pell{.json,.config{.js,.cjs,.json,.yaml,.yml},.yaml,.yml} ;))
        cfg_file=${cfg_files[0]}
        unset CSPELL_CFG_OPTION ; if [ -n "$cfg_file" ]; then CSPELL_CFG_OPTION="--config $cfg_file" ; fi
        # * `cspell`
        ## maint: [2021-11-10; rivy] the `--no-progress` option for `cspell` is a `cspell` v5+ option
        # S=$(cspell ${CSPELL_CFG_OPTION} --no-summary --no-progress "**/*") && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n "s/${PWD//\//\\/}\/(.*):(.*):(.*) - (.*)/::${fault_type} file=\1,line=\2,col=\3::${fault_type^^}: \4 (file:'\1', line:\2)/p" ; fault=true ; true ; }
        S=$(cspell ${CSPELL_CFG_OPTION} --no-summary "**/*") && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n "s/${PWD//\//\\/}\/(.*):(.*):(.*) - (.*)/::${fault_type} file=\1,line=\2,col=\3::${fault_type^^}: \4 (file:'\1', line:\2)/p" ; fault=true ; true ; }
        if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi

  doc_warnings:
    name: Documentation/warnings
    runs-on: ${{ matrix.job.os }}
    strategy:
      fail-fast: false
      matrix:
        job:
          - { os: ubuntu-latest  , features: feat_os_unix }
# for now, don't build it on mac & windows because the doc is only published from linux
# + it needs a bunch of duplication for build
# and I don't want to add a doc step in the regular build to avoid long builds
#          - { os: macos-latest   , features: feat_os_macos }
#          - { os: windows-latest , features: feat_os_windows }
    steps:
    - uses: actions/checkout@v2
    - uses: Swatinem/rust-cache@v1
    - name: Initialize workflow variables
      id: vars
      shell: bash
      run: |
        ## VARs setup
        outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
        # failure mode
        unset FAIL_ON_FAULT ; case '${{ env.STYLE_FAIL_ON_FAULT }}' in
          ''|0|f|false|n|no|off) FAULT_TYPE=warning ;;
          *) FAIL_ON_FAULT=true ; FAULT_TYPE=error ;;
        esac;
        outputs FAIL_ON_FAULT FAULT_TYPE
        # target-specific options
        # * CARGO_FEATURES_OPTION
        CARGO_FEATURES_OPTION='--all-features' ;
        if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi
        outputs CARGO_FEATURES_OPTION
        # * determine sub-crate utility list
        UTILITY_LIST="$(./util/show-utils.sh ${CARGO_FEATURES_OPTION})"
        echo UTILITY_LIST=${UTILITY_LIST}
        CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo "-puu_${u}"; done;)"
        outputs CARGO_UTILITY_LIST_OPTIONS
    - name: Install `rust` toolchain
      uses: actions-rs/toolchain@v1
      with:
        toolchain: stable
        default: true
        profile: minimal # minimal component installation (ie, no documentation)
        components: clippy
    - name: "`cargo doc` with warnings"
      shell: bash
      run: |
        RUSTDOCFLAGS="-Dwarnings" cargo doc  ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-deps --workspace --document-private-items


  min_version:
    name: MinRustV # Minimum supported rust version (aka, MinSRV or MSRV)
    runs-on: ${{ matrix.job.os }}
    strategy:
      matrix:
        job:
          - { os: ubuntu-latest , features: feat_os_unix }
    steps:
    - uses: actions/checkout@v2
    - uses: Swatinem/rust-cache@v1
    - name: Initialize workflow variables
      id: vars
      shell: bash
      run: |
        ## VARs setup
        outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
        # target-specific options
        # * CARGO_FEATURES_OPTION
        unset CARGO_FEATURES_OPTION
        if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi
        outputs CARGO_FEATURES_OPTION
    - name: Install `rust` toolchain (v${{ env.RUST_MIN_SRV }})
      uses: actions-rs/toolchain@v1
      with:
        toolchain: ${{ env.RUST_MIN_SRV }}
        default: true
        profile: minimal # minimal component installation (ie, no documentation)
    - name: Install `cargo-tree` # for dependency information
      uses: actions-rs/install@v0.1
      with:
        crate: cargo-tree
        version: latest
        use-tool-cache: true
      env:
        RUSTUP_TOOLCHAIN: stable
    - name: Confirm MinSRV compatible 'Cargo.lock'
      shell: bash
      run: |
        ## Confirm MinSRV compatible 'Cargo.lock'
        # * 'Cargo.lock' is required to be in a format that `cargo` of MinSRV can interpret (eg, v1-format for MinSRV < v1.38)
        cargo fetch --locked --quiet || { echo "::error file=Cargo.lock::Incompatible (or out-of-date) 'Cargo.lock' file; update using \`cargo +${{ env.RUST_MIN_SRV }} update\`" ; exit 1 ; }
    - name: Confirm MinSRV equivalence for '.clippy.toml'
      shell: bash
      run: |
        ## Confirm MinSRV equivalence for '.clippy.toml'
        # * ensure '.clippy.toml' MSRV configuration setting is equal to ${{ env.RUST_MIN_SRV }}
        CLIPPY_MSRV=$(grep -P "(?i)^\s*msrv\s*=\s*" .clippy.toml | grep -oP "\d+([.]\d+)+")
        if [ "${CLIPPY_MSRV}" != "${{ env.RUST_MIN_SRV }}" ]; then { echo "::error file=.clippy.toml::Incorrect MSRV configuration for clippy (found '${CLIPPY_MSRV}'; should be '${{ env.RUST_MIN_SRV }}'); update '.clippy.toml' with 'msrv = \"${{ env.RUST_MIN_SRV }}\"'" ; exit 1 ; } ; fi
    - name: Info
      shell: bash
      run: |
        ## Info
        # environment
        echo "## environment"
        echo "CI='${CI}'"
        # tooling info display
        echo "## tooling"
        which gcc >/dev/null 2>&1 && (gcc --version | head -1) || true
        rustup -V 2>/dev/null
        rustup show active-toolchain
        cargo -V
        rustc -V
        cargo-tree tree -V
        # dependencies
        echo "## dependency list"
        ## * using the 'stable' toolchain is necessary to avoid "unexpected '--filter-platform'" errors
        RUSTUP_TOOLCHAIN=stable cargo fetch --locked --quiet
        RUSTUP_TOOLCHAIN=stable cargo-tree tree --all --locked --no-dev-dependencies --no-indent ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} | grep -vE "$PWD" | sort --unique
    - name: Test
      uses: actions-rs/cargo@v1
      with:
        command: test
        args: ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} -p uucore -p coreutils
      env:
        RUSTFLAGS: "-Awarnings"

  deps:
    name: Dependencies
    runs-on: ${{ matrix.job.os }}
    strategy:
      fail-fast: false
      matrix:
        job:
          - { os: ubuntu-latest , features: feat_os_unix }
    steps:
    - uses: actions/checkout@v2
    - uses: Swatinem/rust-cache@v1
    - name: Install `rust` toolchain
      uses: actions-rs/toolchain@v1
      with:
        toolchain: stable
        default: true
        profile: minimal # minimal component installation (ie, no documentation)
    - name: "`cargo update` testing"
      shell: bash
      run: |
        ## `cargo update` testing
        # * convert any errors/warnings to GHA UI annotations; ref: <https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message>
        cargo fetch --locked --quiet || { echo "::error file=Cargo.lock::'Cargo.lock' file requires update (use \`cargo +${{ env.RUST_MIN_SRV }} update\`)" ; exit 1 ; }

  build_makefile:
    name: Build/Makefile
    needs: [ min_version, deps ]
    runs-on: ${{ matrix.job.os }}
    strategy:
      fail-fast: false
      matrix:
        job:
          - { os: ubuntu-latest , features: feat_os_unix }
    steps:
    - uses: actions/checkout@v2
    - uses: Swatinem/rust-cache@v1
    - name: Install `rust` toolchain
      uses: actions-rs/toolchain@v1
      with:
        toolchain: stable
        default: true
        profile: minimal # minimal component installation (ie, no documentation)
    - name: "`make build`"
      shell: bash
      run: |
        make build
    - name: "`make test`"
      shell: bash
      run: |
        make test


  compute_size:
    name: Binary sizes
    needs: [ min_version, deps ]
    runs-on: ${{ matrix.job.os }}
    strategy:
      fail-fast: false
      matrix:
        job:
          - { os: ubuntu-latest , features: feat_os_unix }
    steps:
    - uses: actions/checkout@v2
    - uses: Swatinem/rust-cache@v1
    - name: Install dependencies
      shell: bash
      run: |
        ## Install dependencies
        sudo apt-get update
        sudo apt-get install jq
    - name: Install `rust` toolchain
      uses: actions-rs/toolchain@v1
      with:
        toolchain: stable
        default: true
        profile: minimal # minimal component installation (ie, no documentation)
    - name: "`make install`"
      shell: bash
      run: |
        make install DESTDIR=target/size-release/
        make install MULTICALL=y DESTDIR=target/size-multi-release/
        # strip the results
        strip target/size*/usr/local/bin/*
    - name: "Compute sizes"
      shell: bash
      run: |
        SIZE=$(du -s target/size-release/usr/local/bin/|awk '{print $1}')
        SIZEMULTI=$(du -s target/size-multi-release/usr/local/bin/|awk '{print $1}')
        jq -n \
              --arg date "$(date --rfc-email)" \
              --arg sha "$GITHUB_SHA" \
              --arg size "$SIZE" \
              --arg sizemulti "$SIZEMULTI" \
              '{($date): { sha: $sha, size: $size, sizemulti: $sizemulti, }}' > size-result.json
    - uses: actions/upload-artifact@v2
      with:
        name: size-result
        path: size-result.json

  build:
    name: Build
    needs: [ min_version, deps ]
    runs-on: ${{ matrix.job.os }}
    strategy:
      fail-fast: false
      matrix:
        job:
          # { os , target , cargo-options , features , use-cross , toolchain }
          - { os: ubuntu-latest  , target: arm-unknown-linux-gnueabihf, features: feat_os_unix_gnueabihf, use-cross: use-cross, }
          - { os: ubuntu-latest  , target: aarch64-unknown-linux-gnu   , features: feat_os_unix_gnueabihf , use-cross: use-cross }
          - { os: ubuntu-latest  , target: x86_64-unknown-linux-gnu    , features: feat_os_unix           , use-cross: use-cross }
          # - { os: ubuntu-latest  , target: x86_64-unknown-linux-gnu    , features: feat_selinux           , use-cross: use-cross }
          # - { os: ubuntu-18.04   , target: i586-unknown-linux-gnu      , features: feat_os_unix           , use-cross: use-cross } ## note: older windows platform; not required, dev-FYI only
          # - { os: ubuntu-18.04   , target: i586-unknown-linux-gnu      , features: feat_os_unix           , use-cross: use-cross } ## note: older windows platform; not required, dev-FYI only
          - { os: ubuntu-latest   , target: i686-unknown-linux-gnu      , features: feat_os_unix           , use-cross: use-cross }
          - { os: ubuntu-latest   , target: i686-unknown-linux-musl     , features: feat_os_unix_musl      , use-cross: use-cross }
          - { os: ubuntu-latest   , target: x86_64-unknown-linux-gnu    , features: feat_os_unix           , use-cross: use-cross }
          - { os: ubuntu-latest   , target: x86_64-unknown-linux-musl   , features: feat_os_unix_musl      , use-cross: use-cross }
          # Commented until https://github.com/uutils/coreutils/issues/3210 is fixed
          #- { os: ubuntu-18.04   , target: i686-unknown-linux-gnu      , features: feat_os_unix           , use-cross: use-cross }
          #- { os: ubuntu-18.04   , target: i686-unknown-linux-musl     , features: feat_os_unix_musl      , use-cross: use-cross }
          #- { os: ubuntu-18.04   , target: x86_64-unknown-linux-gnu    , features: feat_os_unix           , use-cross: use-cross }
          #- { os: ubuntu-18.04   , target: x86_64-unknown-linux-musl   , features: feat_os_unix_musl      , use-cross: use-cross }
          - { os: macos-latest   , target: x86_64-apple-darwin         , features: feat_os_macos }
          - { os: windows-latest , target: i686-pc-windows-gnu         , features: feat_os_windows }
          - { os: windows-latest , target: i686-pc-windows-msvc        , features: feat_os_windows }
          - { os: windows-latest , target: x86_64-pc-windows-gnu       , features: feat_os_windows } ## note: requires rust >= 1.43.0 to link correctly
          - { os: windows-latest , target: x86_64-pc-windows-msvc      , features: feat_os_windows }
    steps:
    - uses: actions/checkout@v2
    - uses: Swatinem/rust-cache@v1
    - name: Initialize workflow variables
      id: vars
      shell: bash
      run: |
        ## VARs setup
        outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
        # toolchain
        TOOLCHAIN="stable" ## default to "stable" toolchain
        # * specify alternate/non-default TOOLCHAIN for *-pc-windows-gnu targets; gnu targets on Windows are broken for the standard *-pc-windows-msvc toolchain (refs: GH:rust-lang/rust#47048, GH:rust-lang/rust#53454, GH:rust-lang/cargo#6754)
        case ${{ matrix.job.target }} in *-pc-windows-gnu) TOOLCHAIN="stable-${{ matrix.job.target }}" ;; esac;
        # * use requested TOOLCHAIN if specified
        if [ -n "${{ matrix.job.toolchain }}" ]; then TOOLCHAIN="${{ matrix.job.toolchain }}" ; fi
        outputs TOOLCHAIN
        # staging directory
        STAGING='_staging'
        outputs STAGING
        # determine EXE suffix
        EXE_suffix="" ; case '${{ matrix.job.target }}' in *-pc-windows-*) EXE_suffix=".exe" ;; esac;
        outputs EXE_suffix
        # parse commit reference info
        echo GITHUB_REF=${GITHUB_REF}
        echo GITHUB_SHA=${GITHUB_SHA}
        REF_NAME=${GITHUB_REF#refs/*/}
        unset REF_BRANCH ; case "${GITHUB_REF}" in refs/heads/*) REF_BRANCH=${GITHUB_REF#refs/heads/} ;; esac;
        unset REF_TAG ; case "${GITHUB_REF}" in refs/tags/*) REF_TAG=${GITHUB_REF#refs/tags/} ;; esac;
        REF_SHAS=${GITHUB_SHA:0:8}
        outputs REF_NAME REF_BRANCH REF_TAG REF_SHAS
        # parse target
        unset TARGET_ARCH
        case '${{ matrix.job.target }}' in
          aarch64-*) TARGET_ARCH=arm64 ;;
          arm-*-*hf) TARGET_ARCH=armhf ;;
          i586-*) TARGET_ARCH=i586 ;;
          i686-*) TARGET_ARCH=i686 ;;
          x86_64-*) TARGET_ARCH=x86_64 ;;
        esac;
        unset TARGET_OS ; case '${{ matrix.job.target }}' in *-linux-*) TARGET_OS=linux ;; *-apple-*) TARGET_OS=macos ;; *-windows-*) TARGET_OS=windows ;; esac;
        outputs TARGET_ARCH TARGET_OS
        # package name
        PKG_suffix=".tar.gz" ; case '${{ matrix.job.target }}' in *-pc-windows-*) PKG_suffix=".zip" ;; esac;
        PKG_BASENAME=${PROJECT_NAME}-${REF_TAG:-$REF_SHAS}-${{ matrix.job.target }}
        PKG_NAME=${PKG_BASENAME}${PKG_suffix}
        outputs PKG_suffix PKG_BASENAME PKG_NAME
        # deployable tag? (ie, leading "vM" or "M"; M == version number)
        unset DEPLOY ; if [[ $REF_TAG =~ ^[vV]?[0-9].* ]]; then DEPLOY='true' ; fi
        outputs DEPLOY
        # DPKG architecture?
        unset DPKG_ARCH
        case ${{ matrix.job.target }} in
          x86_64-*-linux-*) DPKG_ARCH=amd64 ;;
          *-linux-*) DPKG_ARCH=${TARGET_ARCH} ;;
        esac
        outputs DPKG_ARCH
        # DPKG version?
        unset DPKG_VERSION ; if [[ $REF_TAG =~ ^[vV]?[0-9].* ]]; then DPKG_VERSION=${REF_TAG/#[vV]/} ; fi
        outputs DPKG_VERSION
        # DPKG base name/conflicts?
        DPKG_BASENAME=${PROJECT_NAME}
        DPKG_CONFLICTS=${PROJECT_NAME}-musl
        case ${{ matrix.job.target }} in *-musl) DPKG_BASENAME=${PROJECT_NAME}-musl ; DPKG_CONFLICTS=${PROJECT_NAME} ;; esac;
        outputs DPKG_BASENAME DPKG_CONFLICTS
        # DPKG name
        unset DPKG_NAME;
        if [[ -n $DPKG_ARCH && -n $DPKG_VERSION ]]; then DPKG_NAME="${DPKG_BASENAME}_${DPKG_VERSION}_${DPKG_ARCH}.deb" ; fi
        outputs DPKG_NAME
        # target-specific options
        # * CARGO_FEATURES_OPTION
        CARGO_FEATURES_OPTION='' ;
        if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi
        outputs CARGO_FEATURES_OPTION
        # * CARGO_USE_CROSS (truthy)
        CARGO_USE_CROSS='true' ; case '${{ matrix.job.use-cross }}' in ''|0|f|false|n|no) unset CARGO_USE_CROSS ;; esac;
        outputs CARGO_USE_CROSS
        # ** pass needed environment into `cross` container (iff `cross` not already configured via "Cross.toml")
        if [ -n "${CARGO_USE_CROSS}" ] && [ ! -e "Cross.toml" ] ; then
          printf "[build.env]\npassthrough = [\"CI\"]\n" > Cross.toml
        fi
        # * test only library and/or binaries for arm-type targets
        unset CARGO_TEST_OPTIONS ; case '${{ matrix.job.target }}' in aarch64-* | arm-*) CARGO_TEST_OPTIONS="--bins" ;; esac;
        outputs CARGO_TEST_OPTIONS
        # * executable for `strip`?
        STRIP="strip"
        case ${{ matrix.job.target }} in
          aarch64-*-linux-gnu) STRIP="aarch64-linux-gnu-strip" ;;
          arm-*-linux-gnueabihf) STRIP="arm-linux-gnueabihf-strip" ;;
          *-pc-windows-msvc) STRIP="" ;;
        esac;
        outputs STRIP
    - name: Install/setup prerequisites
      shell: bash
      run: |
        ## Install/setup prerequisites
        case '${{ matrix.job.target }}' in
          arm-unknown-linux-gnueabihf) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;;
          aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;;
        esac
        case '${{ matrix.job.os }}' in
          macos-latest) brew install coreutils ;; # needed for testing
        esac
    - name: Create all needed build/work directories
      shell: bash
      run: |
        ## Create build/work space
        mkdir -p '${{ steps.vars.outputs.STAGING }}'
        mkdir -p '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}'
        mkdir -p '${{ steps.vars.outputs.STAGING }}/dpkg'
    - name: Install/setup prerequisites
      shell: bash
      run: |
        ## Install/setup prerequisites
        case '${{ matrix.job.target }}' in
          arm-unknown-linux-gnueabihf) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;;
          aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;;
        esac
        case '${{ matrix.job.os }}' in
          macos-latest) brew install coreutils ;; # needed for testing
        esac
    - name: rust toolchain ~ install
      uses: actions-rs/toolchain@v1
      with:
        toolchain: ${{ env.RUST_MIN_SRV }}
        target: ${{ matrix.job.target }}
        default: true
        profile: minimal # minimal component installation (ie, no documentation)
    - name: Initialize toolchain-dependent workflow variables
      id: dep_vars
      shell: bash
      run: |
        ## Dependent VARs setup
        outputs() { step_id="dep_vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
        # * determine sub-crate utility list
        UTILITY_LIST="$(./util/show-utils.sh ${CARGO_FEATURES_OPTION})"
        echo UTILITY_LIST=${UTILITY_LIST}
        CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo "-puu_${u}"; done;)"
        outputs CARGO_UTILITY_LIST_OPTIONS
    - name: Install `cargo-tree` # for dependency information
      uses: actions-rs/install@v0.1
      with:
        crate: cargo-tree
        version: latest
        use-tool-cache: true
      env:
        RUSTUP_TOOLCHAIN: stable
    - name: Info
      shell: bash
      run: |
        ## Info
        # commit info
        echo "## commit"
        echo GITHUB_REF=${GITHUB_REF}
        echo GITHUB_SHA=${GITHUB_SHA}
        # environment
        echo "## environment"
        echo "CI='${CI}'"
        # tooling info display
        echo "## tooling"
        which gcc >/dev/null 2>&1 && (gcc --version | head -1) || true
        rustup -V 2>/dev/null
        rustup show active-toolchain
        cargo -V
        rustc -V
        cargo-tree tree -V
        # dependencies
        echo "## dependency list"
        cargo fetch --locked --quiet
        cargo-tree tree --locked --target=${{ matrix.job.target }} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --all --no-dev-dependencies --no-indent | grep -vE "$PWD" | sort --unique
    - name: Build
      uses: actions-rs/cargo@v1
      with:
        use-cross: ${{ steps.vars.outputs.CARGO_USE_CROSS }}
        command: build
        args: --release --target=${{ matrix.job.target }} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }}
        toolchain: ${{ env.RUST_MIN_SRV }}
    - name: Test
      uses: actions-rs/cargo@v1
      with:
        use-cross: ${{ steps.vars.outputs.CARGO_USE_CROSS }}
        command: test
        args: --target=${{ matrix.job.target }} ${{ steps.vars.outputs.CARGO_TEST_OPTIONS}} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }}
        toolchain: ${{ env.RUST_MIN_SRV }}
    - name: Test individual utilities
      uses: actions-rs/cargo@v1
      with:
        use-cross: ${{ steps.vars.outputs.CARGO_USE_CROSS }}
        command: test
        args: --target=${{ matrix.job.target }} ${{ steps.vars.outputs.CARGO_TEST_OPTIONS}} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} ${{ steps.dep_vars.outputs.CARGO_UTILITY_LIST_OPTIONS }}
        toolchain: ${{ env.RUST_MIN_SRV }}
    - name: Archive executable artifacts
      uses: actions/upload-artifact@v2
      with:
        name: ${{ env.PROJECT_NAME }}-${{ matrix.job.target }}
        path: target/${{ matrix.job.target }}/release/${{ env.PROJECT_NAME }}${{ steps.vars.outputs.EXE_suffix }}
    - name: Package
      shell: bash
      run: |
        ## Package artifact(s)
        # binary
        cp 'target/${{ matrix.job.target }}/release/${{ env.PROJECT_NAME }}${{ steps.vars.outputs.EXE_suffix }}' '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}/'
        # `strip` binary (if needed)
        if [ -n "${{ steps.vars.outputs.STRIP }}" ]; then "${{ steps.vars.outputs.STRIP }}" '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}/${{ env.PROJECT_NAME }}${{ steps.vars.outputs.EXE_suffix }}' ; fi
        # README and LICENSE
        # * spell-checker:ignore EADME ICENSE
        (shopt -s nullglob; for f in [R]"EADME"{,.*}; do cp $f '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}/' ; done)
        (shopt -s nullglob; for f in [L]"ICENSE"{-*,}{,.*}; do cp $f '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}/' ; done)
        # core compressed package
        pushd '${{ steps.vars.outputs.STAGING }}/' >/dev/null
        case '${{ matrix.job.target }}' in
          *-pc-windows-*) 7z -y a '${{ steps.vars.outputs.PKG_NAME }}' '${{ steps.vars.outputs.PKG_BASENAME }}'/* | tail -2 ;;
          *) tar czf '${{ steps.vars.outputs.PKG_NAME }}' '${{ steps.vars.outputs.PKG_BASENAME }}'/* ;;
        esac
        popd >/dev/null
        # dpkg
        if [ -n "${{ steps.vars.outputs.DPKG_NAME }}" ]; then
          DPKG_DIR="${{ steps.vars.outputs.STAGING }}/dpkg"
          # binary
          install -Dm755 'target/${{ matrix.job.target }}/release/${{ env.PROJECT_NAME }}${{ steps.vars.outputs.EXE_suffix }}' "${DPKG_DIR}/usr/bin/${{ env.PROJECT_NAME }}${{ steps.vars.outputs.EXE_suffix }}"
          if [ -n "${{ steps.vars.outputs.STRIP }}" ]; then "${{ steps.vars.outputs.STRIP }}" "${DPKG_DIR}/usr/bin/${{ env.PROJECT_NAME }}${{ steps.vars.outputs.EXE_suffix }}" ; fi
          # README and LICENSE
          (shopt -s nullglob; for f in [R]"EADME"{,.*}; do install -Dm644 "$f" "${DPKG_DIR}/usr/share/doc/${{ env.PROJECT_NAME }}/$f" ; done)
          (shopt -s nullglob; for f in [L]"ICENSE"{-*,}{,.*}; do install -Dm644 "$f" "${DPKG_DIR}/usr/share/doc/${{ env.PROJECT_NAME }}/$f" ; done)
          # control file
          mkdir -p "${DPKG_DIR}/DEBIAN"
          printf "Package: ${{ steps.vars.outputs.DPKG_BASENAME }}\nVersion: ${{ steps.vars.outputs.DPKG_VERSION }}\nSection: utils\nPriority: optional\nMaintainer: ${{ env.PROJECT_AUTH }}\nArchitecture: ${{ steps.vars.outputs.DPKG_ARCH }}\nProvides: ${{ env.PROJECT_NAME }}\nConflicts: ${{ steps.vars.outputs.DPKG_CONFLICTS }}\nDescription: ${{ env.PROJECT_DESC }}\n" > "${DPKG_DIR}/DEBIAN/control"
          # build dpkg
          fakeroot dpkg-deb --build "${DPKG_DIR}" "${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.DPKG_NAME }}"
        fi
    - name: Publish
      uses: softprops/action-gh-release@v1
      if: steps.vars.outputs.DEPLOY
      with:
        files: |
          ${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_NAME }}
          ${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.DPKG_NAME }}
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  test_busybox:
    name: Tests/BusyBox test suite
    needs: [ min_version, deps ]
    runs-on: ${{ matrix.job.os }}
    strategy:
      fail-fast: false
      matrix:
        job:
          - { os: ubuntu-latest }
    steps:
    - uses: actions/checkout@v2
    - uses: Swatinem/rust-cache@v1
    - name: Install/setup prerequisites
      shell: bash
      run: |
        ## Install/setup prerequisites
        make prepare-busytest
    - name: Install `rust` toolchain
      uses: actions-rs/toolchain@v1
      with:
        toolchain: stable
        default: true
        profile: minimal # minimal component installation (ie, no documentation)
    - name: "Run BusyBox test suite"
      shell: bash
      run: |
        ## Run BusyBox test suite
        bindir=$(pwd)/target/debug
        cd tmp/busybox-*/testsuite
        output=$(bindir=$bindir ./runtest 2>&1 || true)
        printf "%s\n" "${output}"
        n_fails=$(echo "$output" | grep "^FAIL:\s" | wc --lines)
        if [ $n_fails -gt 0 ] ; then echo "::warning ::${n_fails}+ test failures" ; fi

  test_freebsd:
    name: Tests/FreeBSD test suite
    needs: [ min_version, deps ]
    runs-on: ${{ matrix.job.os }}
    strategy:
      fail-fast: false
      matrix:
        job:
          - { os: macos-10.15 , features: unix } ## GHA MacOS-11.0 VM won't have VirtualBox; refs: <https://github.com/actions/virtual-environments/issues/4060> , <https://github.com/actions/virtual-environments/pull/4010>
    env:
      mem: 2048
    steps:
    - uses: actions/checkout@v2
    - uses: Swatinem/rust-cache@v1
    - name: Prepare, build and test
      ## spell-checker:ignore (ToDO) sshfs usesh vmactions
      uses: vmactions/freebsd-vm@v0.1.5
      with:
        usesh: true
        # sync: sshfs
        prepare: pkg install -y curl gmake sudo
        run: |
          ## Prepare, build, and test
          # implementation modelled after ref: <https://github.com/rust-lang/rustup/pull/2783>
          # * NOTE: All steps need to be run in this block, otherwise, we are operating back on the mac host
          set -e
          #
          TEST_USER=tester
          REPO_NAME=${GITHUB_WORKSPACE##*/}
          WORKSPACE_PARENT="/Users/runner/work/${REPO_NAME}"
          WORKSPACE="${WORKSPACE_PARENT}/${REPO_NAME}"
          #
          pw adduser -n ${TEST_USER} -d /root/ -g wheel -c "Coreutils user to build" -w random
          # chown -R ${TEST_USER}:wheel /root/ "${WORKSPACE_PARENT}"/
          chown -R ${TEST_USER}:wheel /root/ "/Users/runner/work/${REPO_NAME}"/
          whoami
          #
          # Further work needs to be done in a sudo as we are changing users
          sudo -i -u ${TEST_USER} sh << EOF
          set -e
          whoami
          curl https://sh.rustup.rs -sSf --output rustup.sh
          sh rustup.sh -y --profile=minimal
          . $HOME/.cargo/env
          ## Info
          # environment
          echo "## environment"
          echo "CI='${CI}'"
          echo "REPO_NAME='${REPO_NAME}'"
          echo "TEST_USER='${TEST_USER}'"
          echo "WORKSPACE_PARENT='${WORKSPACE_PARENT}'"
          echo "WORKSPACE='${WORKSPACE}'"
          env | sort
          # tooling info
          echo "## tooling info"
          cargo -V
          rustc -V
          #
          cd "${WORKSPACE}"
          unset FAULT
          cargo build || FAULT=1
          cargo test --features "${{ matrix.job.features }}" || FAULT=1
          cargo test --features "${{ matrix.job.features }}" -p uucore || FAULT=1
          # Clean to avoid to rsync back the files
          cargo clean
          if (test -n "$FAULT"); then exit 1 ; fi
          EOF

  coverage:
    name: Code Coverage
    needs: build
    runs-on: ${{ matrix.job.os }}
    strategy:
      fail-fast: true
      matrix:
        # job: [ { os: ubuntu-latest }, { os: macos-latest }, { os: windows-latest } ]
        job:
          - { os: ubuntu-latest  , features: unix }
          - { os: macos-latest   , features: macos }
          - { os: windows-latest , features: windows }
    steps:
    - uses: actions/checkout@v2
    - uses: Swatinem/rust-cache@v1
    # - name: Reattach HEAD ## may be needed for accurate code coverage info
    #   run: git checkout ${{ github.head_ref }}
    - name: Initialize workflow variables
      id: vars
      shell: bash
      run: |
        ## VARs setup
        outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
        # toolchain
        TOOLCHAIN="nightly" ## default to "nightly" toolchain (required for certain required unstable compiler flags) ## !maint: refactor when stable channel has needed support
        # * specify gnu-type TOOLCHAIN for windows; `grcov` requires gnu-style code coverage data files
        case ${{ matrix.job.os }} in windows-*) TOOLCHAIN="$TOOLCHAIN-x86_64-pc-windows-gnu" ;; esac;
        # * use requested TOOLCHAIN if specified
        if [ -n "${{ matrix.job.toolchain }}" ]; then TOOLCHAIN="${{ matrix.job.toolchain }}" ; fi
        outputs TOOLCHAIN
        # staging directory
        STAGING='_staging'
        outputs STAGING
        # target-specific options
        # * CARGO_FEATURES_OPTION
        CARGO_FEATURES_OPTION='--all-features' ;  ## default to '--all-features' for code coverage
        if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi
        outputs CARGO_FEATURES_OPTION
        # * CODECOV_FLAGS
        CODECOV_FLAGS=$( echo "${{ matrix.job.os }}" | sed 's/[^[:alnum:]]/_/g' )
        outputs CODECOV_FLAGS
    - name: Install/setup prerequisites
      shell: bash
      run: |
        ## Install/setup prerequisites
        case '${{ matrix.job.os }}' in
          macos-latest) brew install coreutils ;; # needed for testing
        esac
    - name: rust toolchain ~ install
      uses: actions-rs/toolchain@v1
      with:
        toolchain: ${{ steps.vars.outputs.TOOLCHAIN }}
        default: true
        profile: minimal # minimal component installation (ie, no documentation)
    - name: Initialize toolchain-dependent workflow variables
      id: dep_vars
      shell: bash
      run: |
        ## Dependent VARs setup
        outputs() { step_id="dep_vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
        # * determine sub-crate utility list
        UTILITY_LIST="$(./util/show-utils.sh ${CARGO_FEATURES_OPTION})"
        CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo "-puu_${u}"; done;)"
        outputs CARGO_UTILITY_LIST_OPTIONS
    - name: Test uucore
      uses: actions-rs/cargo@v1
      with:
        command: test
        args: ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-fail-fast -p uucore
      env:
        CARGO_INCREMENTAL: "0"
        RUSTC_WRAPPER: ""
        RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
        RUSTDOCFLAGS: "-Cpanic=abort"
        # RUSTUP_TOOLCHAIN: ${{ steps.vars.outputs.TOOLCHAIN }}
    - name: Test
      uses: actions-rs/cargo@v1
      with:
        command: test
        args: ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-fail-fast
      env:
        CARGO_INCREMENTAL: "0"
        RUSTC_WRAPPER: ""
        RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
        RUSTDOCFLAGS: "-Cpanic=abort"
        # RUSTUP_TOOLCHAIN: ${{ steps.vars.outputs.TOOLCHAIN }}
    - name: Test individual utilities
      uses: actions-rs/cargo@v1
      with:
        command: test
        args: ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-fail-fast ${{ steps.dep_vars.outputs.CARGO_UTILITY_LIST_OPTIONS }}
      env:
        CARGO_INCREMENTAL: "0"
        RUSTC_WRAPPER: ""
        RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
        RUSTDOCFLAGS: "-Cpanic=abort"
        # RUSTUP_TOOLCHAIN: ${{ steps.vars.outputs.TOOLCHAIN }}
    - name: "`grcov` ~ install"
      uses: actions-rs/install@v0.1
      with:
        crate: grcov
        version: latest
        use-tool-cache: false
    - name: Generate coverage data (via `grcov`)
      id: coverage
      shell: bash
      run: |
        ## Generate coverage data
        COVERAGE_REPORT_DIR="target/debug"
        COVERAGE_REPORT_FILE="${COVERAGE_REPORT_DIR}/lcov.info"
        # GRCOV_IGNORE_OPTION='--ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*"' ## `grcov` ignores these params when passed as an environment variable (why?)
        # GRCOV_EXCLUDE_OPTION='--excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()"' ## `grcov` ignores these params when passed as an environment variable (why?)
        mkdir -p "${COVERAGE_REPORT_DIR}"
        # display coverage files
        grcov . --output-type files --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" | sort --unique
        # generate coverage report
        grcov . --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()"
        echo ::set-output name=report::${COVERAGE_REPORT_FILE}
    - name: Upload coverage results (to Codecov.io)
      uses: codecov/codecov-action@v1
      # if: steps.vars.outputs.HAS_CODECOV_TOKEN
      with:
        # token: ${{ secrets.CODECOV_TOKEN }}
        file: ${{ steps.coverage.outputs.report }}
        ## flags: IntegrationTests, UnitTests, ${{ steps.vars.outputs.CODECOV_FLAGS }}
        flags: ${{ steps.vars.outputs.CODECOV_FLAGS }}
        name: codecov-umbrella
        fail_ci_if_error: false