From 1a291a50a818b8c0a6e198110a02e85ab8082c01 Mon Sep 17 00:00:00 2001 From: zimbatm Date: Mon, 24 Aug 2020 14:43:55 +0200 Subject: [PATCH] WIP: implement packagesFrom --- devshell.toml | 5 + mkDevShell/options.nix | 256 +++++++++++++++++++++++++++++++++++++++++ modules/devshell.nix | 19 +++ 3 files changed, 280 insertions(+) create mode 100644 mkDevShell/options.nix diff --git a/devshell.toml b/devshell.toml index 9b32d56e..22626880 100644 --- a/devshell.toml +++ b/devshell.toml @@ -25,6 +25,11 @@ packages = [ "hyperfine", ] +# Expose all the dependencies from a package to the environment. +packagesFrom = [ + "direnv" +] + # Declare commands that are available in the environment. [[commands]] help = "prints hello" diff --git a/mkDevShell/options.nix b/mkDevShell/options.nix new file mode 100644 index 00000000..095c4ff2 --- /dev/null +++ b/mkDevShell/options.nix @@ -0,0 +1,256 @@ +{ lib, pkgs, config, ... }: +with lib; +let + resolveKey = key: + let + attrs = builtins.filter builtins.isString (builtins.split "\\." key); + op = sum: attr: sum.${attr} or (throw "package \"${key}\" not found"); + in + builtins.foldl' op pkgs attrs + ; + + pad = str: num: + if num > 0 then + pad "${str} " (num - 1) + else + str + ; + + # Nix strings only support \t, \r and \n as escape codes, so actually store + # the literal escape "ESC" code. + esc = ""; + ansiOrange = "${esc}[38;5;202m"; + ansiReset = "${esc}[0m"; + ansiBold = "${esc}[1m"; + + commandsToMenu = commands: + let + commandLengths = + map ({ name, ... }: builtins.stringLength name) commands; + + maxCommandLength = + builtins.foldl' + (max: v: if v > max then v else max) + 0 + commandLengths + ; + + commandCategories = lib.unique ( + (zipAttrsWithNames [ "category" ] (name: vs: vs) commands).category + ); + + commandByCategoriesSorted = + builtins.attrValues (lib.genAttrs + commandCategories + (category: lib.nameValuePair category (builtins.sort + (a: b: a.name < b.name) + (builtins.filter + (x: x.category == category) + commands + ) + )) + ); + + opCat = { name, value }: + let + opCmd = { name, help, ... }: + let + len = maxCommandLength - (builtins.stringLength name); + in + if help == null || help == "" then + " ${name}" + else + " ${pad name len} - ${help}"; + in + "\n${ansiBold}[${name}]${ansiReset}\n\n" + builtins.concatStringsSep "\n" (map opCmd value); + in + builtins.concatStringsSep "\n" (map opCat commandByCategoriesSorted) + "\n" + ; + + # Because we want to be able to push pure JSON-like data into the + # environment. + strOrPackage = + types.coercedTo types.str resolveKey types.package; + + # These are all the options available for the commands. + commandOptions = { + name = mkOption { + type = types.str; + # default = null; + description = '' + Name of this command. Defaults to attribute name in commands. + ''; + }; + + category = mkOption { + type = types.str; + default = "general commands"; + description = '' + Set a free text category under which this command is grouped + and shown in the help menu. + ''; + }; + + help = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Describes what the command does in one line of text. + ''; + }; + + command = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + If defined, it will define a script for the command. + ''; + }; + + package = mkOption { + type = types.nullOr strOrPackage; + default = null; + description = '' + Used to bring in a specific package. This package will be added to the + environment. + ''; + }; + }; + + # Returns a list of all the input derivation ... for a derivation. + inputsOf = drv: + (drv.buildInputs or [ ]) ++ + (drv.nativeBuildInputs or [ ]) ++ + (drv.propagatedBuildInputs or [ ]) ++ + (drv.propagatedNativeBuildInputs or [ ]) + ; +in +{ + options = { + name = mkOption { + type = types.str; + default = "devshell"; + description = '' + Name of the shell environment. It usually maps to the project name. + ''; + }; + + # TODO: rename motd to something better. + motd = mkOption { + type = types.str; + default = '' + ${ansiOrange}🔨 Welcome to ${config.name}${ansiReset} + $(menu) + ''; + description = '' + Message Of The Day. + + This is the welcome message that is being printed when the user opens + the shell. + ''; + }; + + commands = mkOption { + type = types.listOf (types.submodule { options = commandOptions; }); + default = [ ]; + description = '' + Add commands to the environment. + ''; + example = literalExample '' + [ + { + help = "print hello"; + name = "hello"; + alias = "echo hello"; + } + + { + help = "used to format nix code"; + package = pkgs.nixpkgs-fmt; + } + ] + ''; + }; + + bash = mkOption { + type = types.submodule { + options = { + extra = mkOption { + type = types.lines; + default = ""; + description = '' + Extra commands to run in bash on environment startup. + ''; + }; + + interactive = mkOption { + type = types.lines; + default = ""; + description = '' + Same as shellHook, but is only executed on interactive shells. + + This is useful to setup things such as custom prompt commands. + ''; + }; + }; + }; + default = { }; + }; + + env = mkOption { + type = types.attrs; + default = { }; + description = '' + Environment variables to add to the environment. + + If the value is null, it will unset the environment variable. + Otherwise, the value will be converted to string before being set. + ''; + example = { + GO111MODULE = "on"; + HTTP_PORT = 8080; + }; + }; + + packages = mkOption { + type = types.listOf strOrPackage; + default = [ ]; + description = '' + A list of packages to add to the environment. + + If the packages are passed as string, they will be retried from + nixpkgs with the same attribute name. + ''; + }; + + packagesFrom = mkOption { + type = types.listOf strOrPackage; + default = [ ]; + description = '' + Add all the build dependencies from the listed packages to the + environment. + ''; + }; + + }; + + config = { + commands = [ + { + help = "prints this menu"; + name = "menu"; + command = '' + menu="${commandsToMenu config.commands}" + echo -e "$menu" + ''; + } + ]; + + packages = + # Get all the packages from the commands + builtins.filter (x: x != null) (map (x: x.package) config.commands) + # Get all the packages from packagesFrom + ++ builtins.foldl' (sum: drv: sum ++ (inputsOf drv)) [ ] config.packagesFrom + ; + }; +} diff --git a/modules/devshell.nix b/modules/devshell.nix index 539dd471..e253ff4d 100644 --- a/modules/devshell.nix +++ b/modules/devshell.nix @@ -140,6 +140,14 @@ let ''; }; + # Returns a list of all the input derivation ... for a derivation. + inputsOf = drv: + (drv.buildInputs or [ ]) ++ + (drv.nativeBuildInputs or [ ]) ++ + (drv.propagatedBuildInputs or [ ]) ++ + (drv.propagatedNativeBuildInputs or [ ]) + ; + in { options.devshell = { @@ -227,6 +235,15 @@ in ''; }; + packagesFrom = mkOption { + type = types.listOf strOrPackage; + default = [ ]; + description = '' + Add all the build dependencies from the listed packages to the + environment. + ''; + }; + shell = mkOption { internal = true; type = types.package; @@ -237,6 +254,8 @@ in config.devshell = { package = devshell_dir; + packages = foldl' (sum: drv: sum ++ (inputsOf drv)) [ ] cfg.packagesFrom; + startup = { motd = noDepEntry '' __devshell-motd() {