Skip to content

Commit

Permalink
ci(gh-actions): Generate CI matrix based on the cache status
Browse files Browse the repository at this point in the history
  • Loading branch information
PetarKirov committed Feb 13, 2024
1 parent 5ada090 commit d2dd023
Show file tree
Hide file tree
Showing 7 changed files with 317 additions and 15 deletions.
45 changes: 45 additions & 0 deletions .github/generate-matrix/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Evaluate CI Matrix
description: Evaluate all packages, check cache status generate CI matrix and publish comment with the summary

inputs:
is-initial:
description: Is this the start of the CI workflow, or the end
default: 'true'
required: true
cachix-cache:
description: The name of the cachix cache to use
required: true
outputs:
matrix:
description: 'Generated Matrix'
value: ${{ steps.generate-matrix.outputs.matrix }}
comment:
description: 'Comment'
value: ${{ steps.generate-matrix.outputs.comment }}

runs:
using: "composite"
steps:
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@v9
with:
extra-conf: accept-flake-config = true

- name: Generate CI Matrix
id: generate-matrix
shell: bash
env:
IS_INITIAL: ${{ inputs.is-initial }}
CACHIX_CACHE: ${{ inputs.cachix-cache }}
run: nix develop .#ci -c ./scripts/ci-matrix.sh

- name: Upload CI Matrix
uses: actions/upload-artifact@v4
with:
name: matrix-${{ inputs.is-initial == 'true' && 'pre' || 'post' }}.json
path: matrix-${{ inputs.is-initial == 'true' && 'pre' || 'post' }}.json

- name: Update GitHub Comment
uses: marocchino/sticky-pull-request-comment@v2.9.0
with:
path: comment.md
44 changes: 29 additions & 15 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,24 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Install Nix
uses: DeterminateSystems/nix-installer-action@v9
- name: Post initial package status comment
uses: marocchino/sticky-pull-request-comment@v2.9.0
with:
recreate: true
message: |
Thanks for your Pull Request!
- id: generate-matrix
name: Generate Nix Matrix
run: |
set -Eeu
matrix="$(nix eval --json '.#lib.mkGHActionsMatrix')"
echo "matrix=$matrix" >> "$GITHUB_OUTPUT"
This comment will be updated automatically with the status of each package.
- name: Generate CI Matrix
id: generate-matrix
uses: ./.github/generate-matrix
with:
is-initial: 'true'
cachix-cache: ${{ vars.CACHIX_CACHE }}
outputs:
matrix: ${{ steps.generate-matrix.outputs.matrix }}
comment: ${{ steps.generate-matrix.outputs.comment }}

build:
needs: generate-matrix
Expand All @@ -54,17 +61,24 @@ jobs:
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}

- name: Build ${{ matrix.package }}
run: nix build -L --json --no-link '.#${{ matrix.attrPath }}'
run: nix build -L --json --no-link '.#packages.${{ matrix.attrPath }}'

results:
if: ${{ always() }}
runs-on: ubuntu-latest
name: Final Results
needs: [build]
needs: [build, generate-matrix]
steps:
- uses: actions/checkout@v4

- name: Generate Matrix
uses: ./.github/generate-matrix
with:
is-initial: 'false'
cachix-cache: ${{ vars.CACHIX_CACHE }}

