From 8eb363ead6f20a2f3b7029c6cf37d7618b20cb9d Mon Sep 17 00:00:00 2001 From: zimbatm Date: Mon, 24 Aug 2020 14:43:55 +0200 Subject: [PATCH 1/3] 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 7e4c5545..d4e98dda 100644 --- a/modules/devshell.nix +++ b/modules/devshell.nix @@ -196,6 +196,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 = { @@ -291,6 +299,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; @@ -331,6 +348,8 @@ in config.devshell = { package = devshell_dir; + packages = foldl' (sum: drv: sum ++ (inputsOf drv)) [ ] cfg.packagesFrom; + startup = { motd = noDepEntry '' __devshell-motd() { From 68116410caa46d56cd4bbcffdb043afee6d2579b Mon Sep 17 00:00:00 2001 From: Danila Danko Date: Sat, 10 Jun 2023 18:34:12 +0300 Subject: [PATCH 2/3] add option --- modules/back-compat.nix | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/back-compat.nix b/modules/back-compat.nix index e9eeb99d..6413aef6 100644 --- a/modules/back-compat.nix +++ b/modules/back-compat.nix @@ -37,12 +37,19 @@ with lib; type = types.listOf strOrPackage; default = [ ]; }; + + packagesFrom = mkOption { + internal = true; + type = types.listOf strOrPackage; + default = [ ]; + }; }; # Copy the values over to the devshell module config.devshell = { packages = config.packages; + packagesFrom = config.packagesFrom; startup.bash_extra = noDepEntry config.bash.extra; interactive.bash_interactive = noDepEntry config.bash.interactive; } From 5326be2574427d540cb5840cf2e650d8d884d302 Mon Sep 17 00:00:00 2001 From: Danila Danko Date: Sun, 11 Jun 2023 22:50:27 +0300 Subject: [PATCH 3/3] rm references to mkDevShell --- README.md | 12 +- mkDevShell/options.nix | 256 ----------------------------------------- overlay.nix | 2 +- 3 files changed, 6 insertions(+), 264 deletions(-) delete mode 100644 mkDevShell/options.nix diff --git a/README.md b/README.md index d3d6cd91..4cd00527 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ new environment variables, which then need to be unset. The `stdenv` itself contains either GCC or Clang which makes it hard to select a specific C compiler. -This is why `mkDevShell` builds its environment from a `builtins.derivation`. +This is why `mkShell` builds its environment from a `builtins.derivation`. direnv loads will change from: ``` @@ -65,11 +65,10 @@ those are useful yet: When entering a random project, it's useful to get a quick view of what commands are available. -When running `nix-shell` or `nix develop`, `mkDevShell` prints a welcome -message: +When running `nix-shell` or `nix develop`, `mkShell` prints a welcome message: ``` -### 🔨 Welcome to mkDevShell #### +🔨 Welcome to devshell # Commands @@ -90,13 +89,12 @@ handle 80% of the use-cases and falling back on Nix is always possible. Life is not complete otherwise. Huhu. Packages that contain bash completions will automatically be loaded by -`mkDevShell` in `nix-shell` or `nix develop` modes. +`mkShell` in `nix-shell` or `nix develop` modes. ### Capture development dependencies in CI With a CI + Binary cache setup, one often wants to be able to capture all the -build inputs of a `shell.nix`. Before, `pkgs.mkShell` would even refuse to -build! (my fault really). With `pkgs.mkDevShell`, capturing all of the +build inputs of a `shell.nix`. With `mkShell` capturing all of the development dependencies is as easy as: ```sh diff --git a/mkDevShell/options.nix b/mkDevShell/options.nix deleted file mode 100644 index 095c4ff2..00000000 --- a/mkDevShell/options.nix +++ /dev/null @@ -1,256 +0,0 @@ -{ 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/overlay.nix b/overlay.nix index 0e054241..2d195552 100644 --- a/overlay.nix +++ b/overlay.nix @@ -1,4 +1,4 @@ -# Import this overlay in your project to add devshell and mkDevShell +# Import this overlay in your project to add devshell final: prev: { devshell = import ./. { nixpkgs = final; };