From e72626b0a9fe810c0d56eb5225d843d0210b6525 Mon Sep 17 00:00:00 2001 From: Archit Gupta Date: Wed, 21 Feb 2024 18:43:05 -0800 Subject: [PATCH] Allow apps to be scripts This enables setting the app to arbitrary bash scripts which will be written to a store path and used for the app's program attr. --- builtinModules/apps.nix | 67 +++++++++++++++++++++++++++++------------ default.nix | 12 ++++++-- 2 files changed, 57 insertions(+), 22 deletions(-) diff --git a/builtinModules/apps.nix b/builtinModules/apps.nix index cb94e47..748095f 100644 --- a/builtinModules/apps.nix +++ b/builtinModules/apps.nix @@ -4,31 +4,58 @@ { config, lib, flakelight, genSystems, ... }: let - inherit (lib) isStringLike mapAttrs mkIf mkMerge mkOption mkOptionType; - inherit (lib.types) coercedTo lazyAttrsOf pathInStore; - inherit (lib.options) mergeEqualOption; - inherit (flakelight.types) nullable optFunctionTo; + inherit (builtins) match storeDir; + inherit (lib) defaultFunctor fix isFunction last mapAttrs mergeDefinitions + mkIf mkMerge mkOption mkOptionType; + inherit (lib.types) coercedTo enum lazyAttrsOf + optionDescriptionPhrase pathInStore submodule; + inherit (flakelight.types) nullable optFunctionTo stringLike; - app = mkOptionType { - name = "app"; - description = "flake app"; - descriptionClass = "noun"; - check = x: (x ? type) && (x.type == "app") && - (x ? program) && (pathInStore.check x.program); - merge = mergeEqualOption; + isStorePath = s: match "${storeDir}/[^.][^ \n]*" s != null; + + app = submodule { + options = { + type = mkOption { type = enum [ "app" ]; default = "app"; }; + program = mkOption { type = pathInStore // { check = isStorePath; }; }; + }; }; - stringLike = mkOptionType { - name = "stringLike"; - description = "string-convertible value"; - descriptionClass = "noun"; - check = isStringLike; - merge = mergeEqualOption; - }; + mkApp = name: pkgs: s: + let s' = "${s}"; in { + program = + if isStorePath s' then s' + else "${pkgs.writeShellScript "app-${name}" s'}"; + }; - mkApp = app: { type = "app"; program = "${app}"; }; + parameterize = value: fn: fix fn value; - appType = optFunctionTo (coercedTo stringLike mkApp app); + appType = parameterize app (self': app: (mkOptionType rec { + name = "appType"; + description = + let + targetDesc = optionDescriptionPhrase + (class: class == "noun" || class == "composite") + (coercedTo stringLike (abort "") app); + in + "${targetDesc} or function that evaluates to it"; + descriptionClass = "composite"; + check = x: isFunction x || app.check x || stringLike.check x; + merge = loc: defs: pkgs: + let + targetType = coercedTo stringLike (mkApp (last loc) pkgs) app; + in + (mergeDefinitions loc targetType (map + (fn: { + inherit (fn) file; + value = if isFunction fn.value then fn.value pkgs else fn.value; + }) + defs)).mergedValue; + inherit (app) getSubOptions getSubModules; + substSubModules = m: self' (app.substSubModules m); + functor = (defaultFunctor name) // { wrapped = app; }; + nestedTypes.coercedType = stringLike; + nestedTypes.finalType = app; + })); in { options = { diff --git a/default.nix b/default.nix index a6a2c83..a877776 100644 --- a/default.nix +++ b/default.nix @@ -7,8 +7,8 @@ let inherit (inputs) nixpkgs; inherit (builtins) isAttrs isPath readDir; inherit (nixpkgs.lib) all attrNames composeManyExtensions evalModules filter - findFirst fix genAttrs getValues hasSuffix isFunction isList mapAttrs - mapAttrsToList mkDefault mkOptionType pathExists pipe removePrefix + findFirst fix genAttrs getValues hasSuffix isFunction isList isStringLike + mapAttrs mapAttrsToList mkDefault mkOptionType pathExists pipe removePrefix removeSuffix singleton warn; inherit (nixpkgs.lib.types) coercedTo defaultFunctor functionTo listOf optionDescriptionPhrase; @@ -74,6 +74,14 @@ let merge = mergeOneOption; }; + stringLike = mkOptionType { + name = "stringLike"; + description = "string-convertible value"; + descriptionClass = "noun"; + check = isStringLike; + merge = mergeEqualOption; + }; + module = mkOptionType { name = "module"; description = "module";