- run: exit 1
if: >-
${{
contains(needs.*.result, 'failure')
|| contains(needs.*.result, 'cancelled')
}}
${{ fromJSON(needs.generate-matrix.outputs.matrix).include.length > 0 &&
(contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')) }}
- run: exit 0
if: ${{fromJSON(needs.generate-matrix.outputs.matrix).include.length == 0}}
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Nix build output
.result
result
comment.md
matrix-pre.json
matrix-post.json

# direnv temporary files
.direnv/
Expand Down
12 changes: 12 additions & 0 deletions lib/mk-gh-actions-matrix.nix
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@

inherit (import ./build-status.nix {inherit lib;}) getBuildStatus;

allowedToFailMap = lib.pipe (mkGHActionsMatrix.include) [
(builtins.groupBy (p: p.package))
(builtins.mapAttrs (
n: v:
builtins.mapAttrs (
s: ps:
(builtins.head ps).allowedToFail
)
(builtins.groupBy (p: p.system) v)
))
];

mkGHActionsMatrix = {
include = lib.pipe (builtins.attrNames nixSystemToGHPlatform) [
(builtins.concatMap
Expand Down
105 changes: 105 additions & 0 deletions scripts/ci-matrix.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#!/usr/bin/env bash

set -euo pipefail

root_dir="$(git rev-parse --show-toplevel)"

# shellcheck source=./nix-eval-jobs.sh
source "$root_dir/scripts/nix-eval-jobs.sh"

eval_packages_to_json() {
flake_attr_pre="${1:-packages}"
flake_attr_post="${2:-}"

cachix_url="https://${CACHIX_CACHE}.cachix.org"

nix eval --json .#lib.allowedToFailMap > "${result_dir}/allowed-to-fail.json"

nix_eval_for_all_systems "$flake_attr_pre" "$flake_attr_post" \
| cat "${result_dir}/allowed-to-fail.json" - \
| jq -sr '
.[0] as $allowed_to_fail
| .[1:] as $nix_eval_results
| {
"x86_64-linux": "ubuntu-latest",
"x86_64-darwin": "macos-14",
"aarch64-darwin": "macos-14"
} as $system_to_gh_platform
| $nix_eval_results
| map({
package: .attr,
attrPath: "\(.system).\(.attr)",
allowedToFail: $allowed_to_fail[.attr][.system],
isCached,
system,
cache_url: .outputs.out
| "'"$cachix_url"'/\(match("^\/nix\/store\/([^-]+)-").captures[0].string).narinfo",
os: $system_to_gh_platform[.system]
})
| sort_by(.package | ascii_downcase)
'
}

save_gh_ci_matrix() {
packages_to_build=$(echo "$packages" | jq -c '. | map(select((.isCached | not) and (.allowedToFail | not)))')
matrix='{"include":'"$packages_to_build"'}'
res_path=''
if [ "${IS_INITIAL:-true}" = "true" ]; then
res_path='matrix-pre.json'
else
res_path='matrix-post.json'
fi
echo "$matrix" > "$res_path"
echo "matrix=$matrix" >> "${GITHUB_OUTPUT:-${result_dir}/gh-output.env}"
}

convert_nix_eval_to_table_summary_json() {
is_initial="${IS_INITIAL:-true}"
echo "$packages" \
| jq '
def getStatus(pkg; key):
if (pkg | has(key))
then if pkg[key].isCached
then "[✅ cached](\(pkg[key].cache_url))"
else if "'$is_initial'" == "true"
then "⏳ building..."
else "❌ build failed" end
end else "🚫 not supported" end;
group_by(.package)
| map(
. | INDEX(.system) as $pkg
| .[0].package as $name
| {
package: $name,
"x86_64-linux": getStatus($pkg; "x86_64-linux"),
"x86_64-darwin": getStatus($pkg; "x86_64-darwin"),
"aarch64-darwin": getStatus($pkg; "aarch64-darwin"),
}
)
| sort_by(.package)'
}

printTableForCacheStatus() {
create_result_dirs
packages="$(eval_packages_to_json "$@")"

save_gh_ci_matrix

{
echo "Thanks for your Pull Request!"
echo
echo "Below you will find a summary of the cachix status of each package, for each supported platform."
echo
# shellcheck disable=SC2016
echo '| package | `x86_64-linux` | `x86_64-darwin` | `aarch64-darwin` |'
echo '| ------- | -------------- | --------------- | ---------------- |'
convert_nix_eval_to_table_summary_json | jq -r '
.[] | "| `\(.package)` | \(.["x86_64-linux"]) | \(.["x86_64-darwin"]) | \(.["aarch64-darwin"]) |"
'
echo
} > comment.md
}

printTableForCacheStatus "$@"

53 changes: 53 additions & 0 deletions scripts/nix-eval-jobs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/usr/bin/env bash

set -euo pipefail

root_dir="$(git rev-parse --show-toplevel)"
result_dir="$root_dir/.result"
gc_roots_dir="$result_dir/gc-roots"

# shellcheck source=./system-info.sh
source "$root_dir/scripts/system-info.sh"

create_result_dirs() {
mkdir -p "$result_dir" "$gc_roots_dir"
}

nix_eval_jobs() {
flake_attr="$1"

get_platform
get_available_memory_mb
get_nix_eval_worker_count

{
(
set -x
nix-eval-jobs \
--quiet \
--option warn-dirty false \
--check-cache-status \
--gc-roots-dir "$gc_roots_dir" \
--workers "$max_workers" \
--max-memory-size "$max_memory_mb" \
--flake "$root_dir#$flake_attr"
) \
| tee /dev/fd/3 \
| stdbuf -i0 -o0 -e0 jq --color-output -c '{ attr, isCached, out: .outputs.out }' > /dev/stderr
} 3>&1 2> >(
grep -vP "(SQLite database|warning: unknown setting 'allowed-users'|warning: unknown setting 'trusted-users')" \
>&2
)
}

nix_eval_for_all_systems() {
flake_pre="$1"
flake_post="${2:+.$2}"

systems=( {x86_64-linux,{x86_64,aarch64}-darwin} )

for system in "${systems[@]}"; do
nix_eval_jobs "${flake_pre}.${system}${flake_post}"
done
}

69 changes: 69 additions & 0 deletions scripts/system-info.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/usr/bin/env bash

set -euo pipefail

get_platform() {
case "$(uname -s).$(uname -m)" in
Linux.x86_64)
export system=x86_64-linux
export is_linux=true
export is_darwin=false
;;
Linux.i?86)
export system=i686-linux
export is_linux=true
export is_darwin=false
;;
Linux.aarch64)
export system=aarch64-linux
export is_linux=true
export is_darwin=false
;;
Linux.armv6l_linux)
export system=armv6l-linux
export is_linux=true
export is_darwin=false
;;
Linux.armv7l_linux)
export system=armv7l-linux
export is_linux=true
export is_darwin=false
;;
Darwin.x86_64)
export system=x86_64-darwin
export is_linux=false
export is_darwin=true
;;
Darwin.arm64|Darwin.aarch64)
system=aarch64-darwin
export is_linux=false
export is_darwin=true
;;
*) error "sorry, there is no binary distribution of Nix for your platform";;
esac
}

