packages feed

hercules-ci-agent-0.9.9: data/default-herculesCI-for-flake.nix

/*
  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
    ;
}