Reimplement framework using the module system

This commit is contained in:
Archit Gupta 2023-08-24 21:14:55 -07:00
parent 4df2ca87b5
commit f6c4424f6e
23 changed files with 749 additions and 533 deletions

View File

@ -1,7 +0,0 @@
_: {
devTools = pkgs: with pkgs; [ nixpkgs-fmt nodePackages.prettier ];
formatters = {
"*.nix" = "nixpkgs-fmt";
"*.md | *.json | *.yml" = "prettier --write";
};
}

View File

@ -1,13 +0,0 @@
{ src, lib }:
let
inherit (lib) getExe optionalAttrs optionalString pathExists;
in
{
checks = optionalAttrs (pathExists (src + /.editorconfig)) {
# By default, high false-positive flags are disabled.
editorconfig = { editorconfig-checker }:
"${getExe editorconfig-checker}"
+ optionalString (!pathExists (src + /.ecrc))
" -disable-indent-size -disable-max-line-length";
};
}

View File

@ -1,10 +0,0 @@
_: {
shellHook = { lib, flakelite }: ''
if [ -f flake.nix ] && [ -d .git/hooks ] &&
[ ! -f .git/hooks/pre-commit ]; then
echo Installing git pre-commit hook...
cp ${lib.getExe flakelite.inputs'.flakelite.packages.pre-commit-hook
} .git/hooks
fi
'';
}

43
builtinModules/apps.nix Normal file
View File

@ -0,0 +1,43 @@
# flakelite -- Framework for making flakes simple
# Copyright (C) 2023 Archit Gupta <archit@accelbread.com>
# SPDX-License-Identifier: MIT
{ config, lib, flakelite, ... }:
let
inherit (lib) isFunction mapAttrs mkIf mkMerge mkOption;
inherit (lib.types) lazyAttrsOf nullOr raw;
inherit (flakelite.types) optFunctionTo;
isApp = x: (x ? type) && (x.type == "app") && (x ? program);
mkApp = pkgs: app:
let
app' = if isFunction app then app pkgs else app;
in
if isApp app' then app' else { type = "app"; program = "${app'}"; };
in
{
options = {
app = mkOption {
type = nullOr raw;
default = null;
};
apps = mkOption {
type = nullOr (optFunctionTo (lazyAttrsOf raw));
default = null;
};
};
config = mkMerge [
(mkIf (config.app != null) {
apps.default = config.app;
})
(mkIf (config.apps != null) {
perSystem = pkgs: {
apps = mapAttrs (_: mkApp pkgs) (config.apps pkgs);
};
})
];
}

View File

@ -0,0 +1,24 @@
# flakelite -- Framework for making flakes simple
# Copyright (C) 2023 Archit Gupta <archit@accelbread.com>
# SPDX-License-Identifier: MIT
{ lib, ... }:
let
inherit (lib) mkEnableOption;
in
{
options.flakelite.builtinFormatters =
mkEnableOption "default formatters" // { default = true; };
config = {
devShell.packages = pkgs: [
pkgs.nixpkgs-fmt
pkgs.nodePackages.prettier
];
formatters = {
"*.nix" = "nixpkgs-fmt";
"*.md | *.json | *.yml" = "prettier --write";
};
};
}

View File

@ -0,0 +1,46 @@
# flakelite -- Framework for making flakes simple
# Copyright (C) 2023 Archit Gupta <archit@accelbread.com>
# SPDX-License-Identifier: MIT
{ config, src, lib, inputs, outputs, flakelite, ... }:
let
inherit (lib) isList mkOption mkOrder mapAttrs optionalAttrs;
inherit (lib.types) listOf nullOr oneOf str;
in
{
options = {
description = mkOption {
type = nullOr str;
default = null;
};
license = mkOption {
type = nullOr (oneOf [ str (listOf str) ]);
default = null;
};
};
config.withOverlays = mkOrder 10 (final: prev:
let
system = prev.stdenv.hostPlatform.system;
in
{
inherit src inputs outputs flakelite system;
inputs' = mapAttrs (_: mapAttrs (_: v: v.${system} or { })) inputs;
outputs' = mapAttrs (_: v: v.${system} or { }) outputs;
defaultMeta = {
platforms = config.systems;
} // optionalAttrs (config.description != null) {
inherit (config) description;
} // optionalAttrs (config.license != null) {
license =
let
getLicense = license: final.lib.licenses.${license} or
(final.lib.meta.getLicenseFromSpdxId license);
in
if isList config.license then map getLicense config.license
else getLicense config.license;
};
});
}

32
builtinModules/checks.nix Normal file
View File