get_nix_eval_worker_count() {
if [[ -z "${MAX_WORKERS:-}" ]]; then
available_parallelism="$(nproc)"
export max_workers="$((available_parallelism < 8 ? available_parallelism : 8))"
else
export max_workers="$MAX_WORKERS"
fi
}

get_available_memory_mb() {
if [ "$is_darwin" = 'true' ]; then
free_pages="$(vm_stat | grep 'Pages free:' | tr -s ' ' | cut -d ' ' -f 3 | tr -d '.')"
inactive_pages="$(vm_stat | grep 'Pages inactive:' | tr -s ' ' | cut -d ' ' -f 3 | tr -d '.')"
pages="$((free_pages + inactive_pages))"
page_size="$(pagesize)"
export max_memory_mb="${MAX_MEMORY:-$(((pages * page_size) / 1024 / 1024 ))}"
else
free="$(< /proc/meminfo grep MemFree | tr -s ' ' | cut -d ' ' -f 2)"
cached="$(< /proc/meminfo grep Cached | grep -v SwapCached | tr -s ' ' | cut -d ' ' -f 2)"
buffers="$(< /proc/meminfo grep Buffers | tr -s ' ' | cut -d ' ' -f 2)"
shmem="$(< /proc/meminfo grep Shmem: | tr -s ' ' | cut -d ' ' -f 2)"
export max_memory_mb="${MAX_MEMORY:-$(((free + cached + buffers + shmem) / 1024 ))}"
fi
}

0 comments on commit d2dd023

Please sign in to comment.