name: CICD
env:
PROJECT_NAME: coreutils
PROJECT_DESC: "Core universal (cross-platform) utilities"
PROJECT_AUTH: "uutils"
RUST_MIN_SRV: "1.88.0"
CARGO_INCREMENTAL: "0"
STYLE_FAIL_ON_FAULT: true
on:
pull_request:
push:
tags:
- '*'
branches:
- '*'
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
cargo-deny:
name: Style/cargo-deny
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: EmbarkStudios/cargo-deny-action@v2
style_deps:
name: Style/deps
runs-on: ${{ matrix.job.os }}
strategy:
fail-fast: false
matrix:
job:
- { 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@v6
with:
persist-credentials: false
- uses: taiki-e/install-action@cargo-udeps
- uses: Swatinem/rust-cache@v2
- name: Initialize workflow variables
id: vars
shell: bash
run: |
echo "RUSTC_BOOTSTRAP=1" >> "${GITHUB_ENV}" # Use -Z
## VARs setup
outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; 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: 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 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
doc_warnings:
name: Documentation/warnings
runs-on: ${{ matrix.job.os }}
strategy:
fail-fast: false
matrix:
job:
- { os: ubuntu-latest , features: feat_os_unix }
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
components: clippy
- uses: Swatinem/rust-cache@v2
- name: Run sccache-cache
id: sccache-setup
uses: mozilla-actions/sccache-action@v0.0.9
continue-on-error: true
- name: Export sccache
if: steps.sccache-setup.outcome == 'success'
run: |
echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV
echo "SCCACHE_GHA_ENABLED=true" >> $GITHUB_ENV
- name: Install/setup prerequisites
shell: bash
run: |
sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu libselinux1-dev
- name: Initialize workflow variables
id: vars
shell: bash
run: |
## VARs setup
outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; 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 -n "-puu_${u} "; done;)"
outputs CARGO_UTILITY_LIST_OPTIONS
- name: "`cargo doc` with warnings"
shell: bash
run: |
RUSTDOCFLAGS="-Dwarnings" cargo doc ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-deps --workspace --document-private-items
- uses: DavidAnson/markdownlint-cli2-action@v23
with:
fix: "true"
globs: |
*.md
docs/src/*.md
src/uu/*/*.md
min_version:
name: MinRustV runs-on: ${{ matrix.job.os }}
strategy:
matrix:
job:
- { os: ubuntu-latest , features: feat_os_unix }
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.RUST_MIN_SRV }}
components: rustfmt
- uses: taiki-e/install-action@nextest
- uses: Swatinem/rust-cache@v2
- name: Run sccache-cache
id: sccache-setup
uses: mozilla-actions/sccache-action@v0.0.9
continue-on-error: true
- name: Export sccache
if: steps.sccache-setup.outcome == 'success'
run: |
echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV
echo "SCCACHE_GHA_ENABLED=true" >> $GITHUB_ENV
- name: Initialize workflow variables
id: vars
shell: bash
run: |
## VARs setup
outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; 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: 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)
for dir in "." "fuzz"; do
( cd "$dir" && cargo fetch --locked --quiet --target $(rustc --print host-tuple)) || { echo "::error file=$dir/Cargo.lock::Incompatible (or out-of-date) '$dir/Cargo.lock' file; update using \`cd '$dir' && cargo +${{ env.RUST_MIN_SRV }} update\`" ; exit 1 ; }
done
- name: Install/setup prerequisites
shell: bash
run: |
# Install a package for one of the tests
sudo apt-get -y update ; sudo apt-get -y install attr
- 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 -V
# dependencies
echo "## dependency list"
## * using the 'stable' toolchain is necessary to avoid "unexpected '--filter-platform'" errors
cargo +stable fetch --locked --quiet --target $(rustc --print host-tuple)
cargo +stable tree --no-dedupe --locked -e=no-dev --prefix=none ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} | grep -vE "$PWD" | sort --unique
- name: Test
run: cargo nextest run --hide-progress-bar --profile ci ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} -p uucore -p coreutils
env:
RUSTFLAGS: "-Awarnings"
RUST_BACKTRACE: "1"
- name: Upload test results to Codecov
if: ${{ !cancelled() }}
uses: codecov/codecov-action@v6
with:
token: ${{ secrets.CODECOV_TOKEN }}
report_type: test_results
files: target/nextest/ci/junit.xml
disable_search: true
flags: msrv,${{ matrix.job.os }}
fail_ci_if_error: false
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@v6
with:
persist-credentials: false
- 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>
for dir in "." "fuzz"; do
( cd "$dir" && cargo fetch --locked --quiet --target $(rustc --print host-tuple)) || { echo "::error file=$dir/Cargo.lock::'$dir/Cargo.lock' file requires update (use \`cd '$dir' && cargo +${{ env.RUST_MIN_SRV }} update\`)" ; exit 1 ; }
done
build_rust_stable:
name: Build/stable
needs: [ min_version, deps ]
runs-on: ${{ matrix.job.os }}
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
job:
- { os: ubuntu-latest , features: feat_os_unix }
- { os: macos-latest , features: feat_os_unix }
- { os: windows-latest , features: feat_os_windows }
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@stable
- uses: taiki-e/install-action@nextest
- uses: Swatinem/rust-cache@v2
- name: Run sccache-cache
id: sccache-setup
uses: mozilla-actions/sccache-action@v0.0.9
continue-on-error: true
- name: Export sccache
if: steps.sccache-setup.outcome == 'success'
run: |
echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV
echo "SCCACHE_GHA_ENABLED=true" >> $GITHUB_ENV
- name: Test
run: cargo nextest run --hide-progress-bar --profile ci --features ${{ matrix.job.features }}
env:
RUST_BACKTRACE: "1"
- name: Upload test results to Codecov
if: ${{ !cancelled() }}
uses: codecov/codecov-action@v6
with:
token: ${{ secrets.CODECOV_TOKEN }}
report_type: test_results
files: target/nextest/ci/junit.xml
disable_search: true
flags: stable,${{ matrix.job.os }}
fail_ci_if_error: false
build_rust_nightly:
name: Build/nightly
needs: [ min_version, deps ]
runs-on: ${{ matrix.job.os }}
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
job:
- { os: ubuntu-latest , features: feat_os_unix }
- { os: macos-latest , features: feat_os_unix }
- { os: windows-latest , features: feat_os_windows }
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@nightly
- uses: taiki-e/install-action@nextest
- uses: Swatinem/rust-cache@v2
- name: Run sccache-cache
id: sccache-setup
uses: mozilla-actions/sccache-action@v0.0.9
continue-on-error: true
- name: Export sccache
if: steps.sccache-setup.outcome == 'success'
run: |
echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV
echo "SCCACHE_GHA_ENABLED=true" >> $GITHUB_ENV
- name: Test
run: cargo nextest run --hide-progress-bar --profile ci --features ${{ matrix.job.features }}
env:
RUST_BACKTRACE: "1"
- name: Upload test results to Codecov
if: ${{ !cancelled() }}
uses: codecov/codecov-action@v6
with:
token: ${{ secrets.CODECOV_TOKEN }}
report_type: test_results
files: target/nextest/ci/junit.xml
disable_search: true
flags: nightly,${{ matrix.job.os }}
fail_ci_if_error: false
build:
permissions:
contents: write
name: Build
needs: [ min_version, deps ]
runs-on: ${{ matrix.job.os }}
timeout-minutes: 90
env:
DOCKER_OPTS: '--volume /etc/passwd:/etc/passwd --volume /etc/group:/etc/group'
strategy:
fail-fast: false
matrix:
job:
- { os: ubuntu-latest , target: arm-unknown-linux-gnueabihf , features: feat_os_unix_gnueabihf , use-cross: use-cross , skip-tests: true }
- { os: ubuntu-24.04-arm , target: aarch64-unknown-linux-gnu , features: feat_os_unix_gnueabihf }
- { os: ubuntu-latest , target: aarch64-unknown-linux-musl , features: feat_os_unix_musl , use-cross: use-cross , skip-tests: true }
- { os: ubuntu-latest , target: riscv64gc-unknown-linux-musl , features: feat_os_unix_musl , use-cross: use-cross , skip-tests: true }
- { os: ubuntu-latest , target: i686-unknown-linux-gnu , features: "feat_os_unix,test_risky_names", 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,test_risky_names", use-cross: use-cross, skip-publish: true }
- { os: ubuntu-latest , target: x86_64-unknown-linux-gnu , features: "feat_os_unix,uudoc" , use-cross: no, workspace-tests: true }
- { os: ubuntu-latest , target: x86_64-unknown-linux-musl , features: feat_os_unix_musl , use-cross: use-cross }
- { os: ubuntu-latest , target: x86_64-unknown-netbsd, features: "feat_os_unix", use-cross: use-cross , skip-tests: true , check-only: true }
- { os: ubuntu-latest , target: x86_64-unknown-redox , features: feat_os_unix_redox , use-cross: redoxer , skip-tests: true , check-only: true }
- { os: ubuntu-latest , target: wasm32-wasip1, default-features: false, features: feat_wasm, skip-tests: true }
- { os: macos-latest , target: aarch64-apple-darwin , features: feat_os_unix, workspace-tests: true } - { os: macos-latest , target: aarch64-apple-darwin , workspace-tests: true, check-only: true } - { os: macos-latest , target: x86_64-apple-darwin , features: feat_os_unix, workspace-tests: true }
- { 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 }
- { os: windows-latest , target: x86_64-pc-windows-msvc , features: feat_os_windows }
- { os: windows-latest , target: aarch64-pc-windows-msvc , features: feat_os_windows, use-cross: use-cross , skip-tests: true }
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- name: Avoid no space left on device
run: sudo rm -rf /usr/share/dotnet /usr/local/lib/android &
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.RUST_MIN_SRV }}
targets: ${{ matrix.job.target }}
- uses: Swatinem/rust-cache@v2
with:
key: "${{ matrix.job.os }}_${{ matrix.job.target }}"
- name: Run sccache-cache
id: sccache-setup
uses: mozilla-actions/sccache-action@v0.0.9
continue-on-error: true
- name: Export sccache
if: steps.sccache-setup.outcome == 'success'
run: |
echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV
echo "SCCACHE_GHA_ENABLED=true" >> $GITHUB_ENV
- name: Initialize workflow variables
id: vars
shell: bash
run: |
## VARs setup
outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; 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
# 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:10}
outputs REF_NAME REF_BRANCH REF_TAG REF_SHAS
# parse target
unset TARGET_ARCH
case '${{ matrix.job.target }}' in
aarch64-*) TARGET_ARCH=arm64 ;;
riscv64gc-*) TARGET_ARCH=riscv64 ;;
arm-*-*hf) TARGET_ARCH=armhf ;;
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 ;;
*-redox*) TARGET_OS=redox ;;
esac
outputs TARGET_ARCH TARGET_OS
# package name
PKG_suffix=".tar.gz" ; case '${{ matrix.job.target }}' in *-pc-windows-*) PKG_suffix=".zip" ;; esac;
# Some 3rd party utils need version at file names
# But we remove it from tag/latest-commit
test ${REF_TAG} \
&& PKG_BASENAME=${PROJECT_NAME}-${REF_TAG}-${{ matrix.job.target }} \
|| PKG_BASENAME=${PROJECT_NAME}-${{ 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
# 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_DEFAULT_FEATURES_OPTION
CARGO_DEFAULT_FEATURES_OPTION='' ;
if [ "${{ matrix.job.default-features }}" == "false" ]; then CARGO_DEFAULT_FEATURES_OPTION='--no-default-features' ; fi
outputs CARGO_DEFAULT_FEATURES_OPTION
# * CARGO_CMD
CARGO_CMD='cross'
CARGO_CMD_OPTIONS='+${{ env.RUST_MIN_SRV }}'
# Added suffix for artifacts, needed when multiple jobs use the same target.
ARTIFACTS_SUFFIX=''
case '${{ matrix.job.use-cross }}' in
''|0|f|false|n|no)
CARGO_CMD='cargo'
ARTIFACTS_SUFFIX='-nocross'
;;
redoxer)
CARGO_CMD='redoxer'
CARGO_CMD_OPTIONS=''
;;
esac
# needed for target "aarch64-apple-darwin". There are two jobs, and the difference between them is whether "features" is set
if [ -z "${{ matrix.job.features }}" ]; then ARTIFACTS_SUFFIX='-nofeatures' ; fi
outputs CARGO_CMD
outputs CARGO_CMD_OPTIONS
outputs ARTIFACTS_SUFFIX
CARGO_TEST_OPTIONS=''
case '${{ matrix.job.workspace-tests }}' in
1|t|true|y|yes)
# This also runs tests in other packages in the source directory (e.g. uucore).
# We cannot enable this everywhere as some platforms are currently broken, and
# we cannot use `cross` as its Docker image is ancient (Ubuntu 16.04) and is
# missing required system dependencies (e.g. recent libclang-dev).
CARGO_TEST_OPTIONS='--workspace'
;;
esac
- uses: taiki-e/install-action@v2
if: steps.vars.outputs.CARGO_CMD == 'cross' && matrix.job.target != 'x86_64-unknown-netbsd'
with:
tool: cross@0.2.5
- name: Install cross from git (NetBSD)
if: steps.vars.outputs.CARGO_CMD == 'cross' && matrix.job.target == 'x86_64-unknown-netbsd'
run: cargo install cross --git https://github.com/cross-rs/cross --rev f86fd03bb70b4c6802847c18087e21391498b0b4
- name: Create all needed build/work directories
shell: bash
run: |
## Create build/work space
mkdir -p '${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}'
- 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-*)
sudo apt-get -y update
sudo apt-get -y install gcc-aarch64-linux-gnu
;;
riscv64gc-unknown-linux-*)
sudo apt-get -y update
sudo apt-get -y install gcc-riscv64-linux-gnu
;;
*-redox*)
sudo apt-get -y update
sudo apt-get -y install fuse3 libfuse-dev
;;
esac
case '${{ matrix.job.os }}' in
macos-latest) brew install coreutils ;; # needed for testing
esac
case '${{ matrix.job.os }}' in
ubuntu-*)
# selinux and systemd headers needed to build tests
sudo apt-get -y update
sudo apt-get -y install libselinux1-dev libsystemd-dev
# pinky is a tool to show logged-in users from utmp, and gecos fields from /etc/passwd.
# In GitHub Action *nix VMs, no accounts log in, even the "runner" account that runs the commands, and "system boot" entry is missing.
# The account also has empty gecos fields.
# To work around these issues for pinky (and who) tests, we create a fake utmp file with a
# system boot entry and a login entry for the GH runner account.
FAKE_UTMP_2='[2] [00000] [~~ ] [reboot] [~ ] [6.0.0-test] [0.0.0.0] [2022-02-22T22:11:22,222222+00:00]'
FAKE_UTMP_7='[7] [999999] [tty2] [runner] [tty2] [ ] [0.0.0.0] [2022-02-22T22:22:22,222222+00:00]'
(echo "$FAKE_UTMP_2" ; echo "$FAKE_UTMP_7") | sudo utmpdump -r -o /var/run/utmp
# ... and add a full name to each account with a gecos field but no full name.
sudo sed -i 's/:,/:runner name,/' /etc/passwd
# We also create a couple optional files pinky looks for
touch /home/runner/.project
echo "foo" > /home/runner/.plan
# add user with digital username for testing with issue #7787
echo 200:x:2000:2000::/home/200:/bin/bash | sudo tee -a /etc/passwd
echo 200:x:2000: | sudo tee -a /etc/group
;;
esac
- uses: taiki-e/install-action@v2
if: steps.vars.outputs.CARGO_CMD == 'redoxer'
with:
tool: redoxer@0.2.56
- name: Initialize toolchain-dependent workflow variables
id: dep_vars
shell: bash
run: |
## Dependent VARs setup
outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; }
# * determine sub-crate utility list
UTILITY_LIST="$(./util/show-utils.sh ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }})"
echo UTILITY_LIST=${UTILITY_LIST}
CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo -n "-puu_${u} "; done;)"
outputs CARGO_UTILITY_LIST_OPTIONS
- 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 -V
# dependencies
echo "## dependency list"
cargo fetch --locked --quiet --target $(rustc --print host-tuple)
cargo tree --locked --target=${{ matrix.job.target }} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} ${{ steps.vars.outputs.CARGO_DEFAULT_FEATURES_OPTION }} --no-dedupe -e=no-dev --prefix=none | grep -vE "$PWD" | sort --unique
- name: Check
shell: bash
if: matrix.job.skip-publish != true && matrix.job.check-only == true
run: |
# expr breaks redox
if [[ "${{ matrix.job.target }}" == *"redox"* ]]; then sed -i.b '/"expr",/d' Cargo.toml; fi
${{ steps.vars.outputs.CARGO_CMD }} ${{ steps.vars.outputs.CARGO_CMD_OPTIONS }} check \
--target=${{ matrix.job.target }} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} ${{ steps.vars.outputs.CARGO_DEFAULT_FEATURES_OPTION }}
- name: Test
if: matrix.job.skip-tests != true && matrix.job.check-only != true
shell: bash
run: |
## Test
${{ steps.vars.outputs.CARGO_CMD }} ${{ steps.vars.outputs.CARGO_CMD_OPTIONS }} test --target=${{ matrix.job.target }} \
${{ steps.vars.outputs.CARGO_TEST_OPTIONS}} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} ${{ steps.vars.outputs.CARGO_DEFAULT_FEATURES_OPTION }} \
${{ steps.dep_vars.outputs.CARGO_UTILITY_LIST_OPTIONS }} -p coreutils
env:
RUST_BACKTRACE: "1"
- name: Build coreutils
shell: bash
if: matrix.job.skip-publish != true && matrix.job.check-only != true && matrix.job.target != 'x86_64-pc-windows-msvc'
run: |
## Build
${{ steps.vars.outputs.CARGO_CMD }} ${{ steps.vars.outputs.CARGO_CMD_OPTIONS }} build --release --config=profile.release.strip=true \
--target=${{ matrix.job.target }} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} ${{ steps.vars.outputs.CARGO_DEFAULT_FEATURES_OPTION }}
- name: Build individual binaries
if: matrix.job.skip-tests != true && matrix.job.target == 'x86_64-pc-windows-msvc'
shell: bash
run: |
${{ steps.vars.outputs.CARGO_CMD }} ${{ steps.vars.outputs.CARGO_CMD_OPTIONS }} build --release --config=profile.release.strip=true \
--target=${{ matrix.job.target }} ${{ steps.dep_vars.outputs.CARGO_UTILITY_LIST_OPTIONS }}
- name: Package
if: matrix.job.skip-publish != true && matrix.job.check-only != true
shell: bash
run: |
## Package artifact(s)
find target/${{ matrix.job.target }}/release -maxdepth 1 -type f \
\( -name "*.wasm" -o -name "*.exe" -o -perm -u+x \) \
-exec ln -v {} "${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}/" \;
# 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
- name: Package manpages and completions
if: matrix.job.target == 'x86_64-unknown-linux-gnu' && matrix.job.features == 'feat_os_unix,uudoc'
run: |
mkdir -p share/{man/man1,bash-completion/completions,fish/vendor_completions.d,zsh/site-functions,elvish/lib}
_uudoc=target/${{ matrix.job.target }}/release/uudoc
for bin in $('target/${{ matrix.job.target }}/release/coreutils' --list) coreutils;do
${_uudoc} manpage ${bin} > share/man/man1/${bin}.1
${_uudoc} completion ${bin} bash > share/bash-completion/completions/${bin}.bash
${_uudoc} completion ${bin} fish > share/fish/vendor_completions.d/${bin}.fish
${_uudoc} completion ${bin} zsh > share/zsh/site-functions/_${bin}
${_uudoc} completion ${bin} elvish > share/elvish/lib/${bin}.elv
done
rm share/zsh/site-functions/_[ # not supported
tar --zstd -cf docs.tar.zst share
- name: Publish
uses: softprops/action-gh-release@v2
if: steps.vars.outputs.DEPLOY && matrix.job.skip-publish != true && matrix.job.check-only != true
with:
draft: true
files: |
${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_NAME }}
docs.tar.zst
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Publish latest commit
uses: softprops/action-gh-release@v2
if: github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.job.skip-publish != true && matrix.job.check-only != true
with:
tag_name: latest-commit
body: |
commit: ${{ github.sha }}
draft: false
prerelease: true
files: |
${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_NAME }}
docs.tar.zst
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
coverage:
name: Code Coverage
runs-on: ${{ matrix.job.os }}
timeout-minutes: 90
env:
RUSTC_BOOTSTRAP: 1
strategy:
fail-fast: false
matrix:
job:
- { os: ubuntu-latest , features: unix }
steps:
- uses: actions/checkout@v6
- uses: taiki-e/install-action@v2
with:
tool: nextest,grcov@0.8.24
- uses: Swatinem/rust-cache@v2
- name: Run sccache-cache
id: sccache-setup
uses: mozilla-actions/sccache-action@v0.0.9
continue-on-error: true
- name: Export sccache
if: steps.sccache-setup.outcome == 'success'
run: |
echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV
echo "SCCACHE_GHA_ENABLED=true" >> $GITHUB_ENV
- name: Initialize workflow variables
id: vars
shell: bash
run: |
## VARs setup
outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; }
# * 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
# 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
case '${{ matrix.job.os }}' in
ubuntu-latest)
# selinux and systemd headers needed to build tests
sudo apt-get -y update
sudo apt-get -y install libselinux1-dev libsystemd-dev
# pinky is a tool to show logged-in users from utmp, and gecos fields from /etc/passwd.
# In GitHub Action *nix VMs, no accounts log in, even the "runner" account that runs the commands, and "system boot" entry is missing.
# The account also has empty gecos fields.
# To work around these issues for pinky (and who) tests, we create a fake utmp file with a
# system boot entry and a login entry for the GH runner account.
FAKE_UTMP_2='[2] [00000] [~~ ] [reboot] [~ ] [6.0.0-test] [0.0.0.0] [2022-02-22T22:11:22,222222+00:00]'
FAKE_UTMP_7='[7] [999999] [tty2] [runner] [tty2] [ ] [0.0.0.0] [2022-02-22T22:22:22,222222+00:00]'
(echo "$FAKE_UTMP_2" ; echo "$FAKE_UTMP_7") | sudo utmpdump -r -o /var/run/utmp
# ... and add a full name to each account with a gecos field but no full name.
sudo sed -i 's/:,/:runner name,/' /etc/passwd
# We also create a couple optional files pinky looks for
touch /home/runner/.project
echo "foo" > /home/runner/.plan
# add user with digital username for testing with issue #7787
echo 200:x:2000:2000::/home/200:/bin/bash | sudo tee -a /etc/passwd
echo 200:x:2000: | sudo tee -a /etc/group
;;
esac
## Install the llvm-tools component to get access to `llvm-profdata`
rustup component add llvm-tools
- name: Run test and coverage
id: run_test_cov
run: |
outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; }
# Run the coverage script
./util/build-run-test-coverage-linux.sh
outputs REPORT_FILE
env:
COVERAGE_DIR: ${{ github.workspace }}/coverage
FEATURES_OPTION: ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }}
- name: Upload coverage results (to Codecov.io)
uses: codecov/codecov-action@v6
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ${{ steps.run_test_cov.outputs.report }}
flags: ${{ steps.vars.outputs.CODECOV_FLAGS }}
name: codecov-umbrella
fail_ci_if_error: false
- name: Upload test results to Codecov
if: ${{ !cancelled() }}
uses: codecov/codecov-action@v6
with:
token: ${{ secrets.CODECOV_TOKEN }}
report_type: test_results
files: target/nextest/coverage/junit.xml
disable_search: true
flags: coverage,${{ matrix.job.os }}
fail_ci_if_error: false
test_selinux:
name: Build/SELinux
needs: [ min_version, deps ]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- name: Setup Lima
uses: lima-vm/lima-actions/setup@v1
id: lima-actions-setup
- name: Cache ~/.cache/lima
uses: actions/cache@v5
with:
path: ~/.cache/lima
key: lima-${{ steps.lima-actions-setup.outputs.version }}
- name: Start Fedora VM with SELinux
run: limactl start --plain --name=default --cpus=4 --disk=30 --memory=4 --network=lima:user-v2 template:fedora
- run: rsync -v -a -e ssh . lima-default:~/work/
- name: Setup Rust and other build deps in VM
run: |
lima sudo dnf install --nodocs gcc g++ git rustup libselinux-devel clang-devel attr -y
lima rustup-init -y --default-toolchain stable --profile minimal -c clippy
- name: Verify SELinux Status
run: |
lima getenforce
lima ls -laZ /etc/selinux
- name: Build and Test with SELinux
run: |
lima ls
lima bash -c "cd work && cargo test --features 'feat_selinux' --no-default-features"
- name: Lint with SELinux
run: lima bash -c "cd work && cargo clippy --all-targets --features 'feat_selinux' --no-default-features -- -D warnings"
test_selinux_stubs:
name: Build/SELinux-Stubs (Non-Linux)
needs: [ min_version, deps ]
runs-on: ${{ matrix.job.os }}
strategy:
fail-fast: false
matrix:
job:
- { os: macos-latest , features: feat_os_unix }
- { os: windows-latest , features: feat_os_windows }
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Build SELinux utilities as stubs
run: cargo build -p uu_chcon -p uu_runcon
- name: Verify stub binaries exist
shell: bash
run: |
test -f target/debug/chcon || test -f target/debug/chcon.exe
test -f target/debug/runcon || test -f target/debug/runcon.exe
- name: Verify workspace builds with stubs
run: cargo check --features ${{ matrix.job.features }}
test_safe_traversal:
name: Safe Traversal Security Check
runs-on: ubuntu-latest
needs: [ min_version, deps ]
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Install strace
run: sudo apt-get update && sudo apt-get install -y strace
- name: Build utilities with safe traversal
run: cargo build --profile=release-small -p uu_rm -p uu_chmod -p uu_chown -p uu_chgrp -p uu_mv -p uu_du
- name: Run safe traversal verification
run: ./util/check-safe-traversal.sh