/* Helper functions for working with flakes. Schema: DefaultHerculesCIHelperSchema */ let inherit (builtins) attrNames concatMap hasAttr intersectAttrs listToAttrs mapAttrs typeOf ; # lib nameValuePair = k: v: { name = k; value = v; }; filterAttrs = pred: set: listToAttrs ( concatMap (name: let v = set.${name}; in if pred name v then [ (nameValuePair name v) ] else [ ]) (attrNames set) ); optionalAttrs = b: if b then a: a else _: { }; optionalCall = f: a: if builtins.isFunction f then f a else f; # end lib # flake -> evalArgs -> { flake | herculesCI } addDefaults = flake: evalArgs: let args = evalArgs // { ciSystems = if flake?herculesCI.ciSystems then listToAttrs (map (sys: nameValuePair sys { }) flake.herculesCI.ciSystems) else args.herculesCI.ciSystems; }; herculesCI = optionalCall (flake.herculesCI or { }) evalArgs; in herculesCI // optionalAttrs (!herculesCI?onPush) { onPush.default = { outputs = flakeToOutputs flake args; }; }; flakeToOutputs = flake: args: mapAttrs (translateFlakeAttr args) flake.outputs; filterSystems = ciSystems: if ciSystems == null then attrs: attrs else intersectAttrs ciSystems; isEnabledSystemConfig = ciSystems: if ciSystems == null then sys: true else sys: hasAttr sys.config.nixpkgs.localSystem.system ciSystems || hasAttr sys.config.nixpkgs.system ciSystems; filterSystemConfigs = ciSystems: filterAttrs (k: v: if isEnabledSystemConfig ciSystems v then true else builtins.trace "Ignoring flake attribute ${k} (system not in ciSystems)" false); translateFlakeAttr = args: k: v: if translations?${k} then translations.${k} args v else if deprecated?${k} then builtins.trace "Ignoring flake attribute ${k} (deprecated)" null else builtins.trace "Ignoring flake attribute ${k}" null; deprecated = { defaultApp = null; defaultPackage = null; defaultTemplate = null; defaultBundler = null; overlay = null; # too soon? # devShell = null; }; checkApp = app: if app.type != "app" then throw "App type attribute must be set to \"app\"." else if builtins.typeOf app.program == "string" then { program = validateProgramFromStringContext app.program; } else { inherit (app) program; }; validateProgramFromStringContext = s: let ctx = builtins.getContext s; drvs = builtins.attrNames ctx; drvPath = if drvs == [ ] then throw "The provided program string does not have a package in its context. Please set the app's program attribute to a package with `meta.mainProgram` or to a string of the form \"\${pkg}/bin/command\", where `pkg` is a package." else if builtins.length drvs != 1 then throw "The provided program string has multiple packages in its context. Please set the app's program attribute to a single package with `meta.mainProgram` or to a string of the form \"\${pkg}/bin/command\", where `pkg` is a single package." else builtins.head drvs; basename = baseNameOf drvPath; hashLength = 33; l = builtins.stringLength basename; in { name = builtins.substring hashLength (l - hashLength - 4) basename; type = "derivation"; drvPath = drvPath; }; translateShell = drv: if drv.type or null != "derivation" then throw "Attribute is not a shell derivation" else drv // { buildDependenciesOnly = true; }; hasPrefix = pre: s: builtins.substring 0 (builtins.stringLength pre) s == pre; stringIsStorePath = s: hasPrefix builtins.storeDir (dirOf s); isPathLike = v: (builtins.typeOf v == "path" && stringIsStorePath (toString v)) || (builtins.typeOf v == "string" && stringIsStorePath v) || (v?__toString && isPathLike (v.__toString v)) || (v?outPath && isPathLike v.outPath); checkTemplate = name: template: if !builtins.isAttrs template then throw "Template `templates.${name}` is not an attribute set." else if !template?description then throw "Template `templates.${name}` does not have a `description` string attribute." else if typeOf template.description != "string" then throw "`templates.${name}.description` must be a string, but its type is ${typeOf template.description}." else if !template?path then throw "Template `templates.${name}` does not have a `path` attribute." else if !isPathLike template.path then if builtins.typeOf template.path == "string" || builtins.typeOf template.path == "path" then throw "`templates.${name}.path` must be a path value in the store, or a string representing a store path, or an attribute set coercible to such values (e.g. cleanSource return value). However, the value is ${toString template.path}" else throw "`templates.${name}.path` must be a path value in the store, or a string representing a store path, or an attribute set coercible to such values (e.g. cleanSource return value). However, the type is ${builtins.typeOf template.path}" else if !(builtins.pathExists template.path) then throw "Template path `templates.${name}.path` points to a location that does not exist." else template; translations = let ignore = args: x: null; same = args: x: x; forSystems = f: args: attrs: mapAttrs f (filterSystems args.ciSystems attrs); in rec { # buildable packages = forSystems (sys: pkgs: pkgs); checks = forSystems (sys: checks: checks); devShell = forSystems (sys: translateShell); devShells = forSystems (sys: mapAttrs (k: translateShell)); apps = forSystems (sys: mapAttrs (k: checkApp)); nixosConfigurations = args: attrs: mapAttrs (k: sys: { config.system.build.toplevel = sys.config.system.build.toplevel; }) (filterSystemConfigs args.ciSystems attrs); darwinConfigurations = args: attrs: mapAttrs (k: sys: { config.system.build.toplevel = sys.config.system.build.toplevel; }) (filterSystemConfigs args.ciSystems attrs); formatter = forSystems (sys: formatter: formatter); templates = args: attrs: mapAttrs checkTemplate attrs; effects = args: effects: if builtins.isAttrs effects then effects else effects args; # not buildable herculesCI = ignore; overlays = ignore; submodules = ignore; nixosModules = ignore; darwinModules = ignore; legacyPackages = ignore; }; in { inherit addDefaults flakeToOutputs ; }