git.delta.rocks / jrsonnet / refs/commits / e468723051e7

difftreelog

ci post results as a comment

lysuzqzyYaroslav Bolyukin2026-05-07parent: #b842704.patch.diff
in: master

2 files changed

modifiedflake.nixdiffbeforeafterboth
39 mkForce39 mkForce
40 optionals40 optionals
41 optionalAttrs41 optionalAttrs
42 concatMapStringsSep
42 ;43 ;
44 rel = system: inputs.self.legacyPackages.${system}.release;
45 releaseSections = [
46 {
47 name = "Linux (glibc)";
48 artifacts = [
49 {
50 label = "jrsonnet-x86_64-linux-glibc";
51 drv = (rel "x86_64-linux").jrsonnet-linux-glibc;
52 }
53 {
54 label = "jrsonnet-experimental-x86_64-linux-glibc";
55 drv = (rel "x86_64-linux").jrsonnet-experimental-linux-glibc;
56 }
57 {
58 label = "jrsonnet-aarch64-linux-glibc";
59 drv = (rel "aarch64-linux").jrsonnet-linux-glibc;
60 }
61 {
62 label = "jrsonnet-experimental-aarch64-linux-glibc";
63 drv = (rel "aarch64-linux").jrsonnet-experimental-linux-glibc;
64 }
65 {
66 label = "jrsonnet-i686-linux-glibc";
67 drv = (rel "i686-linux").jrsonnet-linux-glibc;
68 }
69 {
70 label = "jrsonnet-experimental-i686-linux-glibc";
71 drv = (rel "i686-linux").jrsonnet-experimental-linux-glibc;
72 }
73 {
74 label = "jrsonnet-armv7l-linux-glibc";
75 drv = (rel "armv7l-linux").jrsonnet-linux-glibc;
76 }
77 {
78 label = "jrsonnet-experimental-armv7l-linux-glibc";
79 drv = (rel "armv7l-linux").jrsonnet-experimental-linux-glibc;
80 }
81 ];
82 }
83 {
84 name = "Linux (musl)";
85 artifacts = [
86 {
87 label = "jrsonnet-x86_64-linux-musl";
88 drv = (rel "x86_64-linux").jrsonnet-linux-musl;
89 }
90 {
91 label = "jrsonnet-experimental-x86_64-linux-musl";
92 drv = (rel "x86_64-linux").jrsonnet-experimental-linux-musl;
93 }
94 {
95 label = "jrsonnet-aarch64-linux-musl";
96 drv = (rel "aarch64-linux").jrsonnet-linux-musl;
97 }
98 {
99 label = "jrsonnet-experimental-aarch64-linux-musl";
100 drv = (rel "aarch64-linux").jrsonnet-experimental-linux-musl;
101 }
102 ];
103 }
104 {
105 name = "macOS";
106 artifacts = [
107 {
108 label = "jrsonnet-aarch64-darwin";
109 drv = (rel "aarch64-linux").jrsonnet-darwin;
110 }
111 {
112 label = "jrsonnet-experimental-aarch64-darwin";
113 drv = (rel "aarch64-linux").jrsonnet-experimental-darwin;
114 }
115 ];
116 }
117 {
118 name = "Windows";
119 artifacts = [
120 {
121 label = "jrsonnet-x86_64-windows";
122 drv = (rel "x86_64-linux").jrsonnet-windows;
123 windows = true;
124 }
125 {
126 label = "jrsonnet-experimental-x86_64-windows";
127 drv = (rel "x86_64-linux").jrsonnet-experimental-windows;
128 windows = true;
129 }
130 ];
131 }
132 ];
133 releaseArtifacts = builtins.concatMap (s: s.artifacts) releaseSections;
43 in134 in
44 inputs.flake-parts.lib.mkFlake { inherit inputs; } {135 inputs.flake-parts.lib.mkFlake { inherit inputs; } {
45 imports = [136 imports = [
46 inputs.shelly.flakeModule137 inputs.shelly.flakeModule
47 inputs.hercules-ci-effects.flakeModule138 inputs.hercules-ci-effects.flakeModule
139 ./nix/post-comment.nix
48 ];140 ];
49 systems = [141 systems = [
50 "x86_64-linux"142 "x86_64-linux"
310 ]);402 ]);
311 };403 };
312 };404 };
313 hercules-ci.github-releases.files =405 hercules-ci.github-releases.files = map (a: {
314 let
315 rel = system: inputs.self.legacyPackages.${system}.release;
316 bin = drv: "${drv}/bin/jrsonnet";
317 exe = drv: "${drv}/bin/jrsonnet.exe";
318 in
319 [
320 {
321 label = "jrsonnet-x86_64-linux-musl";
322 path = bin (rel "x86_64-linux").jrsonnet-linux-musl;
323 }
324 {
325 label = "jrsonnet-experimental-x86_64-linux-musl";
326 path = bin (rel "x86_64-linux").jrsonnet-experimental-linux-musl;
327 }
328 {
329 label = "jrsonnet-aarch64-darwin";
330 path = bin (rel "aarch64-linux").jrsonnet-darwin;
331 }
332 {
333 label = "jrsonnet-experimental-aarch64-darwin";
334 path = bin (rel "aarch64-linux").jrsonnet-experimental-darwin;
335 }
336 {
337 label = "jrsonnet-x86_64-windows.exe";406 label = a.label + (if a.windows or false then ".exe" else "");
338 path = exe (rel "x86_64-linux").jrsonnet-windows;407 path = "${a.drv}/bin/jrsonnet${if a.windows or false then ".exe" else ""}";
339 }408 }) releaseArtifacts;
340 {
341 label = "jrsonnet-experimental-x86_64-windows.exe";
342 path = exe (rel "x86_64-linux").jrsonnet-experimental-windows;
343 }
344
345 {
346 label = "jrsonnet-aarch64-linux-musl";
347 path = bin (rel "aarch64-linux").jrsonnet-linux-musl;
348 }
349 {
350 label = "jrsonnet-experimental-aarch64-linux-musl";
351 path = bin (rel "aarch64-linux").jrsonnet-experimental-linux-musl;
352 }
353
354 {
355 label = "jrsonnet-x86_64-linux-glibc";
356 path = bin (rel "x86_64-linux").jrsonnet-linux-glibc;
357 }
358 {
359 label = "jrsonnet-experimental-x86_64-linux-glibc";
360 path = bin (rel "x86_64-linux").jrsonnet-experimental-linux-glibc;
361 }
362 {
363 label = "jrsonnet-aarch64-linux-glibc";
364 path = bin (rel "aarch64-linux").jrsonnet-linux-glibc;
365 }
366 {
367 label = "jrsonnet-experimental-aarch64-linux-glibc";
368 path = bin (rel "aarch64-linux").jrsonnet-experimental-linux-glibc;
369 }
370 {
371 label = "jrsonnet-i686-linux-glibc";
372 path = bin (rel "i686-linux").jrsonnet-linux-glibc;
373 }
374 {
375 label = "jrsonnet-experimental-i686-linux-glibc";
376 path = bin (rel "i686-linux").jrsonnet-experimental-linux-glibc;
377 }
378 {
379 label = "jrsonnet-armv7l-linux-glibc";
380 path = bin (rel "armv7l-linux").jrsonnet-linux-glibc;
381 }
382 {
383 label = "jrsonnet-experimental-armv7l-linux-glibc";
384 path = bin (rel "armv7l-linux").jrsonnet-experimental-linux-glibc;
385 }
386 ];
387 hercules-ci.cargo-publish = {409 hercules-ci.cargo-publish = {
388 enable = true;410 enable = true;
389 secretName = "crates-io";411 secretName = "crates-io";
398 dayOfWeek = [ "Sat" ];420 dayOfWeek = [ "Sat" ];
399 };421 };
400 };422 };
423 hercules-ci.post-comment = {
424 enable = true;
425 caches = [ "jrsonnet.cachix.org" ];
426 script =
427 let
428 benchmarks = inputs.self.legacyPackages.x86_64-linux.benchmarks.default;
429 renderSection = s: ''
430 echo
431 echo "### ${s.name}"
432 echo
433 ${concatMapStringsSep "\n" (a: ''echo "- [${a.label}]($(nixTar ${a.drv}))"'') s.artifacts}
434 '';
435 in
436 ''
437 {
438 echo "## Benchmark results"
439 echo
440 echo "[View rendered]($(nixRender ${benchmarks}))"
441 echo
442 echo "## Downloads"
443 ${concatMapStringsSep "\n" renderSection releaseSections}
444 } > $out
445 '';
446 };
401 herculesCI =447 herculesCI =
402 { lib, config, ... }:448 { lib, config, ... }:
403 {449 {
addednix/post-comment.nixdiffbeforeafterboth
--- /dev/null
+++ b/nix/post-comment.nix
@@ -0,0 +1,150 @@
+{
+  config,
+  lib,
+  withSystem,
+  ...
+}:
+let
+  inherit (lib)
+    mkOption
+    mkIf
+    types
+    concatStringsSep
+    ;
+  cfg = config.hercules-ci.post-comment;
+in
+{
+  options.hercules-ci.post-comment = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to post a GitHub commit comment for every commit Hercules CI runs on.
+      '';
+    };
+    script = mkOption {
+      type = types.lines;
+      description = ''
+        Bash snippet that writes the comment body to `$out`. Runs as part of the effect
+        (after secrets are loaded), so the helpers below are in scope:
+
+        - `nixTar <store-path>` — prints a signed deltarocks URL that streams the path
+          as a tar.zst, realised through the configured caches.
+        - `nixRender <store-path>` — prints a signed deltarocks URL that renders the
+          path's AsciiDoc content as HTML.
+      '';
+      example = lib.literalExpression ''
+        '''
+          {
+            echo "Render: $(nixRender ''${benchmarks})"
+            echo "Tar:    $(nixTar ''${binary})"
+          } > $out
+        '''
+      '';
+    };
+    system = mkOption {
+      type = types.str;
+      default = "x86_64-linux";
+      description = ''
+        System on which the effect runs.
+      '';
+    };
+    baseUrl = mkOption {
+      type = types.str;
+      default = "https://delta.rocks";
+      description = ''
+        Base URL of the deltarocks signing service.
+      '';
+    };
+    caches = mkOption {
+      type = types.listOf types.str;
+      default = [ ];
+      example = [ "jrsonnet.cachix.org" ];
+      description = ''
+        Cache hosts the signing service should use as substituters when realising the
+        signed store path.
+      '';
+    };
+    signSecret = mkOption {
+      type = types.str;
+      default = "deltarocks-nix-sign";
+      description = ''
+        Name of the Hercules CI agent secret that holds the deltarocks signing key.
+        Its `data` must have a field named `ogSecret`.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    herculesCI =
+      { config, ... }:
+      {
+        onPush.default.outputs.effects.post-comment = withSystem cfg.system (
+          { pkgs, hci-effects, ... }:
+          hci-effects.mkEffect {
+            name = "post-comment";
+            inputs = [ pkgs.openssl ];
+            secretsMap = {
+              token = {
+                type = "GitToken";
+              };
+              ogSecret = cfg.signSecret;
+            };
+            owner = config.repo.owner;
+            repoName = config.repo.name;
+            rev = config.repo.rev;
+            baseUrl = cfg.baseUrl;
+            caches = concatStringsSep " " cfg.caches;
+            effectScript = ''
+              set -euo pipefail
+
+              token=$(readSecretString token .token)
+              ogSecret=$(readSecretString ogSecret .ogSecret)
+              read -ra cacheArr <<<"$caches"
+              if [[ ''${#cacheArr[@]} -eq 0 ]]; then
+                echo "hercules-ci.post-comment: at least one cache host is required" >&2
+                exit 1
+              fi
+              sortedCaches=$(printf '%s\n' "''${cacheArr[@]}" | LC_ALL=C sort | paste -sd,)
+
+              _hmacHex() {
+                printf '%s' "$1" \
+                  | openssl dgst -sha256 -hmac "$ogSecret" -hex \
+                  | sed 's/^.*= //'
+              }
+
+              _uri() {
+                jq -nj --arg s "$1" '$s|@uri'
+              }
+
+              _signedUrl() {
+                local endpoint=$1 drv=$2
+                local sig
+                sig=$(_hmacHex "''${endpoint}:''${sortedCaches}:''${drv}")
+                local query=""
+                for c in "''${cacheArr[@]}"; do
+                  query+="cache=$(_uri "$c")&"
+                done
+                query+="drv=$(_uri "$drv")&sig=''${sig}"
+                printf '%s/%s?%s' "$baseUrl" "$endpoint" "$query"
+              }
+
+              nixTar() { _signedUrl nixTar "$1"; }
+              nixRender() { _signedUrl nixRender "$1"; }
+
+              out=$(mktemp)
+              ${cfg.script}
+
+              jq -n --rawfile content "$out" '{body: $content}' \
+                | curl -fsSL -X POST \
+                    -H "Authorization: Bearer $token" \
+                    -H "Accept: application/vnd.github+json" \
+                    -H "X-GitHub-Api-Version: 2022-11-28" \
+                    --data-binary @- \
+                    "https://api.github.com/repos/$owner/$repoName/commits/$rev/comments"
+            '';
+          }
+        );
+      };
+  };
+}