mirror of
https://gitlab.com/rensa-nix/devshell.git
synced 2026-02-01 23:05:08 +01:00
chore: initial commit
This commit is contained in:
commit
fbacfc149b
21 changed files with 748 additions and 0 deletions
19
lib/default.nix
Normal file
19
lib/default.nix
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
pkgs,
|
||||
lib ? pkgs.lib,
|
||||
...
|
||||
}: rec {
|
||||
modules = import ./modules;
|
||||
eval = {config, ...}: let
|
||||
res = lib.evalModules {
|
||||
modules = [config modules];
|
||||
specialArgs = {
|
||||
inherit pkgs;
|
||||
};
|
||||
};
|
||||
in {
|
||||
inherit (res) config options;
|
||||
shell = res.config.shell.finalPackage;
|
||||
};
|
||||
mkShell = config: (eval {inherit config;}).shell;
|
||||
}
|
||||
5
lib/flake.nix
Normal file
5
lib/flake.nix
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
outputs = i: {
|
||||
lib = import ./.;
|
||||
};
|
||||
}
|
||||
6
lib/modules/default.nix
Normal file
6
lib/modules/default.nix
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
imports = [
|
||||
./main.nix
|
||||
./env.nix
|
||||
];
|
||||
}
|
||||
99
lib/modules/env.nix
Normal file
99
lib/modules/env.nix
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
{
|
||||
lib,
|
||||
pkgs,
|
||||
config,
|
||||
...
|
||||
}: let
|
||||
inherit (lib) filter assertMsg head length escapeShellArg types mkOption concatStringsSep;
|
||||
|
||||
envToBash = {
|
||||
name,
|
||||
value,
|
||||
eval,
|
||||
prefix,
|
||||
...
|
||||
} @ args: let
|
||||
vals = filter (key: args.${key} != null && args.${key} != false) [
|
||||
"eval"
|
||||
"prefix"
|
||||
"unset"
|
||||
"value"
|
||||
];
|
||||
valType = head vals;
|
||||
in
|
||||
assert assertMsg (
|
||||
(length vals) > 0
|
||||
) "[devshell/env]: ${name} expected one of (value|eval|prefix|unset) to be set.";
|
||||
assert assertMsg ((length vals) < 2)
|
||||
"[devshell/env]: ${name} expected only one of (value|eval|prefix|unset) to be set. Not ${toString vals}";
|
||||
assert assertMsg (!(name == "PATH" && valType == "value")) "[devshell/env]: ${name} should not override the value. Use 'prefix' instead.";
|
||||
if valType == "value"
|
||||
then "export ${name}=${escapeShellArg (toString value)}"
|
||||
else if valType == "eval"
|
||||
then "export ${name}=${eval}"
|
||||
else if valType == "prefix"
|
||||
then ''export ${name}=$(${pkgs.coreutils}/bin/realpath --canonicalize-missing "${prefix}")''${${name}+:''${${name}}}''
|
||||
else if valType == "unset"
|
||||
then ''unset ${name}''
|
||||
else throw "[devshell] BUG in the env module. This should never be reached.";
|
||||
|
||||
envType = types.submodule ({name, ...}: {
|
||||
options = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
default = name;
|
||||
};
|
||||
value = mkOption {
|
||||
type = with types;
|
||||
nullOr (oneOf [
|
||||
str
|
||||
int
|
||||
bool
|
||||
package
|
||||
]);
|
||||
default = null;
|
||||
};
|
||||
|
||||
eval = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Like value but not evaluated by Bash. This allows to inject other
|
||||
variable names or even commands using the `$()` notation.
|
||||
'';
|
||||
example = "$OTHER_VAR";
|
||||
};
|
||||
|
||||
prefix = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Prepend to PATH-like environment variables.
|
||||
|
||||
For example name = "PATH"; prefix = "bin"; will expand the path of
|
||||
./bin and prepend it to the PATH, separated by ':'.
|
||||
'';
|
||||
example = "bin";
|
||||
};
|
||||
|
||||
unset = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''Unset the env variable'';
|
||||
};
|
||||
};
|
||||
});
|
||||
in {
|
||||
options.env = mkOption {
|
||||
type = types.attrsOf envType;
|
||||
default = {};
|
||||
description = '''';
|
||||
};
|
||||
|
||||
config = {
|
||||
env = {
|
||||
"XDG_DATA_DIRS".eval = "$DEVSHELL_DIR/share:\${XDG_DATA_DIRS-}";
|
||||
};
|
||||
enterShellCommands."env".text = concatStringsSep "\n" (map envToBash (builtins.attrValues config.env));
|
||||
};
|
||||
}
|
||||
113
lib/modules/main.nix
Normal file
113
lib/modules/main.nix
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
{
|
||||
pkgs,
|
||||
lib ? pkgs.lib,
|
||||
config,
|
||||
...
|
||||
}: let
|
||||
inherit (lib) mapAttrs mkOption types textClosureMap id attrNames;
|
||||
mkNakedShell = pkgs.callPackage ./../utils/mkNakedShell.nix {};
|
||||
|
||||
addAttributeName = prefix:
|
||||
mapAttrs (
|
||||
k: v:
|
||||
v
|
||||
// {
|
||||
text = ''
|
||||
#### ${prefix}.${k}
|
||||
${v.text}
|
||||
'';
|
||||
}
|
||||
);
|
||||
|
||||
envBash =
|
||||
pkgs.writeText "devshell-env.bash"
|
||||
# sh
|
||||
''
|
||||
export DEVSHELL_DIR=@DEVSHELL_DIR@
|
||||
# nicely handle it at the front or between/end
|
||||
PATH=''${PATH#/path-not-set:}
|
||||
PATH=''${PATH#:/path-not-set}
|
||||
export PATH=$DEVSHELL_DIR/bin:$PATH
|
||||
|
||||
${textClosureMap
|
||||
id
|
||||
(addAttributeName "startup" config.enterShellCommands)
|
||||
(attrNames config.enterShellCommands)}
|
||||
|
||||
# interactive session
|
||||
if [[ $- == *i* ]]; then
|
||||
true
|
||||
${textClosureMap
|
||||
id
|
||||
(addAttributeName "interactive" config.interactiveShellCommands)
|
||||
(attrNames config.interactiveShellCommands)}
|
||||
fi # interactive session
|
||||
|
||||
unset DEVSHELL_DIR
|
||||
'';
|
||||
|
||||
entryType = types.submodule {
|
||||
options = {
|
||||
text = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
Script to run.
|
||||
'';
|
||||
};
|
||||
|
||||
deps = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = ''
|
||||
A list of other steps that this one depends on.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
in {
|
||||
options = {
|
||||
shell = {
|
||||
finalPackage = mkOption {
|
||||
type = types.package;
|
||||
internal = true;
|
||||
};
|
||||
finalProfile = mkOption {
|
||||
type = types.package;
|
||||
internal = true;
|
||||
};
|
||||
};
|
||||
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
default = "devshell";
|
||||
};
|
||||
packages = mkOption {
|
||||
type = types.listOf types.package;
|
||||
default = [];
|
||||
};
|
||||
enterShellCommands = mkOption {
|
||||
type = types.attrsOf entryType;
|
||||
default = {};
|
||||
};
|
||||
interactiveShellCommands = mkOption {
|
||||
type = types.attrsOf entryType;
|
||||
default = {};
|
||||
};
|
||||
};
|
||||
|
||||
config.shell = {
|
||||
finalProfile = pkgs.buildEnv {
|
||||
name = "${config.name}-profile";
|
||||
paths = config.packages;
|
||||
postBuild = ''
|
||||
substitute ${envBash} $out/env.bash --subst-var-by DEVSHELL_DIR $out
|
||||
# NOTE: maybe also add an entrypoint shell script at $out/bin/devshell-${config.name}?
|
||||
'';
|
||||
meta.mainProgram = "devshell-${config.name}";
|
||||
};
|
||||
finalPackage = mkNakedShell {
|
||||
name = config.name;
|
||||
profile = config.shell.finalProfile;
|
||||
};
|
||||
};
|
||||
}
|
||||
70
lib/utils/mkNakedShell.nix
Normal file
70
lib/utils/mkNakedShell.nix
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
{
|
||||
bashInteractive,
|
||||
coreutils,
|
||||
stdenv,
|
||||
writeTextFile,
|
||||
}: let
|
||||
bashPath = "${bashInteractive}/bin/bash";
|
||||
nakedStdenv = writeTextFile {
|
||||
name = "naked-stdenv";
|
||||
destination = "/setup";
|
||||
text = ''
|
||||
# Fix for `nix develop`
|
||||
: ''${outputs:=out}
|
||||
|
||||
runHook() {
|
||||
eval "$shellHook"
|
||||
unset runHook
|
||||
}
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
name,
|
||||
# A path to a buildEnv that will be loaded by the shell.
|
||||
# We assume that the buildEnv contains an ./env.bash script.
|
||||
profile,
|
||||
meta ? {},
|
||||
passthru ? {},
|
||||
}:
|
||||
(derivation {
|
||||
inherit name;
|
||||
|
||||
system = stdenv.hostPlatform.system;
|
||||
|
||||
# `nix develop` actually checks and uses builder. And it must be bash.
|
||||
builder = bashPath;
|
||||
|
||||
# Bring in the dependencies on `nix-build`
|
||||
args = [
|
||||
"-ec"
|
||||
"${coreutils}/bin/ln -s ${profile} $out; exit 0"
|
||||
];
|
||||
|
||||
# $stdenv/setup is loaded by nix-shell during startup.
|
||||
# https://github.com/nixos/nix/blob/377345e26f1ac4bbc87bb21debcc52a1d03230aa/src/nix-build/nix-build.cc#L429-L432
|
||||
stdenv = nakedStdenv;
|
||||
|
||||
# The shellHook is loaded directly by `nix develop`. But nix-shell
|
||||
# requires that other trampoline.
|
||||
shellHook = ''
|
||||
# Remove all the unnecessary noise that is set by the build env
|
||||
unset NIX_BUILD_TOP NIX_BUILD_CORES NIX_STORE
|
||||
unset TEMP TEMPDIR TMP TMPDIR
|
||||
unset builder out shellHook stdenv system name
|
||||
# Flakes stuff
|
||||
unset dontAddDisableDepTrack outputs
|
||||
|
||||
# For `nix develop`. We get /noshell on Linux and /sbin/nologin on macOS.
|
||||
if [[ "$SHELL" == "/noshell" || "$SHELL" == "/sbin/nologin" ]]; then
|
||||
export SHELL=${bashPath}
|
||||
fi
|
||||
|
||||
# Load the environment
|
||||
source "${profile}/env.bash"
|
||||
'';
|
||||
})
|
||||
// {
|
||||
inherit meta passthru;
|
||||
}
|
||||
// passthru
|
||||
Loading…
Add table
Add a link
Reference in a new issue