Skip to content

Commit 8eb363e

Browse files
zimbatmdeemp
authored andcommitted
WIP: implement packagesFrom
1 parent 6b2554d commit 8eb363e

File tree

3 files changed

+280
-0
lines changed

3 files changed

+280
-0
lines changed

devshell.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ packages = [
2525
"hyperfine",
2626
]
2727

28+
# Expose all the dependencies from a package to the environment.
29+
packagesFrom = [
30+
"direnv"
31+
]
32+
2833
# Declare commands that are available in the environment.
2934
[[commands]]
3035
help = "prints hello"

mkDevShell/options.nix

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
{ lib, pkgs, config, ... }:
2+
with lib;
3+
let
4+
resolveKey = key:
5+
let
6+
attrs = builtins.filter builtins.isString (builtins.split "\\." key);
7+
op = sum: attr: sum.${attr} or (throw "package \"${key}\" not found");
8+
in
9+
builtins.foldl' op pkgs attrs
10+
;
11+
12+
pad = str: num:
13+
if num > 0 then
14+
pad "${str} " (num - 1)
15+
else
16+
str
17+
;
18+
19+
# Nix strings only support \t, \r and \n as escape codes, so actually store
20+
# the literal escape "ESC" code.
21+
esc = "";
22+
ansiOrange = "${esc}[38;5;202m";
23+
ansiReset = "${esc}[0m";
24+
ansiBold = "${esc}[1m";
25+
26+
commandsToMenu = commands:
27+
let
28+
commandLengths =
29+
map ({ name, ... }: builtins.stringLength name) commands;
30+
31+
maxCommandLength =
32+
builtins.foldl'
33+
(max: v: if v > max then v else max)
34+
0
35+
commandLengths
36+
;
37+
38+
commandCategories = lib.unique (
39+
(zipAttrsWithNames [ "category" ] (name: vs: vs) commands).category
40+
);
41+
42+
commandByCategoriesSorted =
43+
builtins.attrValues (lib.genAttrs
44+
commandCategories
45+
(category: lib.nameValuePair category (builtins.sort
46+
(a: b: a.name < b.name)
47+
(builtins.filter
48+
(x: x.category == category)
49+
commands
50+
)
51+
))
52+
);
53+
54+
opCat = { name, value }:
55+
let
56+
opCmd = { name, help, ... }:
57+
let
58+
len = maxCommandLength - (builtins.stringLength name);
59+
in
60+
if help == null || help == "" then
61+
" ${name}"
62+
else
63+
" ${pad name len} - ${help}";
64+
in
65+
"\n${ansiBold}[${name}]${ansiReset}\n\n" + builtins.concatStringsSep "\n" (map opCmd value);
66+
in
67+
builtins.concatStringsSep "\n" (map opCat commandByCategoriesSorted) + "\n"
68+
;
69+
70+
# Because we want to be able to push pure JSON-like data into the
71+
# environment.
72+
strOrPackage =
73+
types.coercedTo types.str resolveKey types.package;
74+
75+
# These are all the options available for the commands.
76+
commandOptions = {
77+
name = mkOption {
78+
type = types.str;
79+
# default = null;
80+
description = ''
81+
Name of this command. Defaults to attribute name in commands.
82+
'';
83+
};
84+
85+
category = mkOption {
86+
type = types.str;
87+
default = "general commands";
88+
description = ''
89+
Set a free text category under which this command is grouped
90+
and shown in the help menu.
91+
'';
92+
};
93+
94+
help = mkOption {
95+
type = types.nullOr types.str;
96+
default = null;
97+
description = ''
98+
Describes what the command does in one line of text.
99+
'';
100+
};
101+
102+
command = mkOption {
103+
type = types.nullOr types.str;
104+
default = null;
105+
description = ''
106+
If defined, it will define a script for the command.
107+
'';
108+
};
109+
110+
package = mkOption {
111+
type = types.nullOr strOrPackage;
112+
default = null;
113+
description = ''
114+
Used to bring in a specific package. This package will be added to the
115+
environment.
116+
'';
117+
};
118+
};
119+
120+
# Returns a list of all the input derivation ... for a derivation.
121+
inputsOf = drv:
122+
(drv.buildInputs or [ ]) ++
123+
(drv.nativeBuildInputs or [ ]) ++
124+
(drv.propagatedBuildInputs or [ ]) ++
125+
(drv.propagatedNativeBuildInputs or [ ])
126+
;
127+
in
128+
{
129+
options = {
130+
name = mkOption {
131+
type = types.str;
132+
default = "devshell";
133+
description = ''
134+
Name of the shell environment. It usually maps to the project name.
135+
'';
136+
};
137+
138+
# TODO: rename motd to something better.
139+
motd = mkOption {
140+
type = types.str;
141+
default = ''
142+
${ansiOrange}🔨 Welcome to ${config.name}${ansiReset}
143+
$(menu)
144+
'';
145+
description = ''
146+
Message Of The Day.
147+
148+
This is the welcome message that is being printed when the user opens
149+
the shell.
150+
'';
151+
};
152+
153+
commands = mkOption {
154+
type = types.listOf (types.submodule { options = commandOptions; });
155+
default = [ ];
156+
description = ''
157+
Add commands to the environment.
158+
'';
159+
example = literalExample ''
160+
[
161+
{
162+
help = "print hello";
163+
name = "hello";
164+
alias = "echo hello";
165+
}
166+
167+
{
168+
help = "used to format nix code";
169+
package = pkgs.nixpkgs-fmt;
170+
}
171+
]
172+
'';
173+
};
174+
175+
bash = mkOption {
176+
type = types.submodule {
177+
options = {
178+
extra = mkOption {
179+
type = types.lines;
180+
default = "";
181+
description = ''
182+
Extra commands to run in bash on environment startup.
183+
'';
184+
};
185+
186+
interactive = mkOption {
187+
type = types.lines;
188+
default = "";
189+
description = ''
190+
Same as shellHook, but is only executed on interactive shells.
191+
192+
This is useful to setup things such as custom prompt commands.
193+
'';
194+
};
195+
};
196+
};
197+
default = { };
198+
};
199+
200+
env = mkOption {
201+
type = types.attrs;
202+
default = { };
203+
description = ''
204+
Environment variables to add to the environment.
205+
206+
If the value is null, it will unset the environment variable.
207+
Otherwise, the value will be converted to string before being set.
208+
'';
209+
example = {
210+
GO111MODULE = "on";
211+
HTTP_PORT = 8080;
212+
};
213+
};
214+
215+
packages = mkOption {
216+
type = types.listOf strOrPackage;
217+
default = [ ];
218+
description = ''
219+
A list of packages to add to the environment.
220+
221+
If the packages are passed as string, they will be retried from
222+
nixpkgs with the same attribute name.
223+
'';
224+
};
225+
226+
packagesFrom = mkOption {
227+
type = types.listOf strOrPackage;
228+
default = [ ];
229+
description = ''
230+
Add all the build dependencies from the listed packages to the
231+
environment.
232+
'';
233+
};
234+
235+
};
236+
237+
config = {
238+
commands = [
239+
{
240+
help = "prints this menu";
241+
name = "menu";
242+
command = ''
243+
menu="${commandsToMenu config.commands}"
244+
echo -e "$menu"
245+
'';
246+
}
247+
];
248+
249+
packages =
250+
# Get all the packages from the commands
251+
builtins.filter (x: x != null) (map (x: x.package) config.commands)
252+
# Get all the packages from packagesFrom
253+
++ builtins.foldl' (sum: drv: sum ++ (inputsOf drv)) [ ] config.packagesFrom
254+
;
255+
};
256+
}

modules/devshell.nix

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,14 @@ let
196196
'';
197197
};
198198

199+
# Returns a list of all the input derivation ... for a derivation.
200+
inputsOf = drv:
201+
(drv.buildInputs or [ ]) ++
202+
(drv.nativeBuildInputs or [ ]) ++
203+
(drv.propagatedBuildInputs or [ ]) ++
204+
(drv.propagatedNativeBuildInputs or [ ])
205+
;
206+
199207
in
200208
{
201209
options.devshell = {
@@ -291,6 +299,15 @@ in
291299
'';
292300
};
293301

302+
packagesFrom = mkOption {
303+
type = types.listOf strOrPackage;
304+
default = [ ];
305+
description = ''
306+
Add all the build dependencies from the listed packages to the
307+
environment.
308+
'';
309+
};
310+
294311
shell = mkOption {
295312
internal = true;
296313
type = types.package;
@@ -331,6 +348,8 @@ in
331348
config.devshell = {
332349
package = devshell_dir;
333350

351+
packages = foldl' (sum: drv: sum ++ (inputsOf drv)) [ ] cfg.packagesFrom;
352+
334353
startup = {
335354
motd = noDepEntry ''
336355
__devshell-motd() {

0 commit comments

Comments
 (0)