-
Notifications
You must be signed in to change notification settings - Fork 1
/
workspaces.nix
195 lines (169 loc) · 5.46 KB
/
workspaces.nix
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
{ pkgs }:
with pkgs.lib;
let
eval_modules = modules:
(evalModules {
modules = [{ _module.args = { inherit pkgs; }; }] ++ modules;
}).config;
base_module = { config, ... }: {
imports = [
modules/git.nix
modules/github.nix
modules/shell_nix.nix
modules/tools.nix
modules/vim.nix
modules/xdg.nix
];
options = {
name = mkOption {
type = types.str;
default = null;
description =
"Workspace name. Defaults to the attribute name used to define it.";
};
init_script = mkOption {
type = types.lines;
default = "";
description = "Run when the workspace is activated for the first time.";
};
activation_script = mkOption {
type = types.lines;
default = "";
description = "Script run when entering a workspace.";
};
command = mkOption {
type = types.str;
default = "${pkgs.bashInteractive}/bin/bash";
description = "Command to run after activation.";
};
buildInputs = mkOption {
type = types.listOf types.package;
default = [ ];
description = "Workspace dependencies.";
};
cache_dir = mkOption {
type = types.str;
default = ".cache/workspaces/${config.name}";
description = ''
Directory for per-workspace cache, relative to the home directory.
Used to store history files and other unimportant things.
'';
};
activation_command = mkOption {
type = types.str;
default = config.command;
description =
"Command run at the end of the activation script. The default is to run 'command'.";
};
};
config = {
activation_script = ''
export WORKSPACE=${config.name}
export HISTFILE=$HOME/${config.cache_dir}/bash_history
'';
};
};
# Options for the '_default' attribute.
global_configuration = { config, ... }: {
options = {
prefix = mkOption {
type = types.str;
default = "$HOME/w";
description = ''
Base path under which workspaces are located. Can contain bash
substitutions, which will be evaluated when the entry script is
called.
'';
};
defaults = mkOption {
type = with types; oneOf [ attrs (functionTo attrs) ];
# type = types.deferredModule {}; # Too recent
default = { };
description = ''
Configuration added to every workspaces. Useful to configure
'activation_command' or to add basic tools to 'buildInputs'.
'';
};
};
};
make_workspace = { defaults, ... }: name: configuration:
let default_name = { name = mkDefault name; };
in eval_modules [
base_module
default_name # Base modules
defaults # From global configuration
configuration # User configuration
];
stdenv = pkgs.stdenvNoCC;
# Generate the 'workspace-init' and 'workspace-activate' script for the
# workspace.
make_drv = w:
stdenv.mkDerivation {
name = strings.sanitizeDerivationName w.name;
inherit (w) buildInputs;
passAsFile = [ "init_script" "activation_script" ];
init_script = ''
#!${pkgs.runtimeShell}
${w.init_script}
'';
activation_script = ''
mkdir -p "$HOME/${w.cache_dir}"
${w.activation_script}
${w.activation_command}
'';
# Similar to 'pkgs.writeShellScriptBin', inlined to avoid generating many
# store paths.
# Some build variables defined by stdenv are hardcoded into the
# activation script to avoid needing 'nix-shell': 'PATH' and some
# variables used by pkg-config, gcc and ld wrappers.
buildPhase = ''
mkdir -p $out/bin
mv $init_scriptPath $out/bin/workspace-init
chmod +x $out/bin/workspace-init
${stdenv.shell} -n $out/bin/workspace-init
keep_var() { for v in "$@"; do echo "export $v=''\'''${!v}'"; done; }
{
echo "#!${pkgs.runtimeShell}"
echo "PATH='$PATH':\"\$PATH\""
for v in ''${!NIX_*}; do
if [[ $v = *_FOR_TARGET || $v = *_TARGET_TARGET_* ]]; then
keep_var $v
fi
done
keep_var ''${!PKG_CONFIG_PATH_*}
cat $activation_scriptPath
} > $out/bin/workspace-activate
chmod +x $out/bin/workspace-activate
${stdenv.shell} -n $out/bin/workspace-activate
'';
preferLocalBuild = true;
allowSubstitutes = false;
phases = [ "buildPhase" "fixupPhase" ];
};
# Hard code workspace derivation paths into the script
make_entry_script = { prefix, ... }:
workspaces:
pkgs.writeShellScriptBin "workspaces" ''
declare -A workspaces
workspaces=(
${
concatMapStrings (drv: ''
["${drv.name}"]="${drv}"
'') workspaces
}
)
PREFIX=${prefix}
${readFile ./workspaces.sh}
'';
in config:
# Entry point. 'config' is a attributes set of workspaces. See 'base_module'
# above for the low-level options and './modules' for modules.
let
global_config =
eval_modules [ global_configuration (config._default or { }) ];
workspaces_def = builtins.removeAttrs config [ "_default" ];
workspaces =
mapAttrsToList (make_workspace global_config) workspaces_def;
workspaces_drv = map make_drv workspaces;
entry_script = make_entry_script global_config workspaces_drv;
in entry_script