diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 416c246..36d9200 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -5,7 +5,7 @@ on:
- main
jobs:
- check:
+ build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -14,3 +14,23 @@ jobs:
- name: build
run: nix-build -A ci
+
+ test-update:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: cachix/install-nix-action@v26
+
+ - name: test update script
+ run: |
+ nix-build -A autoPrUpdate
+ {
+ result/bin/auto-pr-update .
+ echo ""
+ echo '```diff'
+ git diff
+ echo '```'
+ } > $GITHUB_STEP_SUMMARY
+ env:
+ GH_TOKEN: ${{ github.token }}
diff --git a/.github/workflows/update.yml b/.github/workflows/update.yml
index 2e74f74..02fd83a 100644
--- a/.github/workflows/update.yml
+++ b/.github/workflows/update.yml
@@ -18,6 +18,8 @@ jobs:
run: |
nix-build repo -A autoPrUpdate
result/bin/auto-pr-update repo > body
+ env:
+ GH_TOKEN: ${{ github.token }}
- name: Create Pull Request
uses: peter-evans/create-pull-request@v6
diff --git a/default.nix b/default.nix
index 0004bf5..19747ab 100644
--- a/default.nix
+++ b/default.nix
@@ -11,6 +11,7 @@ let
config = {};
overlays = [];
};
+ inherit (pkgs) lib;
runtimeExprPath = ./src/eval.nix;
testNixpkgsPath = ./tests/mock-nixpkgs.nix;
@@ -32,8 +33,12 @@ let
results = {
- build = pkgs.callPackage ./package.nix {
- inherit nixpkgsLibPath initNix runtimeExprPath testNixpkgsPath;
+ # We're using this value as the root result. By default, derivations expose all of their
+ # internal attributes, which is very messy. We prevent this using lib.lazyDerivation
+ build = lib.lazyDerivation {
+ derivation = pkgs.callPackage ./package.nix {
+ inherit nixpkgsLibPath initNix runtimeExprPath testNixpkgsPath;
+ };
};
shell = pkgs.mkShell {
@@ -48,31 +53,61 @@ let
};
# Run regularly by CI and turned into a PR
- autoPrUpdate = pkgs.writeShellApplication {
- name = "auto-pr-update";
- runtimeInputs = with pkgs; [
- npins
- cargo
- ];
- text =
- let
- commands = {
- "npins changes" = ''
- npins update --directory "$REPO_ROOT/npins"'';
- "cargo changes" = ''
- cargo update --manifest-path "$REPO_ROOT/Cargo.toml"'';
+ autoPrUpdate =
+ let
+ updateScripts = {
+ npins = pkgs.writeShellApplication {
+ name = "update-npins";
+ runtimeInputs = with pkgs; [
+ npins
+ ];
+ text = ''
+ echo "npins changes
"
+ # Needed because GitHub's rendering of the first body line breaks down otherwise
+ echo ""
+ echo '```'
+ npins update --directory "$1/npins" 2>&1
+ echo '```'
+ echo " "
+ '';
};
- in
- ''
- REPO_ROOT=$1
- echo "Run automated updates"
- ''
- + pkgs.lib.concatStrings (pkgs.lib.mapAttrsToList (title: command: ''
- echo -e '${title}
\n\n```'
- ${command} 2>&1
- echo -e '```\n '
- '') commands);
- };
+ cargo = pkgs.writeShellApplication {
+ name = "update-cargo";
+ runtimeInputs = with pkgs; [
+ cargo
+ ];
+ text = ''
+ echo "cargo changes
"
+ # Needed because GitHub's rendering of the first body line breaks down otherwise
+ echo ""
+ echo '```'
+ cargo update --manifest-path "$1/Cargo.toml" 2>&1
+ echo '```'
+ echo " "
+ '';
+ };
+ githubActions = pkgs.writeShellApplication {
+ name = "update-github-actions";
+ runtimeInputs = with pkgs; [
+ dependabot-cli
+ jq
+ github-cli
+ ];
+ text = builtins.readFile ./scripts/update-github-actions.sh;
+ };
+ };
+ in
+ pkgs.writeShellApplication {
+ name = "auto-pr-update";
+ text = ''
+ # Prevent impurities
+ unset PATH
+ ${lib.concatMapStringsSep "\n" (script: ''
+ echo >&2 "Running ${script}"
+ ${lib.getExe script} "$1"
+ '') (lib.attrValues updateScripts)}
+ '';
+ };
# Tests the tool on the pinned Nixpkgs tree, this is a good sanity check
nixpkgsCheck = pkgs.runCommand "test-nixpkgs-check-by-name" {
diff --git a/scripts/update-github-actions.sh b/scripts/update-github-actions.sh
new file mode 100755
index 0000000..b8ec5e5
--- /dev/null
+++ b/scripts/update-github-actions.sh
@@ -0,0 +1,56 @@
+#!/usr/bin/env bash
+
+# This script calls the dependabot CLI (https://github.com/dependabot/cli)
+# to determine updates to GitHub Action dependencies in the local repository.
+# It then also applies the updates and outputs the results to standard output.
+
+set -euo pipefail
+
+REPO_ROOT=$1
+
+echo -e "GitHub Action updates
\n\n"
+
+# CI sets the GH_TOKEN env var, which `gh auth token` defaults to if set
+githubToken=$(gh auth token)
+
+# Each dependabot update call tries to update all dependencies,
+# but it outputs individual results for each dependency,
+# with the intention of creating a PR for each.
+#
+# We want to have all changes together though,
+# so we'd need to merge updates of the same files together,
+# which could cause merge conflicts, no good.
+#
+# Instead, we run dependabot repeatedly,
+# each time only taking the first dependency update and updating the files with it,
+# such that the next iteration takes into account the previous updates.
+# We do this until there's no more dependencies to be updated,
+# at which point --exit-status will make jq return with a non-zero exit code.
+#
+# This does mean that dependabot internally needs to perform O(n^2) updates,
+# but this isn't a problem in practice, since we run these updates regularly,
+# so n is low.
+while
+ # Unused argument would be the remote GitHub repo, which is not used if we pass --local
+ create_pull_request=$(LOCAL_GITHUB_ACCESS_TOKEN="$githubToken" \
+ dependabot update github_actions this-argument-is-unused --local "$REPO_ROOT" \
+ | jq --exit-status --compact-output --slurp 'map(select(.type == "create_pull_request")) | .[0].data')
+do
+ title=$(jq --exit-status --raw-output '."pr-title"' <<< "$create_pull_request")
+ echo "$title
"
+
+ # Needed because GitHub's rendering of the first body line breaks down otherwise
+ echo ""
+
+ jq --exit-status --raw-output '."pr-body"' <<< "$create_pull_request"
+ echo ' '
+
+ jq --compact-output '."updated-dependency-files"[]' <<< "$create_pull_request" \
+ | while read -r fileUpdate; do
+ file=$(jq --exit-status --raw-output '.name' <<< "$fileUpdate")
+ # --join-output makes sure to not output a trailing newline
+ jq --exit-status --raw-output --join-output '.content' <<< "$fileUpdate" > "$REPO_ROOT/$file"
+ done
+done
+
+echo -e " "