Compare commits

..
Author SHA1 Message Date
eldritch horrors 24b375099d update nixpkgs, lix pins 2025-10-10 15:13:44 +02:00
eldritch horrors d761f9942f fail if any non-final build is not passed
the buildkite docs seem to be incomplete. `timed_out` is also a valid
failure state, but this isn't mentioned anywhere in the rest api docs
2025-10-10 15:10:53 +02:00
Raito Bezarius 3d0263176a pipeline: add exec
The current version uses `exec` instead of running this as a script.

Change-Id: Iba99753ba556b1713e73255de49b4a5c54867cd5
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-08-21 17:33:20 +02:00
eldritch horrorsandGerrit Code Review 2aa832134a Merge changes into ci-config 2025-02-23 17:18:32 +00:00
eldritch horrors 638b3fa23e hook up status reporting
Change-Id: I90dfc9a1da09431c8ef8807272d50ae4239affc9
2025-02-23 18:15:14 +01:00
eldritch horrors 847478da61 hook up aarch64-darwin
Change-Id: I5943ab756a9e3a9bd7faab6e00cff7351f9a1183
2025-02-23 16:40:23 +01:00
eldritch horrors 0a5650647c hic sunt dracones 2025-02-18 22:39:58 +01:00
5 changed files with 489 additions and 1 deletions
+173
View File
@@ -0,0 +1,173 @@
import subprocess
import itertools
import json
import typing
import graphlib
import textwrap
import sys
import logging
import json
logging.basicConfig(format="[%(asctime)s] %(message)s", level=logging.INFO)
log = logging.getLogger(__name__)
SYSTEMS = {
'x86_64-linux',
'aarch64-linux',
'aarch64-darwin',
# disabled due to low utility
# 'i686-linux',
# disabled due to low capacity
# 'x86_64-darwin',
}
EVAL_WORKERS = 4
EVAL_MEMORY = 4096
class Job(typing.NamedTuple):
drv: str
name: str
system: str
path: list[str]
expensive: bool
def __hash__(self):
return hash(self.drv)
def __eq__(self, other):
return self.drv == other.drv
def group(self):
return self.path[0]
def drv_hash(self):
return self.drv.rpartition("/")[2].partition("-")[0]
class EvalFailure(Exception):
errors: list[str]
def __init__(self, errors):
self.errors = errors
def get_jobs() -> list[Job]:
nej = subprocess.Popen(
["nix-eval-jobs",
"--flake", ".#hydraJobs",
"--option", "eval-cache", "false",
"--meta",
"--quiet",
"--workers", str(EVAL_WORKERS),
"--max-memory-size", str(EVAL_MEMORY),
"--show-trace",
"--force-recurse",
],
stdout=subprocess.PIPE
)
drvs = []
errors = []
while line := nej.stdout.readline():
item = json.loads(line)
log.info("found %s", item['attr'])
if 'error' in item:
errors.append(item)
elif item['system'] in SYSTEMS:
drvs.append(item)
if nej.wait(60) != 0:
raise subprocess.CalledProcessError(nej.returncode, nej.args)
elif errors:
raise EvalFailure(errors)
return [
# we use "has a mainProgram" as the marker for expensive builds.
# at time of writing this matches only lix builds, not n-e-j etc
Job(drv=j['drvPath'], name=j['attr'], system=j['system'], path=j['attrPath'],
expensive=j.get('meta', {}).get('mainProgram'))
for j in drvs
]
def get_dependencies(jobs: list[Job]) -> dict[Job, set[Job]]:
log.info("getting derivation infos")
by_drv = { j.drv: j for j in jobs }
drv_info = json.loads(subprocess.run(
["nix", "derivation", "show", "-r", *by_drv.keys()],
capture_output=True,
check=True
).stdout.decode())
log.info("calculating dependencies")
deps = { drv: set(info["inputDrvs"].keys()) for drv, info in drv_info.items() }
for n in graphlib.TopologicalSorter(deps).static_order():
for d in tuple(deps[n]):
deps[n].update(deps[d])
return { j: set(( by_drv[d] for d in deps[j.drv] & by_drv.keys() )) for j in jobs }
try:
# make unique based on drv hash to avoid errors when multiple attrs
# point to the same derivations. sort by attrpath for stable order.
jobs = [
tuple(js[1])[0]
for all_jobs in [ get_jobs() ]
for sorted_jobs in [ sorted(all_jobs, key=lambda j: (j.drv, j.path)) ]
for js in itertools.groupby(sorted_jobs, lambda j: j.drv)
]
deps = get_dependencies(jobs)
commands = [
{
'group': group,
'steps': [
{
'command': f'nix build --no-link -L ".#hydraJobs.{job.name}"',
'label': ".".join(job.path),
'key': job.drv_hash(),
'depends_on': [ d.drv_hash() for d in deps[job] ],
'agents': {
'queue': job.system,
('large' if job.expensive else 'small'): True,
},
}
for job in items
],
}
for group, items in itertools.groupby(sorted(jobs, key=Job.group), Job.group)
] + [
{
'wait': '~',
'continue_on_failure': True,
},
{
# Exit with success or failure depending on whether any other steps
# failed (but not retried).
#
# This information is checked by querying the Buildkite REST API
# and counting all non-`passed` steps.
#
# This step must be :ice_cream: (yes, really!) because the post-command
# hook will inspect this name.
#
# Note that this step has requirements for the agent environment:
#
# * curl and jq must be on the $PATH of build agents (enforced via nixos config)
# * a buildkite secret named `build_status_token` with `read_builds` access must exist
'label': ':ice_cream:',
'command': textwrap.dedent("""
set -ueo pipefail
readonly FAILED_JOBS=$(curl --silent \
-H "Authorization: Bearer $(buildkite-agent secret get build_status_token)" \
"https://api.buildkite.com/v2/organizations/$${BUILDKITE_ORGANIZATION_SLUG}/pipelines/$${BUILDKITE_PIPELINE_SLUG}/builds/$${BUILDKITE_BUILD_NUMBER}" \
| jq '.jobs | map(select(has("state") and .state != "passed" and .name != ":ice_cream:")) | length')
echo "$$FAILED_JOBS build jobs failed."
(( $$FAILED_JOBS == 0 ))
"""),
}
]
print(json.dumps({'steps': commands}, indent=2))
except EvalFailure as e:
print("")
print("\x1b[1m\x1b[31mERRORS\x1b[0m encountered during evaluation:")
for e in e.errors:
print(f"\ton attribute hydraJobs.\x1b[1m{e['attr']}\x1b[0m:")
print(textwrap.indent(e['error'], "\t\t"))
sys.exit(1)
Generated
+265
View File
@@ -0,0 +1,265 @@
{
"nodes": {
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flakey-profile": {
"locked": {
"lastModified": 1712898590,
"narHash": "sha256-FhGIEU93VHAChKEXx905TSiPZKga69bWl1VB37FK//I=",
"owner": "lf-",
"repo": "flakey-profile",
"rev": "243c903fd8eadc0f63d205665a92d4df91d42d9d",
"type": "github"
},
"original": {
"owner": "lf-",
"repo": "flakey-profile",
"type": "github"
}
},
"lix": {
"inputs": {
"flake-compat": "flake-compat",
"nix2container": "nix2container",
"nix_2_18": "nix_2_18",
"nixpkgs": "nixpkgs_2",
"nixpkgs-regression": "nixpkgs-regression",
"pre-commit-hooks": "pre-commit-hooks"
},
"locked": {
"lastModified": 1759921757,
"narHash": "sha256-Ehc6T61Ntmxq/o1+9J2WttwjAoA86KW3z0rhGTg6GYA=",
"ref": "refs/heads/main",
"rev": "5e2412ea7e3a8725355a07bed1f3cccc5506edcf",
"revCount": 18412,
"type": "git",
"url": "https://git.lix.systems/lix-project/lix"
},
"original": {
"type": "git",
"url": "https://git.lix.systems/lix-project/lix"
}
},
"lix-module": {
"inputs": {
"flake-utils": "flake-utils",
"flakey-profile": "flakey-profile",
"lix": [
"lix"
],
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1756511062,
"narHash": "sha256-IgD1JR7scSEwlK/YAbmrcTWpAYT30LPldCUHdzXkaMs=",
"ref": "refs/heads/main",
"rev": "3f09a5eb772e02d98bb8878ab687d5b721f00d16",
"revCount": 162,
"type": "git",
"url": "https://git.lix.systems/lix-project/nixos-module"
},
"original": {
"type": "git",
"url": "https://git.lix.systems/lix-project/nixos-module"
}
},
"lowdown-src": {
"flake": false,
"locked": {
"lastModified": 1633514407,
"narHash": "sha256-Dw32tiMjdK9t3ETl5fzGrutQTzh2rufgZV4A/BbxuD4=",
"owner": "kristapsdz",
"repo": "lowdown",
"rev": "d2c2b44ff6c27b936ec27358a2653caaef8f73b8",
"type": "github"
},
"original": {
"owner": "kristapsdz",
"repo": "lowdown",
"type": "github"
}
},
"nix2container": {
"flake": false,
"locked": {
"lastModified": 1724996935,
"narHash": "sha256-njRK9vvZ1JJsP8oV2OgkBrpJhgQezI03S7gzskCcHos=",
"owner": "nlewo",
"repo": "nix2container",
"rev": "fa6bb0a1159f55d071ba99331355955ae30b3401",
"type": "github"
},
"original": {
"owner": "nlewo",
"repo": "nix2container",
"type": "github"
}
},
"nix_2_18": {
"inputs": {
"flake-compat": [
"lix",
"flake-compat"
],
"lowdown-src": "lowdown-src",
"nixpkgs": "nixpkgs",
"nixpkgs-regression": [
"lix",
"nixpkgs-regression"
]
},
"locked": {
"lastModified": 1730375271,
"narHash": "sha256-RrOFlDGmRXcVRV2p2HqHGqvzGNyWoD0Dado/BNlJ1SI=",
"owner": "NixOS",
"repo": "nix",
"rev": "0f665ff6779454f2117dcc32e44380cda7f45523",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "2.18.9",
"repo": "nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1705033721,
"narHash": "sha256-K5eJHmL1/kev6WuqyqqbS1cdNnSidIZ3jeqJ7GbrYnQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a1982c92d8980a0114372973cbdfe0a307f1bdea",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-23.05-small",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-regression": {
"locked": {
"lastModified": 1643052045,
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1758391731,
"narHash": "sha256-UuwQoPWv13DVKMveeev+F0OC/N95AOmAz6SzCuGhxjQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "3f00d36f15e16e0471d9ca1e8f88958941fa970a",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-25.05-small",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1759735786,
"narHash": "sha256-a0+h02lyP2KwSNrZz4wLJTu9ikujNsTWIC874Bv7IJ0=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "20c4598c84a671783f741e02bf05cbfaf4907cff",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-25.05",
"repo": "nixpkgs",
"type": "github"
}
},
"pre-commit-hooks": {
"flake": false,
"locked": {
"lastModified": 1733318908,
"narHash": "sha256-SVQVsbafSM1dJ4fpgyBqLZ+Lft+jcQuMtEL3lQWx2Sk=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "6f4e2a2112050951a314d2733a994fbab94864c6",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"root": {
"inputs": {
"lix": "lix",
"lix-module": "lix-module",
"nixpkgs": "nixpkgs_3"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}
+41
View File
@@ -0,0 +1,41 @@
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
lix.url = "git+https://git.lix.systems/lix-project/lix";
lix-module = {
url = "git+https://git.lix.systems/lix-project/nixos-module";
inputs.nixpkgs.follows = "nixpkgs";
inputs.lix.follows = "lix";
};
};
outputs = { nixpkgs, lix-module, ... }:
let
systems = [ "x86_64-linux" ];
nixpkgsFor = builtins.listToAttrs
(map
(system: {
name = system;
value = import nixpkgs {
inherit system;
overlays = [ lix-module.overlays.default ];
};
})
systems);
forEachSystem = f: builtins.mapAttrs f nixpkgsFor;
in
{
packages = forEachSystem (system: pkgs: {
default = pkgs.writeShellApplication {
name = "calculate-buildkite-steps";
runtimeInputs = [
pkgs.nix-eval-jobs
pkgs.python3
];
text = ''
python ${./calculate-buildkite-steps.py} | buildkite-agent pipeline upload
'';
};
});
};
}
Submodule series deleted from cc005058a9
+10
View File
@@ -0,0 +1,10 @@
---
# buildkite static setup file, in case the pipeline gets lost
steps:
- label: ":clapper:"
agents:
queue: "x86_64-linux"
large: true
command: |
exec nix --tarball-ttl 0 run "git+$${BUILDKITE_REPO}?ref=refs/heads/ci-config"