|
| 1 | +#!/usr/bin/env bash |
| 2 | +# |
| 3 | +# Mimic sameold's container-based CI jobs locally, without github. |
| 4 | +# Requires Linux and podman. |
| 5 | + |
| 6 | +REGISTRY="ghcr.io/cbs228/sameold/builder/" |
| 7 | + |
| 8 | +TARGETS=( |
| 9 | + x86_64-unknown-linux-gnu |
| 10 | + i686-unknown-linux-gnu |
| 11 | + armv7-unknown-linux-gnueabihf |
| 12 | + aarch64-unknown-linux-gnu |
| 13 | +) |
| 14 | + |
| 15 | +CONTAINER_TAG=latest |
| 16 | + |
| 17 | +# podman volumes to be removed on exit |
| 18 | +declare -a REMOVE_VOLUMES=() |
| 19 | + |
| 20 | +container_runner() { |
| 21 | + # Usage: container_runner |
| 22 | + # |
| 23 | + # Automatically detect container platform command |
| 24 | + |
| 25 | + if [ -x "${BUILDER:-}" ]; then |
| 26 | + echo "${BUILDER}" |
| 27 | + elif [ -n "${BUILDER:-}" ]; then |
| 28 | + command -v "${BUILDER}" |
| 29 | + else |
| 30 | + { |
| 31 | + command -v podman || \ |
| 32 | + command -v docker |
| 33 | + } 2>/dev/null || { |
| 34 | + echo >&2 "FATAL: container platform tools not found" |
| 35 | + return 1; |
| 36 | + } |
| 37 | + fi |
| 38 | +} |
| 39 | + |
| 40 | +is_any() { |
| 41 | + # Usage: is_any NEEDLE HAYSTACK0 HAYSTACK1 HAYSTACK2 ... |
| 42 | + # |
| 43 | + # Returns true if any of the haystacks match NEEDLE, exactly. |
| 44 | + |
| 45 | + local match="$1" |
| 46 | + shift |
| 47 | + local e |
| 48 | + for e; do |
| 49 | + [[ "$e" == "$match" ]] && return 0; |
| 50 | + done |
| 51 | + return 1 |
| 52 | +} |
| 53 | + |
| 54 | +make_volume() { |
| 55 | + # Usage: make_temporary_volume NAME TEMPORARY |
| 56 | + # |
| 57 | + # Create a podman volume with NAME. If TEMPORARY is set, the |
| 58 | + # volume will be destroyed when this script exits |
| 59 | + |
| 60 | + local vol |
| 61 | + vol="${1?expected NAME}" |
| 62 | + |
| 63 | + local is_temp |
| 64 | + is_temp="${2:-}" |
| 65 | + |
| 66 | + if [ -n "${is_temp:-}" ]; then |
| 67 | + "$BUILDER" >/dev/null 2>&1 volume rm "$vol" || true |
| 68 | + REMOVE_VOLUMES+=("$vol") |
| 69 | + fi |
| 70 | + |
| 71 | + "$BUILDER" >/dev/null 2>&1 volume create --ignore "$vol" |
| 72 | +} |
| 73 | + |
| 74 | +run() { |
| 75 | + # Usage: run CMD ARG0 ARG1 ... |
| 76 | + # |
| 77 | + # Echo and run |
| 78 | + |
| 79 | + echo >&2 "$@" |
| 80 | + exec "$@" |
| 81 | +} |
| 82 | + |
| 83 | +run_default_image_rw() { |
| 84 | + # Usage: run_default_image_rw CMD ARG1 ARG2 |
| 85 | + # |
| 86 | + # Run the given CMD in the native-architecture image, with |
| 87 | + # caches *read/write*. The image has the cargo registry cache |
| 88 | + # and the vendored sources volumes mounted read-write. |
| 89 | + |
| 90 | + run "$BUILDER" run --rm \ |
| 91 | + --init \ |
| 92 | + --security-opt=label:disable \ |
| 93 | + --userns=keep-id:uid=1001,gid=1001 \ |
| 94 | + --workdir /src \ |
| 95 | + -v .:/src:ro \ |
| 96 | + -v "sameold-target-$DEFAULT_TARGET":/src/target:rw \ |
| 97 | + -v sameold-vendored-sources:/src/vendor:rw \ |
| 98 | + -v sameold-cargo-cache:/cargo:rw \ |
| 99 | + -v sameold-cargo-config:/src/.cargo:rw \ |
| 100 | + "$DEFAULT_IMAGE" \ |
| 101 | + "$@" & |
| 102 | + |
| 103 | + wait -f "$!" |
| 104 | +} |
| 105 | + |
| 106 | +run_default_image_ro() { |
| 107 | + # Usage: run_default_image_ro CMD ARG1 ARG2 |
| 108 | + # |
| 109 | + # Run the given CMD in the native-architecture image, with |
| 110 | + # caches read-only. The image has the vendored sources volume |
| 111 | + # mounted read-only and the offline flag set. |
| 112 | + |
| 113 | + run "$BUILDER" run --rm \ |
| 114 | + --init \ |
| 115 | + --security-opt=label:disable \ |
| 116 | + --userns=keep-id:uid=1001,gid=1001 \ |
| 117 | + --workdir /src \ |
| 118 | + -e CARGO_NET_OFFLINE=true \ |
| 119 | + -e RUSTFLAGS='-C strip=symbols' \ |
| 120 | + -v .:/src:ro \ |
| 121 | + -v "sameold-target-$DEFAULT_TARGET":/src/target:rw \ |
| 122 | + -v sameold-vendored-sources:/src/vendor:ro \ |
| 123 | + -v sameold-cargo-config:/src/.cargo:ro \ |
| 124 | + "$DEFAULT_IMAGE" \ |
| 125 | + "$@" & |
| 126 | + |
| 127 | + wait -f "$!" |
| 128 | +} |
| 129 | + |
| 130 | +run_all_images() { |
| 131 | + # Usage: run_all_images CMD ARG1 ARG2 |
| 132 | + # |
| 133 | + # Runs the given CMD on all images, in parallel with |
| 134 | + # GNU parallel. The image has the vendored sources volume |
| 135 | + # mounted read-only and the offline flag set. |
| 136 | + |
| 137 | + run parallel -i "$BUILDER" run --rm \ |
| 138 | + --init \ |
| 139 | + --security-opt=label:disable \ |
| 140 | + --userns=keep-id:uid=1001,gid=1001 \ |
| 141 | + --workdir /src \ |
| 142 | + -e CARGO_NET_OFFLINE=true \ |
| 143 | + -e RUSTFLAGS='-C strip=symbols' \ |
| 144 | + -v .:/src:ro \ |
| 145 | + -v 'sameold-target-{}:/src/target:rw' \ |
| 146 | + -v sameold-vendored-sources:/src/vendor:ro \ |
| 147 | + -v sameold-cargo-config:/src/.cargo:ro \ |
| 148 | + "${REGISTRY}"'{}'":$CONTAINER_TAG" \ |
| 149 | + "$@" \ |
| 150 | + -- \ |
| 151 | + "${TARGETS[@]}" & |
| 152 | + |
| 153 | + wait -f "$!" || return 1 |
| 154 | + |
| 155 | + echo >&2 |
| 156 | + echo >&2 "All targets passed:" |
| 157 | + printf >&2 ' ✔ %s\n' "${TARGETS[@]}" |
| 158 | +} |
| 159 | + |
| 160 | +cleanup_exit() { |
| 161 | + # Actions executed when the script exits, either error or not |
| 162 | + trap '' ERR EXIT |
| 163 | + |
| 164 | + # Kill all background jobs |
| 165 | + kill $(jobs -p) 2>/dev/null || true |
| 166 | + wait |
| 167 | + |
| 168 | + # remove volumes |
| 169 | + local vol |
| 170 | + for vol in "${REMOVE_VOLUMES[@]}"; do |
| 171 | + [ -n "${vol:-}" ] || continue |
| 172 | + "$BUILDER" >/dev/null 2>&1 volume rm "$vol" || true |
| 173 | + done |
| 174 | + REMOVE_VOLUMES=() |
| 175 | + |
| 176 | + echo >&2 "Cleanup finished." |
| 177 | +} |
| 178 | + |
| 179 | +BUILDER="$(container_runner)" |
| 180 | +DEFAULT_TARGET="$(uname -m)-unknown-linux-gnu" |
| 181 | + |
| 182 | +# compute full image names |
| 183 | +DEFAULT_IMAGE="${REGISTRY}${DEFAULT_TARGET}:${CONTAINER_TAG}" |
| 184 | + |
| 185 | +# git version → source date epoch, if possible |
| 186 | +SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-$(git log -1 --pretty=%ct || date +%s)}" |
| 187 | + |
| 188 | +# return if sourced |
| 189 | +(return 0 2>/dev/null) && return 0 |
| 190 | + |
| 191 | +set -euo pipefail |
| 192 | + |
| 193 | +if ! is_any "$DEFAULT_TARGET" "${TARGETS[@]}"; then |
| 194 | + echo >&2 "error: expected image \"$DEFAULT_TARGET\" for native architecture," |
| 195 | + echo >&2 "but none exists." |
| 196 | + exit 1 |
| 197 | +fi |
| 198 | + |
| 199 | +# install cleanup hooks |
| 200 | +trap cleanup_exit ERR EXIT |
| 201 | + |
| 202 | +# 0. CHECK FOR WHITESPACE ERRORS |
| 203 | +git diff --check develop.. |
| 204 | + |
| 205 | +# make volume for cargo registry (cached across runs) |
| 206 | +make_volume sameold-cargo-cache |
| 207 | + |
| 208 | +# make volume for vendored sources (cached across runs) |
| 209 | +make_volume sameold-vendored-sources |
| 210 | + |
| 211 | +# make volume for cargo config (destroyed on exit) |
| 212 | +make_volume sameold-cargo-config temporary |
| 213 | + |
| 214 | +# make volumes for built binaries (destroyed on exit, shared) |
| 215 | +for target in "${TARGETS[@]}"; do |
| 216 | + make_volume sameold-target-"$target" temporary |
| 217 | +done |
| 218 | + |
| 219 | +# 1. VENDOR SOURCES |
| 220 | +# |
| 221 | +# Vendor sources, updating our cargo cache of crates.io |
| 222 | +# while we do it. Also audit all dependencies. |
| 223 | + |
| 224 | +{ cargo_vendor=$(cat) ; } <<'SCRIPT' |
| 225 | +cargo vendor --versioned-dirs --locked >.cargo/config.toml |
| 226 | +SCRIPT |
| 227 | + |
| 228 | +run_default_image_rw bash -c "$cargo_vendor" |
| 229 | + |
| 230 | + |
| 231 | +# 2. RUN LINTS |
| 232 | +# |
| 233 | +# Check that the code is cargo-fmt compliant, lints OK, and |
| 234 | +# builds documentation without warnings |
| 235 | +{ cargo_check=$(cat) ; } <<'SCRIPT' |
| 236 | +cargo fmt --check && |
| 237 | +cargo check --frozen --locked --workspace && |
| 238 | +cargo doc --frozen --locked --no-deps --workspace |
| 239 | +SCRIPT |
| 240 | + |
| 241 | +run_default_image_ro bash -c "$cargo_check" |
| 242 | + |
| 243 | + |
| 244 | +# 3. RUN ALL BUILDS on all architectures |
| 245 | +# |
| 246 | +# Test in debug-mode |
| 247 | +# Test in release-mode |
| 248 | +# Build and install in release-mode |
| 249 | +# Run integration tests |
| 250 | +{ cargo_build=$(cat) ; } <<'SCRIPT' |
| 251 | +cargo test --offline --frozen --workspace && |
| 252 | +cargo test --offline --frozen --release --workspace && |
| 253 | +cargo install --offline --frozen --path=crates/samedec && |
| 254 | +qemu-run-maybe samedec --version && |
| 255 | +./sample/test.sh qemu-run-maybe samedec && |
| 256 | +echo >&2 "✔ OK $CARGO_BUILD_TARGET" |
| 257 | +SCRIPT |
| 258 | + |
| 259 | +run_all_images bash -c "$cargo_build" |
0 commit comments