{ flake-parts-lib, lib, inputs, ... }: { options.perSystem = flake-parts-lib.mkPerSystemOption ( { config, pkgs, system, ... }: let cfg = config.ci.config; filterAttrsRec = pred: v: if lib.isAttrs v then lib.filterAttrs pred (lib.mapAttrs (path: filterAttrsRec pred) v) else v; subType = options: lib.types.submodule {inherit options;}; mkNullOption = type: lib.mkOption { default = null; type = lib.types.nullOr type; }; configType = with lib; subType { default-nix-image = mkOption { type = types.str; default = "registry.gitlab.com/technofab/nix-gitlab-ci/nix-ci:latest"; description = "The image to use on nix jobs"; }; nix-jobs-per-default = mkOption { type = types.bool; default = true; description = "Handle jobs nix-based by default or via opt-in (in job set nix = true) if false"; }; }; jobType = with lib; subType { # nix ci opts nix = mkOption { type = types.bool; default = cfg.nix-jobs-per-default; }; deps = mkOption { type = types.listOf types.package; default = []; }; # gitlab opts script = mkOption { type = types.listOf types.str; default = []; }; stage = mkOption { type = types.str; }; image = mkOption { type = types.str; default = cfg.default-nix-image; }; after_script = mkNullOption (types.listOf types.str); allow_failure = mkNullOption (types.either types.attrs types.bool); artifacts = mkNullOption (types.attrs); before_script = mkNullOption (types.listOf types.str); cache = mkNullOption (types.attrs); coverage = mkNullOption (types.str); dependencies = mkNullOption (types.listOf types.str); environment = mkNullOption (types.either types.attrs types.str); extends = mkNullOption (types.str); hooks = mkNullOption (types.attrs); id_tokens = mkNullOption (types.attrs); "inherit" = mkNullOption (types.attrs); interruptile = mkNullOption (types.bool); needs = mkNullOption (types.listOf types.str); publish = mkNullOption (types.str); pages = mkNullOption (types.attrs); parallel = mkNullOption (types.either types.int types.attrs); release = mkNullOption (types.attrs); retry = mkNullOption (types.either types.int types.attrs); rules = mkNullOption (types.listOf types.attrs); resource_group = mkNullOption (types.str); secrets = mkNullOption (types.attrs); services = mkNullOption (types.listOf types.attrs); start_in = mkNullOption (types.str); tags = mkNullOption (types.listOf types.str); timeout = mkNullOption (types.str); variables = mkNullOption (types.attrs); when = mkNullOption (types.str); }; in { options = with lib; { ci = mkOption { type = subType { config = mkOption { type = configType; description = '' Configuration options for the nix part itself ''; default = {}; }; image = mkNullOption (types.str); variables = mkNullOption (types.attrs); default = mkNullOption (types.attrs); stages = mkNullOption (types.listOf types.str); include = mkNullOption (types.attrs); workflow = mkNullOption (types.attrs); jobs = mkOption { type = types.lazyAttrsOf jobType; default = {}; }; }; description = '' Generate a Gitlab CI configuration which can be used to trigger a child pipeline. This will inject code which pre-downloads the nix deps before each job and adds them to PATH. ''; default = {}; }; }; config.packages = let toYaml = (pkgs.formats.yaml {}).generate; mapAttrs = cb: set: builtins.listToAttrs (builtins.map (key: cb key (builtins.getAttr key set)) (builtins.attrNames set)); prepend = key: arr: job: job // lib.optionalAttrs job.nix { ${key} = arr ++ job.${key} or []; }; prependToBeforeScript = prepend "before_script"; prependToAfterScript = prepend "after_script"; jobs = filterAttrsRec (n: v: v != null) config.ci.jobs; rest = filterAttrsRec (n: v: v != null) (builtins.removeAttrs config.ci ["jobs" "config"]); # this allows us to nix build this to get all the mentioned dependencies from the binary cache # pro: we don't have to download everything, just the deps for the current job # before, we just allowed pkgs inside the script string directly, but now with the ability to source this file # we can support different architectures between runners (eg. the arch of the initial runner does not matter) jobsMappedForDeps = mapAttrs (key: job: { name = "gitlab-ci-job-deps:${key}"; value = pkgs.writeShellScript "gitlab-ci-job-deps:${key}" '' export PATH="${lib.makeBinPath job.deps}:$PATH"; ''; }) jobs; # allows the user to directly run the script jobsMappedForScript = mapAttrs (key: job: { name = "gitlab-ci-job:${key}"; value = pkgs.writeShellScriptBin "gitlab-ci-job:${key}" (lib.strings.concatLines (job.before_script or [] ++ job.script ++ job.after_script or [])); }) jobs; # build the deps specific for this job before anything, this way the deps should be fetched from the cache jobsPatched = mapAttrs (key: job: { name = key; value = builtins.removeAttrs ( (prependToBeforeScript [ "source setup_nix_ci ${key}" ] job) // (prependToAfterScript [ "finalize_nix_ci" ] job) // lib.optionalAttrs job.nix { image = job.image; } ) ["nix" "deps"]; }) jobs; in { gitlab-ci-config = toYaml "generated-gitlab-ci.yml" (rest // jobsPatched); } // jobsMappedForDeps // jobsMappedForScript; } ); }