1
1
forked from extern/flakelight
flakelight/default.nix

463 lines
15 KiB
Nix
Raw Normal View History

2023-04-09 02:56:06 +02:00
# flakelite -- Framework for making flakes simple
# Copyright (C) 2023 Archit Gupta <archit@accelbread.com>
# SPDX-License-Identifier: MIT
localInputs:
2023-04-09 02:56:06 +02:00
let
2023-04-15 23:23:29 +02:00
inherit (builtins) intersectAttrs isPath readDir;
inherit (localInputs.nixpkgs.lib) attrNames attrVals callPackageWith
2023-04-16 03:37:48 +02:00
composeManyExtensions concat filter filterAttrs foldAttrs foldl functionArgs
genAttrs hasSuffix isFunction isList isString listToAttrs mapAttrs
mapAttrsToList mapAttrs' mergeAttrs nameValuePair optional optionalAttrs
optionalString parseDrvName pathExists pipe recursiveUpdate removePrefix
removeSuffix zipAttrsWith;
2023-04-09 02:56:06 +02:00
2023-04-16 09:55:45 +02:00
/* Attributes in flakelite's lib output.
*/
exports = {
2023-04-15 22:42:53 +02:00
inherit mkFlake systems importDir autoImport autoImportAttrs defaultPkgName
2023-04-16 03:37:48 +02:00
supportedSystem mergeModules moduleAttrs rootAttrs ensureFn callFn
filterArgs callPkg callPkgs tryImport mkApp mkCheck liftFn2 fnConcat
fnMergeAttrs;
};
2023-04-16 09:55:45 +02:00
/* Module which is always included as first module.
*/
2023-04-15 23:23:29 +02:00
builtinModule = { src, inputs, root }: {
2023-04-16 09:55:45 +02:00
# Ensures nixpkgs and flakelite are available for modules.
2023-04-16 05:11:34 +02:00
inputs = {
flakelite = localInputs.self;
inherit (localInputs) nixpkgs;
};
withOverlays = params: [
(final: prev: {
2023-04-16 09:55:45 +02:00
# Allows access to flakelite lib functions from package sets.
# Also adds pkgs-specific additional args.
flakelite = params // {
2023-04-16 09:55:45 +02:00
# Inputs with system auto-selected.
# i.e. inputs.self.packages.${system} -> inputs'.self.packages
inputs' = mapAttrs
(_: mapAttrs
(_: v: v.${prev.system} or { }))
inputs;
2023-04-16 09:55:45 +02:00
# Default package meta attribute generated from root module attrs.
meta = {
platforms = root.systems;
} // optionalAttrs (root ? description) {
inherit (root) description;
} // optionalAttrs (root ? license) {
license =
if isList root.license
then attrVals root.license final.lib.licenses
else final.lib.licenses.${root.license};
};
};
})
];
2023-04-09 02:56:06 +02:00
checks = { pkgs, lib, ... }:
2023-04-16 09:55:45 +02:00
# Enable editorconfig support if detected.
# By default, high false-positive flags are disabled.
2023-04-09 02:56:06 +02:00
(optionalAttrs (pathExists (src + /.editorconfig)) {
editorconfig = "${lib.getExe pkgs.editorconfig-checker}"
+ optionalString (!pathExists (src + /.ecrc))
" -disable-indent-size -disable-max-line-length";
});
devTools = pkgs: with pkgs; [ nixpkgs-fmt nodePackages.prettier ];
2023-04-09 02:56:06 +02:00
formatters = {
"*.nix" = "nixpkgs-fmt";
"*.md | *.json | *.yml" = "prettier --write";
2023-04-09 02:56:06 +02:00
};
};
2023-04-16 09:55:45 +02:00
/* Import each nix file in a directory as attrs. Attr name is file name with
extension stripped. To allow use in an importable directory, default.nix is
skipped. To provide a file that will result in a "default" attr, name the
file "_default.nix".
*/
2023-04-15 22:42:53 +02:00
importDir = path: genAttrs
(pipe (readDir path) [
attrNames
(filter (s: s != "default.nix"))
(filter (hasSuffix ".nix"))
(map (removeSuffix ".nix"))
2023-04-15 20:02:06 +02:00
(map (removePrefix "_"))
])
(p: import (path + (if pathExists
2023-04-15 20:02:06 +02:00
(path + "/_${p}.nix") then "/_${p}.nix" else "/${p}.nix")));
2023-04-16 09:55:45 +02:00
/* Try to load an attr from a directory. If a nix file by that name exits,
import it. If an importable directory with that name exists, import it.
Else, if a non-importable directory with that name exists, load the nix
files in that dir as an attrset. Returns null if the attr could not be
loaded.
*/
2023-04-15 22:42:53 +02:00
autoImport = dir: attr:
if pathExists (dir + "/${attr}.nix")
then import (dir + "/${attr}.nix")
else if pathExists (dir + "/${attr}/default.nix")
then import (dir + "/${attr}")
else if pathExists (dir + "/${attr}")
then importDir (dir + "/${attr}")
2023-04-15 13:41:27 +02:00
else null;
2023-04-16 09:55:45 +02:00
/* List of attrs that can be provided by a module.
*/
2023-04-15 22:42:53 +02:00
moduleAttrs = [
"inputs"
2023-04-15 13:41:27 +02:00
"withOverlay"
"withOverlays"
"package"
"packages"
"devTools"
"devShell"
"devShells"
"env"
"overlay"
"overlays"
"app"
2023-04-15 13:41:27 +02:00
"apps"
"checks"
2023-04-15 15:36:46 +02:00
"nixosModule"
2023-04-15 13:41:27 +02:00
"nixosModules"
"nixosConfigurations"
2023-04-15 15:36:46 +02:00
"template"
2023-04-15 13:41:27 +02:00
"templates"
"formatters"
2023-04-15 22:42:53 +02:00
];
2023-04-16 09:55:45 +02:00
/* List of handled attrs in root module. Does not have optionally checked
attrs like name, description, or license, or attrs used by modules.
*/
2023-04-15 22:42:53 +02:00
rootAttrs = moduleAttrs ++ [
2023-04-15 13:41:27 +02:00
"systems"
"perSystem"
"outputs"
2023-04-15 22:42:53 +02:00
"nixDir"
2023-04-15 13:41:27 +02:00
];
2023-04-16 09:55:45 +02:00
/* Generate an attrset by importing attrs from dir. Filters null values.
*/
2023-04-15 22:42:53 +02:00
autoImportAttrs = dir: attrs:
filterAttrs (_: v: v != null) (genAttrs attrs (autoImport dir));
2023-04-16 09:55:45 +02:00
/* Makes the parameter callable, if it isn't. This allows values that are not
always functions to be applied to parameters.
*/
ensureFn = v: if isFunction v then v else _: v;
2023-04-16 09:55:45 +02:00
/* Takes a binary function and returns a binary function that operates on
functions that return the original parameter types.
Type: liftFn2 :: (a -> b -> c) -> (d -> a) -> (d -> b) -> d -> c
*/
2023-04-16 03:37:48 +02:00
liftFn2 = fn: a: b: args: fn (a args) (b args);
fnConcat = liftFn2 concat;
fnMergeAttrs = liftFn2 mergeAttrs;
2023-04-16 09:55:45 +02:00
/* Merges attrsets of overlays, combining overlays with same name.
*/
2023-04-16 03:37:48 +02:00
mergeOverlayAttrs = a: b: zipAttrsWith (_: composeManyExtensions) [ a b ];
mergeModules = a: b: mapAttrs (n: v: v a.${n} b.${n}) {
inputs = mergeAttrs;
withOverlays = concat;
packages = mergeAttrs;
devTools = fnConcat;
devShells = fnMergeAttrs;
env = fnMergeAttrs;
overlays = mergeOverlayAttrs;
apps = fnMergeAttrs;
checks = fnMergeAttrs;
nixosModules = mergeAttrs;
nixosConfigurations = mergeAttrs;
templates = mergeAttrs;
formatters = fnMergeAttrs;
};
2023-04-16 09:55:45 +02:00
/* Calls f with required arguments from args. If the function does not have
named arguments, just passes it args instead of nothing like callPackage
does. If f is not a function, returns f.
*/
2023-04-15 22:42:53 +02:00
callFn = args: f:
let
2023-04-15 23:23:29 +02:00
f' = ensureFn f;
in
2023-04-15 23:23:29 +02:00
if functionArgs f' == { } then f' args
else f' (intersectAttrs (functionArgs f) args);
2023-04-16 09:55:45 +02:00
/* Ensures x is called with only it's required parameters.
*/
2023-04-16 01:59:02 +02:00
filterArgs = x: args: callFn args x;
2023-04-16 09:55:45 +02:00
/* If arg is importable, imports it, else returns arg as is.
*/
2023-04-15 23:23:29 +02:00
tryImport = x: if (isPath x) || (isString x) then import x else x;
2023-04-16 09:55:45 +02:00
/* Like callFn, but intended for functions that return derivations. Uses
callPackage so will make result overridable. Trys importing the value if a
path.
*/
2023-04-15 23:23:29 +02:00
callPkg = args: f:
let
f' = ensureFn (tryImport f);
in
if functionArgs f' == { } then f' args
else (args.callPackage or (callPackageWith args)) f' { };
callPkgs = pkgs: mapAttrs (_: callPkg pkgs);
2023-04-16 09:55:45 +02:00
/* Gets the name for the default package using value set in root module or
derivation attrs.
*/
2023-04-15 22:42:53 +02:00
defaultPkgName = root: pkg: root.name or pkg.pname or (parseDrvName pkg).name;
2023-04-16 09:55:45 +02:00
/* Merges elements of a list of sets recursively. Each element can optionally
be a function that takes the merged previous elements.
*/
2023-04-16 03:37:48 +02:00
recUpdateSets = foldl (acc: x: recursiveUpdate acc ((ensureFn x) acc)) { };
isApp = x: (x ? type) && (x.type == "app") && (x ? program);
2023-04-16 09:55:45 +02:00
/* Turns app into an app attribute, if it is not already. Passes it pkgs if it
is a function.
*/
2023-04-16 03:37:48 +02:00
mkApp = pkgs: app:
let
app' = callFn pkgs app;
in
if isApp app' then app'
else { type = "app"; program = "${app'}"; };
2023-04-16 09:55:45 +02:00
/* Makes cmd into a derivation for a flake's checks output. If it is not
already a derivation, makes one that runs cmd on the flake source and
depends on its success.
*/
2023-04-16 03:37:48 +02:00
mkCheck = pkgs: src: name: cmd:
if pkgs.lib.isDerivation cmd then cmd else
pkgs.runCommand "check-${name}" { } ''
cp --no-preserve=mode -r ${src} src
cd src
${cmd}
touch $out
'';
2023-04-16 09:55:45 +02:00
/* Takes a packages set and a package and returns true if the package is
supported on the system for that packages set. If unknown, returns true.
*/
supportedSystem = { lib, stdenv, ... }: pkg:
if pkg ? meta.platforms
then lib.meta.availableOn stdenv.hostPlatform pkg
else true;
2023-04-15 22:42:53 +02:00
systems = rec {
2023-04-16 09:55:45 +02:00
# Linux systems with binary caches.
2023-04-15 22:42:53 +02:00
linuxDefault = [
"x86_64-linux"
"aarch64-linux"
];
2023-04-16 09:55:45 +02:00
# Linux systems supported as a host platform.
2023-04-15 22:42:53 +02:00
linuxAll = linuxDefault ++ [
"armv6l-linux"
"armv7l-linux"
"i686-linux"
];
};
2023-04-16 09:55:45 +02:00
/* Creates flake outputs; takes the path of the flake and the root module.
*/
mkFlake = src: root:
2023-04-09 02:56:06 +02:00
let
2023-04-16 09:55:45 +02:00
# These are passed to modules and non-system-dependent module attrs.
2023-04-15 22:42:53 +02:00
nonSysArgs = exports // {
args = nonSysArgs;
flakelite = exports;
root = root';
inherit src;
inherit (merged) inputs;
inherit (merged.inputs.nixpkgs) lib;
2023-04-15 22:42:53 +02:00
};
2023-04-15 23:23:29 +02:00
applyNonSysArgs = callFn nonSysArgs;
2023-04-15 22:42:53 +02:00
moduleAttrDefaults = {
inputs = { };
2023-04-09 02:56:06 +02:00
withOverlays = [ ];
packages = { };
devTools = _: [ ];
devShells = _: { };
2023-04-09 02:56:06 +02:00
env = _: { };
overlays = { };
apps = _: { };
checks = _: { };
nixosModules = { };
nixosConfigurations = { };
templates = { };
formatters = _: { };
};
normalizeModule = module:
let
2023-04-15 22:42:53 +02:00
module' = moduleAttrDefaults // module;
2023-04-09 02:56:06 +02:00
in
module' // {
2023-04-15 22:42:53 +02:00
withOverlays = (applyNonSysArgs module'.withOverlays)
2023-04-09 02:56:06 +02:00
++ optional (module' ? withOverlay) module'.withOverlay;
2023-04-15 22:42:53 +02:00
packages = (applyNonSysArgs module'.packages)
// optionalAttrs (module' ? package) {
2023-04-09 02:56:06 +02:00
default = module'.package;
};
2023-04-16 01:59:02 +02:00
devTools = filterArgs module'.devTools;
2023-04-16 03:37:48 +02:00
devShells = fnMergeAttrs (filterArgs module'.devShells)
(_: optionalAttrs (module' ? devShell) {
default = module'.devShell;
});
2023-04-16 01:59:02 +02:00
env = filterArgs module'.env;
2023-04-15 22:42:53 +02:00
overlays = (applyNonSysArgs module'.overlays)
// optionalAttrs (module' ? overlay) {
2023-04-09 02:56:06 +02:00
default = module'.overlay;
};
2023-04-16 03:37:48 +02:00
apps = fnMergeAttrs (filterArgs module'.apps)
(_: optionalAttrs (module' ? app) {
default = module'.app;
});
2023-04-16 01:59:02 +02:00
checks = filterArgs module'.checks;
2023-04-15 22:42:53 +02:00
nixosModules = (applyNonSysArgs module'.nixosModules)
2023-04-15 15:36:46 +02:00
// optionalAttrs (module' ? nixosModule) {
default = module'.nixosModule;
};
2023-04-15 22:42:53 +02:00
nixosConfigurations = applyNonSysArgs module'.nixosConfigurations;
templates = (applyNonSysArgs module'.templates)
2023-04-15 15:36:46 +02:00
// optionalAttrs (module' ? template) {
default = module'.template;
};
2023-04-16 01:59:02 +02:00
formatters = filterArgs module'.formatters;
2023-04-09 02:56:06 +02:00
};
2023-04-16 09:55:45 +02:00
# Root module with autoloads, normalization, and additional attrs.
root' =
let
2023-04-15 14:07:03 +02:00
nixDir = root.nixDir or (src + ./nix);
2023-04-15 22:42:53 +02:00
fullRoot = (autoImportAttrs nixDir rootAttrs) // root;
in
2023-04-15 22:42:53 +02:00
normalizeModule fullRoot // {
2023-04-16 03:37:48 +02:00
modules = fullRoot.modules or
(pipe (removeAttrs root'.inputs [ "self" ]) [
(filterAttrs (_: v: v ? flakeliteModule))
(mapAttrsToList (_: v: v.flakeliteModule))
]);
2023-04-16 01:59:02 +02:00
systems = applyNonSysArgs (fullRoot.systems or systems.linuxDefault);
perSystem = filterArgs (fullRoot.perSystem or { });
outputs = applyNonSysArgs (fullRoot.outputs or { });
2023-04-15 14:07:03 +02:00
inherit nixDir;
raw = root;
};
2023-04-16 09:55:45 +02:00
# Merge result of all the modules.
2023-04-15 22:42:53 +02:00
merged = foldl mergeModules moduleAttrDefaults
2023-04-15 23:23:29 +02:00
((map (m: normalizeModule (applyNonSysArgs m))
2023-04-16 03:37:48 +02:00
([ builtinModule ] ++ root'.modules)) ++ [ root' ]);
2023-04-09 02:56:06 +02:00
2023-04-16 09:55:45 +02:00
# Returns package set for a system.
pkgsFor = system: import merged.inputs.nixpkgs {
2023-04-09 02:56:06 +02:00
inherit system;
overlays = merged.withOverlays ++ [
2023-04-15 23:23:29 +02:00
(final: _: callPkgs final merged.packages)
];
2023-04-09 02:56:06 +02:00
};
2023-04-16 09:55:45 +02:00
# Attrset mapping systems to corresponding package sets.
2023-04-09 02:56:06 +02:00
systemPkgs = listToAttrs (map
(system: nameValuePair system (pkgsFor system))
root'.systems);
2023-04-16 09:55:45 +02:00
# Calls fn for each supported system. Fn should return an attrset whose
# attrs normally would have values in system attrs. Merges results into
# attrset with system attrs.
2023-04-09 02:56:06 +02:00
eachSystem = fn: foldAttrs mergeAttrs { } (map
(system: mapAttrs
(_: v: { ${system} = v; })
(fn systemPkgs.${system}))
root'.systems);
2023-04-16 09:55:45 +02:00
# Replaces the "default" attr in set with the default package name.
2023-04-09 02:56:06 +02:00
replaceDefault = set:
if set ? default
then (removeAttrs set [ "default" ]) //
2023-04-15 22:42:53 +02:00
{ ${defaultPkgName root' set.default} = set.default; }
2023-04-09 02:56:06 +02:00
else set;
in
2023-04-15 22:42:53 +02:00
recUpdateSets [
2023-04-09 02:56:06 +02:00
(optionalAttrs (merged.packages != { }) ({
2023-04-16 09:55:45 +02:00
# Packages in overlay depend on withOverlays which are not in pkg set.
2023-04-15 23:23:29 +02:00
overlays.default = final: _: callPkgs
2023-04-09 02:56:06 +02:00
(final.appendOverlays merged.withOverlays)
(replaceDefault merged.packages);
} // eachSystem (pkgs: rec {
2023-04-16 09:55:45 +02:00
# Packages are generated in overlay on system pkgs; grab from there.
2023-04-09 02:56:06 +02:00
packages = filterAttrs (_: supportedSystem pkgs)
2023-04-15 23:23:29 +02:00
(intersectAttrs merged.packages pkgs);
2023-04-15 22:42:53 +02:00
checks = mapAttrs' (n: nameValuePair ("packages-" + n)) packages;
2023-04-09 02:56:06 +02:00
})))
(prev: optionalAttrs (merged.overlays != { }) ({
2023-04-16 03:37:48 +02:00
overlays = mergeOverlayAttrs (prev.overlays or { }) merged.overlays;
2023-04-09 02:56:06 +02:00
}))
(eachSystem ({ pkgs, lib, ... }:
optionalAttrs (merged.formatters pkgs != { }) rec {
formatter = pkgs.writeShellScriptBin "formatter" ''
PATH=${lib.makeBinPath (merged.devTools pkgs)}
for f in "$@"; do
if [ -d "$f" ]; then
${pkgs.fd}/bin/fd "$f" -Htf -x "$0"
else
case "$(${pkgs.coreutils}/bin/basename "$f")" in
2023-04-15 22:42:53 +02:00
${toString (mapAttrsToList (n: v: "${n}) ${v} \"$f\";;")
2023-04-09 02:56:06 +02:00
(merged.formatters pkgs))}
esac
fi
done &>/dev/null
'';
2023-04-16 03:37:48 +02:00
checks.formatting = mkCheck pkgs src "formatting" ''
2023-04-09 02:56:06 +02:00
${lib.getExe formatter} .
${pkgs.diffutils}/bin/diff -qr ${src} . |\
sed 's/Files .* and \(.*\) differ/File \1 not formatted/g'
'';
}))
2023-04-15 22:42:53 +02:00
(eachSystem (pkgs:
2023-04-09 02:56:06 +02:00
let
2023-04-16 03:37:48 +02:00
checks = mapAttrs (mkCheck pkgs src) (merged.checks pkgs);
2023-04-09 02:56:06 +02:00
in
optionalAttrs (checks != { }) { inherit checks; }))
(eachSystem (pkgs:
2023-04-09 02:56:06 +02:00
let
apps = mapAttrs (_: mkApp pkgs) (merged.apps pkgs);
2023-04-09 02:56:06 +02:00
in
optionalAttrs (apps != { }) { inherit apps; }))
(optionalAttrs (merged.nixosModules != { }) {
inherit (merged) nixosModules;
})
(optionalAttrs (merged.nixosConfigurations != { }) {
inherit (merged) nixosConfigurations;
2023-04-15 22:42:53 +02:00
checks = recUpdateSets (mapAttrsToList
(n: v: {
${v.config.nixpkgs.system}."nixos-${n}" =
v.config.system.build.toplevel;
})
merged.nixosConfigurations);
2023-04-09 02:56:06 +02:00
})
(optionalAttrs (merged.templates != { }) {
inherit (merged) templates;
})
(prev: eachSystem ({ pkgs, system, mkShell, ... }: {
devShells.default = mkShell (merged.env pkgs // {
inputsFrom = optional (prev ? packages.${system}.default)
prev.packages.${system}.default;
packages = merged.devTools pkgs;
});
2023-04-15 23:23:29 +02:00
} // (callPkgs pkgs (merged.devShells pkgs))))
2023-04-09 02:56:06 +02:00
(eachSystem root'.perSystem)
root'.outputs
];
in
exports