@ -0,0 +1,32 @@
# flakelite -- Framework for making flakes simple
# Copyright (C) 2023 Archit Gupta <archit@accelbread.com>
# SPDX-License-Identifier: MIT
{ config, src, lib, flakelite, ... }:
let
inherit (lib) isDerivation isFunction mkOption mkIf mapAttrs;
inherit (lib.types) lazyAttrsOf nullOr raw;
inherit (flakelite.types) optFunctionTo;
mkCheck = pkgs: src: name: cmd:
let
cmd' = if isFunction cmd then cmd pkgs else cmd;
in
if isDerivation cmd' then cmd' else
pkgs.runCommand "check-${name}" { } ''
cp --no-preserve=mode -r ${src} src
cd src
${cmd'}
touch $out
'';
in
{
options.checks = mkOption {
type = nullOr (optFunctionTo (lazyAttrsOf raw));
default = null;
};
config.perSystem = mkIf (config.checks != null) (pkgs: {
checks = mapAttrs (mkCheck pkgs src) (config.checks pkgs);
});
}

69
builtinModules/core.nix Normal file
View File

@ -0,0 +1,69 @@
# flakelite -- Framework for making flakes simple
# Copyright (C) 2023 Archit Gupta <archit@accelbread.com>
# SPDX-License-Identifier: MIT
{ config, inputs, lib, flakelite, ... }:
let
inherit (builtins) all head isAttrs length;
inherit (lib) foldAttrs getFiles getValues mapAttrs mergeAttrs mkOption
mkOptionType showFiles showOption;
inherit (lib.types) functionTo lazyAttrsOf listOf nonEmptyStr raw uniq;
inherit (flakelite.types) optListOf overlay;
outputs = mkOptionType {
name = "outputs";
description = "output values";
descriptionClass = "noun";
merge = loc: defs:
if (length defs) == 1 then (head defs).value
else if all isAttrs (getValues defs) then
(lazyAttrsOf outputs).merge loc defs
else throw "The option `${showOption loc}' has conflicting definitions in ${showFiles (getFiles defs)}";
};
in
{
options = {
inputs = mkOption {
type = lazyAttrsOf raw;
};
systems = mkOption {
type = uniq (listOf nonEmptyStr);
default = [ "x86_64-linux" "aarch64-linux" ];
};
outputs = mkOption {
type = lazyAttrsOf outputs;
default = { };
};
perSystem = mkOption {
type = functionTo (lazyAttrsOf outputs);
default = _: { };
};
nixpkgs.config = mkOption {
type = lazyAttrsOf raw;
default = { };
};
withOverlays = mkOption {
type = optListOf overlay;
default = [ ];
};
};
config = {
_module.args = { inherit (config) inputs outputs; };
outputs = foldAttrs mergeAttrs { } (map
(system: mapAttrs
(_: v: { ${system} = v; })
(config.perSystem (import inputs.nixpkgs {
inherit system;
config = config.nixpkgs.config;
overlays = config.withOverlays;
})))
config.systems);
};
}

View File

@ -0,0 +1,70 @@
# flakelite -- Framework for making flakes simple
# Copyright (C) 2023 Archit Gupta <archit@accelbread.com>
# SPDX-License-Identifier: MIT
{ config, lib, flakelite, ... }:
let
inherit (lib) any attrValues filterAttrs mapAttrs mkIf mkMerge mkOption
optionalAttrs;
inherit (lib.types) lazyAttrsOf functionTo lines listOf nullOr package str;
inherit (flakelite) supportedSystem;
inherit (flakelite.types) optFunctionTo packageDef;
in
{
options = {
devShell = {
inputsFrom = mkOption {
type = nullOr
(functionTo (listOf package));
default = null;
};
packages = mkOption {
type = nullOr
(functionTo (listOf package));
default = null;
};
shellHook = mkOption {
type = nullOr (optFunctionTo lines);
default = null;
};
env = mkOption {
type = nullOr
(optFunctionTo (lazyAttrsOf str));
default = null;
};
};
devShells = mkOption {
type = lazyAttrsOf packageDef;
default = { };
};
};
config = mkMerge [
(mkIf (any (x: x != null) (attrValues config.devShell)) {
devShells.default = { pkgs, mkShell }: mkShell (
optionalAttrs (config.devShell.env != null)
(config.devShell.env pkgs)
// optionalAttrs (config.devShell.inputsFrom != null) {
inputsFrom = config.devShell.inputsFrom pkgs;
}
// optionalAttrs (config.devShell.packages != null) {
packages = config.devShell.packages pkgs;
}
// optionalAttrs (config.devShell.shellHook != null) {
shellHook = config.devShell.shellHook pkgs;
}
);
})
(mkIf (config.devShells != { }) {
perSystem = pkgs: {
devShells = filterAttrs (_: supportedSystem pkgs)
(mapAttrs (_: v: pkgs.callPackage v { }) config.devShells);
};
})
];
}

View File

@ -0,0 +1,22 @@
# flakelite -- Framework for making flakes simple
# Copyright (C) 2023 Archit Gupta <archit@accelbread.com>
# SPDX-License-Identifier: MIT
{ config, lib, src, ... }:
let
inherit (lib) mkEnableOption mkIf optionalString pathExists;
in
{
options.flakelite.editorconfig =
mkEnableOption "editorconfig check" // { default = true; };
config.checks = mkIf
(config.flakelite.editorconfig && (pathExists (src + /.editorconfig)))
{
# By default, high false-positive flags are disabled.
editorconfig = { editorconfig-checker, ... }:
"${editorconfig-checker}/bin/editorconfig-checker"
+ optionalString (!pathExists (src + /.ecrc))
" -disable-indent-size -disable-max-line-length";
};
}

