Compare commits

..
Author SHA1 Message Date
eldritch horrorsandLix Systems Gerrit 9cfd6d9c84 Revert "update flake inputs"
This reverts commit acf8f97b6b.

Reason for revert: ci broke? idfk

Change-Id: I2f8f02f3a5eb8562f8ff94341b47d82c29ed74ba
2026-07-06 11:37:09 +00:00
eldritch horrors acf8f97b6b update flake inputs
it's been a hot second 😰

Change-Id: I9eb3c93dc300ea658b9eec2c5a111d8f446ba61f
2026-07-04 16:25:50 +02:00
Pierre Bourdon 700f43c2cb ci-config: explicitly build all outputs in nix build invocation
Without this, even though all outputs will still be built, only the main
output (usually $out) store path gets printed by --print-out-paths. In
turn, this means that only the main output will get sent to attic for
caching.

In Lix's case, this was causing the following scenarios:
- builder1 builds build.aarch64-darwin.{out,dev,doc}
- builder1 only pushes .out to the caches
- builder2 attempts to build perlBindings.aarch64-darwin, which depends
  on -dev from build.aarch64-darwin.
- builder2 needs to rebuild all of build.aarch64-darwin to get -dev.

Change-Id: I51f7e641f987eebcd65b3cd57c1f6b1bfbfebd32
2026-01-20 11:11:08 +01:00
Pierre Bourdon e676a6c5b5 ci-config: print the store paths that have been built
For easier debugging of what actually gets pushed to attic.

Change-Id: I6ac5844045758c3ecc126c4a65fbfa10da5acfdd
2026-01-20 07:54:20 +01:00
Pierre Bourdon 41c63d68f5 ci-config: best-effort push CI intermediate build results to attic
If the builder is set up with an attic configuration (as is the case
since today on the Lix Buildkite builders), every Buildkite build step
attempts to push all their output store paths to attic so they can be
reused in later builds and/or in further steps being scheduled on
different builders.

Change-Id: Id102be66ede70c46ca8c34e1c929977edb4d5f57
2026-01-19 17:04:05 +01:00
eldritch horrors f706079df3 update nixpkgs, lix pins
Change-Id: I9d93a01751682473dabc5ae9def8348a35c2cef9
2025-10-10 15:30:48 +02:00
eldritch horrors bc626f868f 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

Change-Id: I1911dba2e87466215fbafb779dfe65c547fb41a4
2025-10-10 15:30:43 +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 494 additions and 1 deletions
+178
View File
@@ -0,0 +1,178 @@
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': textwrap.dedent(f'''
set -ueo pipefail
resultPath=$(nix build --print-out-paths --no-link -L ".#hydraJobs.{job.name}^*")
echo "Built paths: $$resultPath"
attic push afnix-ci $$resultPath || true
'''),
'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"