View File

@ -0,0 +1,40 @@
# flakelite -- Framework for making flakes simple
# Copyright (C) 2023 Archit Gupta <archit@accelbread.com>
# SPDX-License-Identifier: MIT
{ config, src, lib, flakelite, ... }:
let
inherit (lib) mkOption mkIf mapAttrsToList;
inherit (lib.types) lazyAttrsOf nullOr str;
inherit (flakelite.types) optFunctionTo;
in
{
options.formatters = mkOption {
type = nullOr (optFunctionTo (lazyAttrsOf str));
default = null;
};
config = mkIf (config.formatters != null) {
perSystem = { pkgs, lib, fd, coreutils, ... }: {
formatter = pkgs.writeShellScriptBin "formatter" ''
PATH=${lib.makeBinPath (config.devShell.packages pkgs)}
for f in "$@"; do
if [ -d "$f" ]; then
${fd}/bin/fd "$f" -Htf -x "$0"
else
case "$(${coreutils}/bin/basename "$f")" in
${toString (mapAttrsToList
(n: v: "${n}) ${v} \"$f\";;") (config.formatters pkgs))}
esac
fi
done &>/dev/null
'';
};
checks.formatting = { lib, outputs', diffutils, ... }: ''
${lib.getExe outputs'.formatter} .
${diffutils}/bin/diff -qr ${src} . |\
sed 's/Files .* and \(.*\) differ/File \1 not formatted/g'
'';
};
}

View File

@ -0,0 +1,34 @@
# flakelite -- Framework for making flakes simple
# Copyright (C) 2023 Archit Gupta <archit@accelbread.com>
# SPDX-License-Identifier: MIT
{ config, lib, ... }:
let
inherit (builtins) isAttrs;
inherit (lib) foldl mapAttrsToList mergeOneOption mkOption mkOptionType mkIf
recursiveUpdate;
inherit (lib.types) lazyAttrsOf;
homeConfiguration = mkOptionType {
name = "homeConfiguration";
description = "homeConfiguration";
descriptionClass = "noun";
check = x: isAttrs x && x ? activationPackage;
merge = mergeOneOption;
};
in
{
options.homeConfigurations = mkOption {
type = lazyAttrsOf homeConfiguration;
default = { };
};
config.outputs = mkIf (config.homeConfigurations != { }) {
inherit (config) homeConfigurations;
checks = foldl recursiveUpdate { } (mapAttrsToList
(n: v: {
${v.config.nixpkgs.system}."home-${n}" = v.activationPackage;
})
config.homeConfigurations);
};
}

View File

@ -0,0 +1,33 @@
# flakelite -- Framework for making flakes simple
# Copyright (C) 2023 Archit Gupta <archit@accelbread.com>
# SPDX-License-Identifier: MIT
{ config, lib, flakelite, ... }:
let
inherit (lib) mkOption mkIf mkMerge;
inherit (lib.types) lazyAttrsOf nullOr;
inherit (flakelite.types) module;
in
{
options = {
homeModule = mkOption {
type = nullOr module;
default = null;
};
homeModules = mkOption {
type = lazyAttrsOf module;
default = { };
};
};
config = mkMerge [
(mkIf (config.homeModule != null) {
homeModules.default = config.homeModule;
})
(mkIf (config.homeModules != { }) {
outputs = { inherit (config) homeModules; };
})
];
}

75
builtinModules/nixDir.nix Normal file
View File

@ -0,0 +1,75 @@
# flakelite -- Framework for making flakes simple
# Copyright (C) 2023 Archit Gupta <archit@accelbread.com>
# SPDX-License-Identifier: MIT
{ config, src, lib, flakelite, ... }@args:
let
inherit (lib) isFunction mkOption mkIf mkMerge optionalAttrs;
inherit (flakelite) autoImport;
inherit (flakelite.types) path;
in
{
options.nixDir = mkOption {
type = path;
default = src + /nix;
};
config =
let
autoImport' = autoImport config.nixDir;
autoImportArgs = n:
let v = autoImport' n; in
if isFunction v then v args else v;
outputs = autoImportArgs "outputs";
perSystem = autoImport' "perSystem";
withOverlays = autoImport' "withOverlays";
package = autoImport' "package";
packages = autoImportArgs "packages";
overlays = autoImportArgs "overlays";
devShell = autoImportArgs "devShell";
devShells = autoImportArgs "devShells";
app = autoImport' "app";
apps = autoImport' "apps";
checks = autoImport' "checks";
nixosModule = autoImport' "nixosModule";
nixosModules = autoImportArgs "nixosModules";
nixosConfigurations = autoImportArgs [ "nixosConfigurations" "nixos" ];
homeModule = autoImport' "homeModule";
homeModules = autoImportArgs "homeModules";
homeConfigurations = autoImportArgs [ "homeConfigurations" "home" ];
template = autoImportArgs "template";
templates = autoImportArgs "templates";
formatters = autoImport' "formatters";
in
mkMerge [
(mkIf (outputs != null) { inherit outputs; })
(mkIf (perSystem != null) { inherit perSystem; })
(mkIf (withOverlays != null) { inherit withOverlays; })
(mkIf (package != null) { inherit package; })
(mkIf (packages != null) { inherit packages; })
(mkIf (overlays != null) { inherit overlays; })
(mkIf (devShell != null) {
devShell = optionalAttrs (devShell ? inputsFrom)
{ inherit (devShell) inputsFrom; }
// optionalAttrs (devShell ? packages)
{ inherit (devShell) packages; }
// optionalAttrs (devShell ? shellHook)
{ inherit (devShell) shellHook; }
// optionalAttrs (devShell ? env)
{ inherit (devShell) env; };
})
(mkIf (devShells != null) { inherit devShells; })
(mkIf (app != null) { inherit app; })
(mkIf (apps != null) { inherit apps; })
(mkIf (checks != null) { inherit checks; })
(mkIf (nixosModule != null) { inherit nixosModule; })
(mkIf (nixosModules != null) { inherit nixosModules; })
(mkIf (nixosConfigurations != null) { inherit nixosConfigurations; })
(mkIf (homeModule != null) { inherit homeModule; })
(mkIf (homeModules != null) { inherit homeModules; })
(mkIf (homeConfigurations != null) { inherit homeConfigurations; })
(mkIf (template != null) { inherit template; })
(mkIf (templates != null) { inherit templates; })
(mkIf (formatters != null) { inherit formatters; })
];
}

View File

@ -0,0 +1,37 @@
# flakelite -- Framework for making flakes simple
# Copyright (C) 2023 Archit Gupta <archit@accelbread.com>
# SPDX-License-Identifier: MIT
{ config, lib, ... }:
let
inherit (builtins) isAttrs;
inherit (lib) foldl mapAttrsToList mergeOneOption mkIf mkOption mkOptionType
recursiveUpdate;
inherit (lib.types) lazyAttrsOf;
nixosConfiguration = mkOptionType {
name = "nixosConfiguration";
description = "nixosConfiguration";
descriptionClass = "noun";
check = x: isAttrs x
&& x ? config.nixpkgs.system
&& x ? config.system.build.toplevel;
merge = mergeOneOption;
};
in
{
options.nixosConfigurations = mkOption {
type = lazyAttrsOf nixosConfiguration;
default = { };
};
config.outputs = mkIf (config.nixosConfigurations != { }) {
inherit (config) nixosConfigurations;
checks = foldl recursiveUpdate { } (mapAttrsToList
(n: v: {
${v.config.nixpkgs.system}."nixos-${n}" =
v.config.system.build.toplevel;
})
config.nixosConfigurations);
};
}

View File

@ -0,0 +1,33 @@
# flakelite -- Framework for making flakes simple
# Copyright (C) 2023 Archit Gupta <archit@accelbread.com>
# SPDX-License-Identifier: MIT
{ config, lib, flakelite, ... }:
let
inherit (lib) mkOption mkIf mkMerge;
inherit (lib.types) lazyAttrsOf nullOr;
inherit (flakelite.types) module;
in
{
options = {
nixosModule = mkOption {
type = nullOr module;
default = null;
};
nixosModules = mkOption {
type = lazyAttrsOf module;
default = { };
};
};
config = mkMerge [
(mkIf (config.nixosModule != null) {
nixosModules.default = config.nixosModule;
})
(mkIf (config.nixosModules != { }) {
outputs = { inherit (config) nixosModules; };
})
];
}

View File

@ -0,0 +1,18 @@
# flakelite -- Framework for making flakes simple
# Copyright (C) 2023 Archit Gupta <archit@accelbread.com>
# SPDX-License-Identifier: MIT
{ config, lib, flakelite, ... }:
let
inherit (lib) mkOption mkIf;
inherit (lib.types) lazyAttrsOf;
inherit (flakelite.types) overlay;
in
{
options.overlays = mkOption {
type = lazyAttrsOf overlay;
default = { };
};
config.outputs = mkIf (config.overlays != { }) { inherit (config) overlays; };
}

View File

@ -0,0 +1,68 @@
# flakelite -- Framework for making flakes simple
# Copyright (C) 2023 Archit Gupta <archit@accelbread.com>
# SPDX-License-Identifier: MIT
{ config, lib, flakelite, ... }:
let
inherit (builtins) attrNames functionArgs intersectAttrs parseDrvName;
inherit (lib) composeManyExtensions filterAttrs fix genAttrs mapAttrs mapAttrs'
mkIf mkMerge mkOption mkOrder nameValuePair optional optionalAttrs remove;
inherit (lib.types) lazyAttrsOf nullOr;
inherit (flakelite) supportedSystem;
inherit (flakelite.types) packageDef;
getName = pkg: pkg.pname or (parseDrvName pkg.name).name;
callPkg = pkgs: def: def (intersectAttrs (functionArgs def) pkgs);
in
{
options = {
package = mkOption {
type = nullOr packageDef;
default = null;
};
packages = mkOption {
type = lazyAttrsOf packageDef;
default = { };
};
};
config = mkMerge [
(mkIf (config.package != null) {
packages.default = config.package;
})
(mkIf (config.packages != { }) {
withOverlays = mkOrder 2000 (final: prev:
let
pkgNames = attrNames config.packages;
pkgNames' = remove "default" pkgNames;
defaultName = getName (fix (self:
prev // (genAttrs pkgNames
(n: callPkg self config.packages.${n})))).default;
in
(optionalAttrs (config.packages ? default) {
${defaultName} = final.callPackage config.packages.default { };
}) //
(genAttrs pkgNames' (n: final.callPackage config.packages.${n} { })));
overlays.default = final: prev:
let
pkgs' = fix (composeManyExtensions config.withOverlays) prev;
defaultName = getName (callPkg pkgs' config.packages.default);
pkgNames = (remove "default" (attrNames config.packages))
++ (optional (config.packages ? default) defaultName);
pkgs = final.appendOverlays config.withOverlays;
in
genAttrs pkgNames (n: pkgs.${n});
perSystem = pkgs: rec {
packages = filterAttrs (_: supportedSystem pkgs)
(mapAttrs (_: v: pkgs.callPackage v { }) config.packages);
checks = mapAttrs' (n: nameValuePair ("packages-" + n)) packages;
};
})
];
}

View File

@ -0,0 +1,42 @@
# flakelite -- Framework for making flakes simple
# Copyright (C) 2023 Archit Gupta <archit@accelbread.com>
# SPDX-License-Identifier: MIT
{ config, lib, ... }:
let
inherit (builtins) isPath isString;
inherit (lib) mkOption mkOptionType mkIf mkMerge;
inherit (lib.types) lazyAttrsOf nullOr;
template = mkOptionType {
name = "template";
description = "template definition";
descriptionClass = "noun";
check = x: (x ? path) && (isPath x.path) &&
(x ? description) && (isString x.description) &&
((! x ? welcomeText) || (isString x.welcomeText));
};
in
{
options = {
template = mkOption {
type = nullOr template;
default = null;
};
templates = mkOption {
type = lazyAttrsOf template;
default = { };
};
};
config = mkMerge [
(mkIf (config.template != null) {
templates.default = config.template;
})
(mkIf (config.templates != { }) {
outputs = { inherit (config) templates; };
})
];
}

View File

@ -4,65 +4,69 @@
localInputs:
let
inherit (builtins) intersectAttrs isPath readDir;
inherit (localInputs.nixpkgs.lib) attrNames attrVals attrValues
callPackageWith composeManyExtensions concat concatStringsSep filter
filterAttrs findFirst foldAttrs foldl functionArgs genAttrs hasSuffix
isFunction isList isString listToAttrs mapAttrs mapAttrsToList mapAttrs'
mergeAttrs nameValuePair optional optionalAttrs parseDrvName pathExists pipe
recursiveUpdate removePrefix removeSuffix zipAttrsWith;
inherit (builtins) isAttrs isPath readDir;
inherit (localInputs.nixpkgs.lib) attrNames composeManyExtensions
filter findFirst genAttrs getValues hasSuffix isFunction isList mapAttrs
mapAttrsToList pathExists pipe removePrefix removeSuffix evalModules
mkDefault mkOptionType singleton;
inherit (localInputs.nixpkgs.lib.types) coercedTo functionTo listOf;
/* Attributes in flakelite's lib output.
*/
exports = {
inherit mkFlake systems importDir autoImport autoImportAttrs defaultPkgName
supportedSystem mergeModules moduleAttrs rootAttrs ensureFn callFn
filterArgs callPkg callPkgs tryImport mkApp mkCheck liftFn2 fnConcat
fnMergeAttrs;
};
builtinModules = mapAttrsToList (k: _: ./builtinModules + ("/" + k))
(readDir ./builtinModules);
/* Module which is always included as first module.
*/
baseModule = { inputs, root, args }: {
# Ensures nixpkgs and flakelite are available for modules.
inputs = {
flakelite = localInputs.self;
inherit (localInputs) nixpkgs;
};
withOverlay = final: prev: {
# Allows access to flakelite lib functions from package sets.
# Also adds pkgs-specific additional args.
flakelite = args // {
# Inputs with system auto-selected.
# i.e. inputs.self.packages.${system} -> inputs'.self.packages
inputs' = mapAttrs
(_: mapAttrs
(_: v: v.${prev.system} or { }))
inputs;
# 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};
};
mkFlake = src: root: (evalModules {
specialArgs.modulesPath = ./builtinModules;
modules = builtinModules ++ [
{ inputs.nixpkgs = mkDefault localInputs.nixpkgs; }
{ _module.args = { inherit src flakelite; }; }
root
];
}).config.outputs;
flakelite = {
inherit mkFlake supportedSystem importDir autoImport;
types = {
overlay = mkOptionType {
name = "overlay";
description = "nixpkgs overlay";
descriptionClass = "noun";
check = isFunction;
merge = _: defs: composeManyExtensions (getValues defs);
};
# These attrs are important enough for top-level pkg set.
inherit (final.flakelite) inputs inputs';
packageDef = mkOptionType {
name = "packageDef";
description = "package definition";
descriptionClass = "noun";
check = isFunction;
};
path = mkOptionType {
name = "path";
description = "path";
descriptionClass = "noun";
check = isPath;
};
module = mkOptionType {
name = "module";
description = "module";
descriptionClass = "noun";
check = x: isPath x || isFunction x || isAttrs x;
merge = _: defs: { imports = getValues defs; };
};
optListOf = elemType: coercedTo elemType singleton (listOf elemType);
optFunctionTo = elemType: coercedTo elemType (x: _: x)
(functionTo elemType);
};
};
builtinModules = attrValues (importDir ./builtin-modules);
supportedSystem = { lib, stdenv, ... }:
lib.meta.availableOn stdenv.hostPlatform;
/* 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".
*/
importDir = path: genAttrs
(pipe (readDir path) [
attrNames
@ -74,12 +78,6 @@ let
(p: import (path + (if pathExists
(path + "/_${p}.nix") then "/_${p}.nix" else "/${p}.nix")));
/* Try to load a name 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 name could not be
loaded. If name is a list, tries all names in that list.
*/
autoImport = dir: name:
if isList name
then findFirst (x: x != null) null (map (autoImport dir) name)
@ -92,428 +90,8 @@ let
then importDir (dir + "/${name}")
else null;
/* List of attrs that can be provided by a module.
*/
moduleAttrs = [
"inputs"
"withOverlay"
"withOverlays"
"nixpkgsConfig"
"package"
"packages"
"devTools"
"shellHook"
"devShell"
"devShells"
"env"
"overlay"
"overlays"
"app"
"apps"
"checks"
"nixosModule"
"nixosModules"
"nixosConfigurations"
"homeModule"
"homeModules"
"homeConfigurations"
"template"
"templates"
"formatters"
];
/* List of handled attrs in root module. Does not have optionally checked
attrs like name, description, or license, or attrs used by modules.
*/
rootAttrs = moduleAttrs ++ [
"systems"
"perSystem"
"outputs"
"nixDir"
];
/* Alternative names for autoloading root attrs.
*/
attrAliases = {
"nixosConfigurations" = [ "nixos" ];
"homeConfigurations" = [ "home" ];
# Lets `nix-shell` shell.nix be used automatically if no ./nix dir.
"devShell" = [ "shell" ];
"nixpkgsConfig" = [ "nixpkgs/config" ];
};
/* Generate an attrset by importing attrs from dir. Filters null values.
Alternative names from aliases are used.
*/
autoImportAttrs = dir: attrs: aliases:
filterAttrs (_: v: v != null) (genAttrs attrs
(attr: autoImport dir ([ attr ] ++ aliases.${attr} or [ ])));
/* 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;
/* 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
*/
liftFn2 = fn: a: b: args: fn (a args) (b args);
fnConcat = liftFn2 concat;
fnMergeAttrs = liftFn2 mergeAttrs;
/* Takes a function which takes a list, and returns a binary function.
Type: mkBinary :: ([a] -> b) -> a -> a -> b
*/
mkBinary = fn: a: b: fn [ a b ];
/* Merges attrsets of overlays, combining overlays with same name.
*/
mergeOverlayAttrs = mkBinary (zipAttrsWith (_: composeManyExtensions));
fnConcatScripts = liftFn2 (mkBinary (concatStringsSep "\n"));
mergeModules = a: b: mapAttrs (n: v: v a.${n} b.${n}) {
inputs = mergeAttrs;
withOverlays = concat;
nixpkgsConfig = mergeAttrs;
packages = mergeAttrs;
devTools = fnConcat;
shellHook = fnConcatScripts;
devShells = fnMergeAttrs;
env = fnMergeAttrs;
overlays = mergeOverlayAttrs;
apps = fnMergeAttrs;
checks = fnMergeAttrs;
nixosModules = mergeAttrs;
nixosConfigurations = mergeAttrs;
homeModules = mergeAttrs;
homeConfigurations = mergeAttrs;
templates = mergeAttrs;
formatters = fnMergeAttrs;
};
/* 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.
*/
callFn = args: f:
let
f' = ensureFn f;
in
if functionArgs f' == { } then f' args
else f' (intersectAttrs (functionArgs f) args);
/* Ensures x is called with only it's required parameters.
*/
filterArgs = x: args: callFn args x;
/* If arg is importable, imports it, else returns arg as is.
*/
tryImport = x: if (isPath x) || (isString x) then import x else x;
/* Like callFn, but intended for functions that return derivations. Uses
callPackage so will make result overridable. Trys importing the value if a
path.
*/
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);
/* Gets the name for the default package using value set in root module or
derivation attrs.
*/
defaultPkgName = root: pkg: root.name or pkg.pname or (parseDrvName pkg).name;
/* Merges elements of a list of sets recursively. Each element can optionally
be a function that takes the merged previous elements.
*/
recUpdateSets = foldl (acc: x: recursiveUpdate acc ((ensureFn x) acc)) { };
isApp = x: (x ? type) && (x.type == "app") && (x ? program);
/* Turns app into an app attribute, if it is not already. Passes it pkgs if it
is a function.
*/
mkApp = pkgs: app:
let
app' = callFn pkgs app;
in
if isApp app' then app'
else { type = "app"; program = "${app'}"; };
/* 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. Passes cmd pkgs if it is its a function.
*/
mkCheck = pkgs: src: name: cmd:
let
cmd' = callFn pkgs cmd;
in
if pkgs.lib.isDerivation cmd' then cmd' else
pkgs.runCommand "check-${name}" { } ''
cp --no-preserve=mode -r ${src} src
cd src
${cmd'}
touch $out
'';
/* 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;
systems = rec {
# Linux systems with binary caches.
linuxDefault = [
"x86_64-linux"
"aarch64-linux"
];
# Linux systems supported as a host platform.
linuxAll = linuxDefault ++ [
"armv6l-linux"
"armv7l-linux"
"i686-linux"
];
};
/* Creates flake outputs; takes the path of the flake and the root module.
*/
mkFlake = src: root:
let
# These are passed to modules and non-system-dependent module attrs.
nonSysArgs = exports // {
args = nonSysArgs;
flakelite = exports;
root = root';
inherit src;
inherit (merged) inputs;
inherit (merged.inputs.nixpkgs) lib;
};
applyNonSysArgs = callFn nonSysArgs;
moduleAttrDefaults = {
inputs = { };
withOverlays = [ ];
nixpkgsConfig = { };
packages = { };
devTools = _: [ ];
shellHook = _: "";
devShells = _: { };
env = _: { };
overlays = { };
apps = _: { };
checks = _: { };
nixosModules = { };
nixosConfigurations = { };
homeModules = { };
homeConfigurations = { };
templates = { };
formatters = _: { };
};
normalizeModule = module:
let
module' = moduleAttrDefaults // module;
in
module' // {
withOverlays = (applyNonSysArgs module'.withOverlays)
++ optional (module' ? withOverlay) module'.withOverlay;
nixpkgsConfig = applyNonSysArgs module'.nixpkgsConfig;
packages = (applyNonSysArgs module'.packages)
// optionalAttrs (module' ? package) {
default = module'.package;
};
devTools = filterArgs module'.devTools;
shellHook = filterArgs module'.shellHook;
devShells = fnMergeAttrs (filterArgs module'.devShells)
(_: optionalAttrs (module' ? devShell) {
default = module'.devShell;
});
env = filterArgs module'.env;
overlays = (applyNonSysArgs module'.overlays)
// optionalAttrs (module' ? overlay) {
default = module'.overlay;
};
apps = fnMergeAttrs (filterArgs module'.apps)
(_: optionalAttrs (module' ? app) {
default = module'.app;
});
checks = filterArgs module'.checks;
nixosModules = (applyNonSysArgs module'.nixosModules)
// optionalAttrs (module' ? nixosModule) {
default = module'.nixosModule;
};
nixosConfigurations = applyNonSysArgs module'.nixosConfigurations;
homeModules = (applyNonSysArgs module'.homeModules)
// optionalAttrs (module' ? homeModule) {
default = module'.homeModule;
};
homeConfigurations = applyNonSysArgs module'.homeConfigurations;
templates = (applyNonSysArgs module'.templates)
// optionalAttrs (module' ? template) {
default = module'.template;
};
formatters = filterArgs module'.formatters;
};
# Root module with autoloads, normalization, and additional attrs.
root' =
let
appliedRoot = applyNonSysArgs root;
nixDir = appliedRoot.nixDir or (src + /nix);
fullRoot = (autoImportAttrs nixDir rootAttrs attrAliases)
// appliedRoot;
in
normalizeModule fullRoot // {
modules = fullRoot.modules or
(pipe (removeAttrs root'.inputs [ "self" ]) [
(filterAttrs (_: v: v ? flakeliteModule))
(mapAttrsToList (_: v: v.flakeliteModule))
]);
systems = applyNonSysArgs (fullRoot.systems or systems.linuxDefault);
perSystem = filterArgs (fullRoot.perSystem or { });
outputs = applyNonSysArgs (fullRoot.outputs or { });
inherit nixDir;
};
modules = [ baseModule ] ++ builtinModules ++ root'.modules;
# Merge result of all the modules.
merged = foldl mergeModules moduleAttrDefaults
((map (m: normalizeModule (applyNonSysArgs m)) modules)
++ [ root' ]);
# Returns package set for a system.
pkgsFor = system: import merged.inputs.nixpkgs {
inherit system;
config = merged.nixpkgsConfig;
overlays = merged.withOverlays ++ [
(final: _: callPkgs final merged.packages)
];
};
# Attrset mapping systems to corresponding package sets.
systemPkgs = listToAttrs (map
(system: nameValuePair system (pkgsFor system))
root'.systems);
# 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.
eachSystem = fn: foldAttrs mergeAttrs { } (map
(system: mapAttrs
(_: v: { ${system} = v; })
(fn systemPkgs.${system}))
root'.systems);
# Replaces the "default" attr in set with the default package name.
replaceDefault = set:
if set ? default
then (removeAttrs set [ "default" ]) //
{ ${defaultPkgName root' set.default} = set.default; }
else set;
in
recUpdateSets [
(optionalAttrs (merged.packages != { }) ({
# Packages in overlay depend on withOverlays which are not in pkg set.
overlays.default = final: _: callPkgs
(final.appendOverlays merged.withOverlays)
(replaceDefault merged.packages);
} // eachSystem (pkgs: rec {
# Packages are generated in overlay on system pkgs; grab from there.
packages = filterAttrs (_: supportedSystem pkgs)
(intersectAttrs merged.packages pkgs);
checks = mapAttrs' (n: nameValuePair ("packages-" + n)) packages;
})))
(prev: optionalAttrs (merged.overlays != { }) ({
overlays = mergeOverlayAttrs (prev.overlays or { }) merged.overlays;
}))
(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
${toString (mapAttrsToList
(n: v: "${n}) ${callFn pkgs v} \"$f\";;")
(merged.formatters pkgs))}
esac
fi
done &>/dev/null
'';
checks.formatting = mkCheck pkgs src "formatting" ''
${lib.getExe formatter} .
${pkgs.diffutils}/bin/diff -qr ${src} . |\
sed 's/Files .* and \(.*\) differ/File \1 not formatted/g'
'';
}))
(eachSystem (pkgs:
let
checks = mapAttrs (mkCheck pkgs src) (merged.checks pkgs);
in
optionalAttrs (checks != { }) { inherit checks; }))
(eachSystem (pkgs:
let
apps = mapAttrs (_: mkApp pkgs) (merged.apps pkgs);
in
optionalAttrs (apps != { }) { inherit apps; }))
(optionalAttrs (merged.nixosModules != { }) {
inherit (merged) nixosModules;
})
(optionalAttrs (merged.nixosConfigurations != { }) {
inherit (merged) nixosConfigurations;
checks = recUpdateSets (mapAttrsToList
(n: v: {
${v.config.nixpkgs.system}."nixos-${n}" =
v.config.system.build.toplevel;
})
merged.nixosConfigurations);
})
(optionalAttrs (merged.homeModules != { }) {
inherit (merged) homeModules;
})
(optionalAttrs (merged.homeConfigurations != { }) {
inherit (merged) homeConfigurations;
checks = recUpdateSets (mapAttrsToList
(n: v: {
${v.config.nixpkgs.system}."home-${n}" = v.activationPackage;
})
merged.homeConfigurations);
})
(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;
shellHook = merged.shellHook pkgs;
});
} // (callPkgs pkgs (merged.devShells pkgs));
}))
(eachSystem root'.perSystem)
(_: root'.outputs)
];
in
{
lib = exports;
lib = flakelite;
__functor = _: mkFlake;
}

View File

@ -2,16 +2,16 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1680665430,
"narHash": "sha256-MTVhTukwza1Jlq2gECITZPFnhROmylP2uv3O3cSqQCE=",
"lastModified": 1692447944,
"narHash": "sha256-fkJGNjEmTPvqBs215EQU4r9ivecV5Qge5cF/QDLVn3U=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "5233fd2ba76a3accb5aaa999c00509a11fd0793c",
"rev": "d680ded26da5cf104dd2735a51e88d2d8f487b4d",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-22.11",
"ref": "nixos-unstable",
"type": "indirect"
}
},

View File

@ -3,13 +3,10 @@
# SPDX-License-Identifier: MIT
{
inputs.nixpkgs.url = "nixpkgs/nixos-22.11";
inputs.nixpkgs.url = "nixpkgs/nixos-unstable";
outputs = inputs:
let
flakelite = import ./. inputs;
in
flakelite ./. {
nixDir = ./.;
outputs = _: flakelite;
};
flakelite ./. { outputs = flakelite; };
}

View File

@ -1,15 +0,0 @@
{ writeShellApplication
, coreutils
, nix
, git
, gnutar
}:
writeShellApplication {
name = "pre-commit";
runtimeInputs = [ coreutils nix git gnutar ];
text = ''
TREE=$(mktemp -d)
git archive "$(git write-tree)" | tar -xC "$TREE"
nix flake check "$TREE"
'';
}