-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy path.devenv.flake.nix
More file actions
513 lines (490 loc) · 20.5 KB
/
Copy path.devenv.flake.nix
File metadata and controls
513 lines (490 loc) · 20.5 KB
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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
{
inputs =
let
vars = {
version = "1.11.2";
system = "aarch64-darwin";
devenv_root = "/Users/agucova/repos/tacet";
project_input_ref = "path:/Users/agucova/repos/tacet";
devenv_dotfile = "/Users/agucova/repos/tacet/.devenv";
devenv_dotfile_path = ./.devenv;
devenv_tmpdir = "/var/folders/lk/jgh211f55z72kb1lr9zp7d1c0000gn/T/";
devenv_runtime = "/var/folders/lk/jgh211f55z72kb1lr9zp7d1c0000gn/T/devenv-19c1a42";
devenv_istesting = false;
devenv_direnvrc_latest_version = 1;
container_name = null;
active_profiles = [
];
hostname = "tachyon";
username = "agucova";
git_root = "/Users/agucova/repos/tacet";
secretspec = null;
};
in
{
git-hooks.url = "github:cachix/git-hooks.nix";
git-hooks.inputs.nixpkgs.follows = "nixpkgs";
pre-commit-hooks.follows = "git-hooks";
nixpkgs.url = "github:cachix/devenv-nixpkgs/rolling";
devenv.url = "github:cachix/devenv?dir=src/modules";
}
// (
if builtins.pathExists (vars.devenv_dotfile_path + "/flake.json") then
builtins.fromJSON (builtins.readFile (vars.devenv_dotfile_path + "/flake.json"))
else
{ }
);
outputs =
{ nixpkgs, ... }@inputs:
let
vars = {
version = "1.11.2";
system = "aarch64-darwin";
devenv_root = "/Users/agucova/repos/tacet";
project_input_ref = "path:/Users/agucova/repos/tacet";
devenv_dotfile = "/Users/agucova/repos/tacet/.devenv";
devenv_dotfile_path = ./.devenv;
devenv_tmpdir = "/var/folders/lk/jgh211f55z72kb1lr9zp7d1c0000gn/T/";
devenv_runtime = "/var/folders/lk/jgh211f55z72kb1lr9zp7d1c0000gn/T/devenv-19c1a42";
devenv_istesting = false;
devenv_direnvrc_latest_version = 1;
container_name = null;
active_profiles = [
];
hostname = "tachyon";
username = "agucova";
git_root = "/Users/agucova/repos/tacet";
secretspec = null;
};
devenv =
if builtins.pathExists (vars.devenv_dotfile_path + "/devenv.json") then
builtins.fromJSON (builtins.readFile (vars.devenv_dotfile_path + "/devenv.json"))
else
{ };
systems = [
"x86_64-linux"
"aarch64-linux"
"x86_64-darwin"
"aarch64-darwin"
];
# Function to create devenv configuration for a specific system with profiles support
mkDevenvForSystem =
targetSystem:
let
getOverlays =
inputName: inputAttrs:
map (
overlay:
let
input =
inputs.${inputName} or (throw "No such input `${inputName}` while trying to configure overlays.");
in
input.overlays.${overlay}
or (throw "Input `${inputName}` has no overlay called `${overlay}`. Supported overlays: ${nixpkgs.lib.concatStringsSep ", " (builtins.attrNames input.overlays)}")
) inputAttrs.overlays or [ ];
overlays = nixpkgs.lib.flatten (nixpkgs.lib.mapAttrsToList getOverlays (devenv.inputs or { }));
permittedUnfreePackages =
devenv.nixpkgs.per-platform."${targetSystem}".permittedUnfreePackages
or devenv.nixpkgs.permittedUnfreePackages or [ ];
pkgs = import nixpkgs {
system = targetSystem;
config = {
allowUnfree =
devenv.nixpkgs.per-platform."${targetSystem}".allowUnfree or devenv.nixpkgs.allowUnfree
or devenv.allowUnfree or false;
allowBroken =
devenv.nixpkgs.per-platform."${targetSystem}".allowBroken or devenv.nixpkgs.allowBroken
or devenv.allowBroken or false;
cudaSupport =
devenv.nixpkgs.per-platform."${targetSystem}".cudaSupport or devenv.nixpkgs.cudaSupport or false;
cudaCapabilities =
devenv.nixpkgs.per-platform."${targetSystem}".cudaCapabilities or devenv.nixpkgs.cudaCapabilities
or [ ];
permittedInsecurePackages =
devenv.nixpkgs.per-platform."${targetSystem}".permittedInsecurePackages
or devenv.nixpkgs.permittedInsecurePackages or devenv.permittedInsecurePackages or [ ];
allowUnfreePredicate =
if (permittedUnfreePackages != [ ]) then
(pkg: builtins.elem (nixpkgs.lib.getName pkg) permittedUnfreePackages)
else
(_: false);
};
inherit overlays;
};
inherit (pkgs) lib;
importModule =
path:
if lib.hasPrefix "./" path then
if lib.hasSuffix ".nix" path then
./. + (builtins.substring 1 255 path)
else
./. + (builtins.substring 1 255 path) + "/devenv.nix"
else if lib.hasPrefix "../" path then
# For parent directory paths, concatenate with /.
# ./. refers to the directory containing this file (project root)
# So ./. + "/../shared" = <project-root>/../shared
if lib.hasSuffix ".nix" path then ./. + "/${path}" else ./. + "/${path}/devenv.nix"
else
let
paths = lib.splitString "/" path;
name = builtins.head paths;
input = inputs.${name} or (throw "Unknown input ${name}");
subpath = "/${lib.concatStringsSep "/" (builtins.tail paths)}";
devenvpath = "${input}" + subpath;
devenvdefaultpath = devenvpath + "/devenv.nix";
in
if lib.hasSuffix ".nix" devenvpath then
devenvpath
else if builtins.pathExists devenvdefaultpath then
devenvdefaultpath
else
throw (devenvdefaultpath + " file does not exist for input ${name}.");
# Phase 1: Base evaluation to extract profile definitions
baseProject = pkgs.lib.evalModules {
specialArgs = inputs // {
inherit inputs;
};
modules = [
(
{ config, ... }:
{
_module.args.pkgs = pkgs.appendOverlays (config.overlays or [ ]);
}
)
(inputs.devenv.modules + /top-level.nix)
(
{ options, ... }:
{
config.devenv = lib.mkMerge [
{
cliVersion = vars.version;
root = vars.devenv_root;
dotfile = vars.devenv_dotfile;
}
(pkgs.lib.optionalAttrs (builtins.hasAttr "tmpdir" options.devenv) {
tmpdir = vars.devenv_tmpdir;
})
(pkgs.lib.optionalAttrs (builtins.hasAttr "isTesting" options.devenv) {
isTesting = vars.devenv_istesting;
})
(pkgs.lib.optionalAttrs (builtins.hasAttr "runtime" options.devenv) {
runtime = vars.devenv_runtime;
})
(pkgs.lib.optionalAttrs (builtins.hasAttr "direnvrcLatestVersion" options.devenv) {
direnvrcLatestVersion = vars.devenv_direnvrc_latest_version;
})
];
}
)
(
{ options, ... }:
{
config = lib.mkMerge [
(pkgs.lib.optionalAttrs (builtins.hasAttr "git" options) {
git.root = vars.git_root;
})
];
}
)
(pkgs.lib.optionalAttrs (vars.container_name != null) {
container.isBuilding = pkgs.lib.mkForce true;
containers.${vars.container_name}.isBuilding = true;
})
]
++ (map importModule (devenv.imports or [ ]))
++ [
(if builtins.pathExists ./devenv.nix then ./devenv.nix else { })
(devenv.devenv or { })
(if builtins.pathExists ./devenv.local.nix then ./devenv.local.nix else { })
(
if builtins.pathExists (vars.devenv_dotfile_path + "/cli-options.nix") then
import (vars.devenv_dotfile_path + "/cli-options.nix")
else
{ }
)
];
};
# Phase 2: Extract and apply profiles using extendModules with priority overrides
project =
let
# Build ordered list of profile names: hostname -> user -> manual
manualProfiles = vars.active_profiles;
currentHostname = vars.hostname;
currentUsername = vars.username;
hostnameProfiles = lib.optional (
currentHostname != ""
&& builtins.hasAttr currentHostname (baseProject.config.profiles.hostname or { })
) "hostname.${currentHostname}";
userProfiles = lib.optional (
currentUsername != "" && builtins.hasAttr currentUsername (baseProject.config.profiles.user or { })
) "user.${currentUsername}";
# Ordered list of profiles to activate
orderedProfiles = hostnameProfiles ++ userProfiles ++ manualProfiles;
# Resolve profile extends with cycle detection
resolveProfileExtends =
profileName: visited:
if builtins.elem profileName visited then
throw "Circular dependency detected in profile extends: ${lib.concatStringsSep " -> " visited} -> ${profileName}"
else
let
profile = getProfileConfig profileName;
extends = profile.extends or [ ];
newVisited = visited ++ [ profileName ];
extendedProfiles = lib.flatten (map (name: resolveProfileExtends name newVisited) extends);
in
extendedProfiles ++ [ profileName ];
# Get profile configuration by name from baseProject
getProfileConfig =
profileName:
if lib.hasPrefix "hostname." profileName then
let
name = lib.removePrefix "hostname." profileName;
in
baseProject.config.profiles.hostname.${name}
else if lib.hasPrefix "user." profileName then
let
name = lib.removePrefix "user." profileName;
in
baseProject.config.profiles.user.${name}
else
let
availableProfiles = builtins.attrNames (baseProject.config.profiles or { });
hostnameProfiles = map (n: "hostname.${n}") (
builtins.attrNames (baseProject.config.profiles.hostname or { })
);
userProfiles = map (n: "user.${n}") (builtins.attrNames (baseProject.config.profiles.user or { }));
allAvailableProfiles = availableProfiles ++ hostnameProfiles ++ userProfiles;
in
baseProject.config.profiles.${profileName}
or (throw "Profile '${profileName}' not found. Available profiles: ${lib.concatStringsSep ", " allAvailableProfiles}");
# Fold over ordered profiles to build final list with extends
expandedProfiles = lib.foldl' (
acc: profileName:
let
allProfileNames = resolveProfileExtends profileName [ ];
in
acc ++ allProfileNames
) [ ] orderedProfiles;
# Map over expanded profiles and apply priorities
allPrioritizedModules = lib.imap0 (
index: profileName:
let
# Decrement priority for each profile (lower = higher precedence)
# Start with the next lowest priority after the default priority for values (100)
profilePriority = (lib.modules.defaultOverridePriority - 1) - index;
profileConfig = getProfileConfig profileName;
# Check if an option type needs explicit override to resolve conflicts
# Only apply overrides to LEAF values (scalars), not collection types that can merge
typeNeedsOverride =
type:
if type == null then
false
else
let
typeName = type.name or type._type or "";
# True leaf types that need priority resolution when they conflict
isLeafType = builtins.elem typeName [
"str"
"int"
"bool"
"enum"
"path"
"package"
"float"
"anything"
];
in
if isLeafType then
true
else if typeName == "nullOr" then
# For nullOr, check the wrapped type recursively
let
innerType =
type.elemType
or (if type ? nestedTypes && type.nestedTypes ? elemType then type.nestedTypes.elemType else null);
in
if innerType != null then typeNeedsOverride innerType else false
else
# Everything else (collections, submodules, etc.) should merge naturally
false;
# Check if a config path needs explicit override
pathNeedsOverride =
optionPath:
let
# Try direct option first
directOption = lib.attrByPath optionPath null baseProject.options;
in
if directOption != null && lib.isOption directOption then
typeNeedsOverride directOption.type
else if optionPath != [ ] then
# Check parent for freeform type
let
parentPath = lib.init optionPath;
parentOption = lib.attrByPath parentPath null baseProject.options;
in
if parentOption != null && lib.isOption parentOption then
let
# Look for freeform type:
# 1. Standard location: type.freeformType (primary)
# 2. Nested location: type.nestedTypes.freeformType (evaluated form)
freeformType = parentOption.type.freeformType or parentOption.type.nestedTypes.freeformType or null;
elementType =
if freeformType ? elemType then
freeformType.elemType
else if freeformType ? nestedTypes && freeformType.nestedTypes ? elemType then
freeformType.nestedTypes.elemType
else
freeformType;
in
typeNeedsOverride elementType
else
false
else
false;
# Support overriding both plain attrset modules and functions
applyModuleOverride =
config:
if builtins.isFunction config then
let
wrapper = args: applyOverrideRecursive (config args) [ ];
in
lib.mirrorFunctionArgs config wrapper
else
applyOverrideRecursive config [ ];
# Apply overrides recursively based on option types
applyOverrideRecursive =
config: optionPath:
if lib.isAttrs config && config ? _type then
config # Don't touch values with existing type metadata
else if lib.isAttrs config then
lib.mapAttrs (name: value: applyOverrideRecursive value (optionPath ++ [ name ])) config
else if pathNeedsOverride optionPath then
lib.mkOverride profilePriority config
else
config;
# Apply priority overrides recursively to the deferredModule imports structure
prioritizedConfig = (
profileConfig.module
// {
imports = lib.map (
importItem:
importItem
// {
imports = lib.map (nestedImport: applyModuleOverride nestedImport) (importItem.imports or [ ]);
}
) (profileConfig.module.imports or [ ]);
}
);
in
prioritizedConfig
) expandedProfiles;
in
if allPrioritizedModules == [ ] then
baseProject
else
baseProject.extendModules { modules = allPrioritizedModules; };
config = project.config;
options = pkgs.nixosOptionsDoc {
options = builtins.removeAttrs project.options [ "_module" ];
warningsAreErrors = false;
# Unpack Nix types, e.g. literalExpression, mDoc.
transformOptions =
let
isDocType =
v:
builtins.elem v [
"literalDocBook"
"literalExpression"
"literalMD"
"mdDoc"
];
in
lib.attrsets.mapAttrs (
_: v:
if v ? _type && isDocType v._type then
v.text
else if v ? _type && v._type == "derivation" then
v.name
else
v
);
};
# Recursively search for outputs in the config.
# This is used when not building a specific output by attrpath.
build =
options: config:
lib.concatMapAttrs (
name: option:
if lib.isOption option then
let
typeName = option.type.name or "";
in
if
builtins.elem typeName [
"output"
"outputOf"
]
then
{ ${name} = config.${name}; }
else
{ }
else if builtins.isAttrs option && !lib.isDerivation option then
let
v = build option config.${name};
in
if v != { } then
{
${name} = v;
}
else
{ }
else
{ }
) options;
in
{
inherit
config
options
build
project
;
shell = config.shell;
packages = {
optionsJSON = options.optionsJSON;
# deprecated
inherit (config)
info
procfileScript
procfileEnv
procfile
;
ci = config.ciDerivation;
};
};
# Generate per-system devenv configurations
perSystem = nixpkgs.lib.genAttrs systems mkDevenvForSystem;
# Default devenv for the current system
currentSystemDevenv = perSystem.${vars.system};
in
{
devShell = nixpkgs.lib.genAttrs systems (s: perSystem.${s}.shell);
packages = nixpkgs.lib.genAttrs systems (s: perSystem.${s}.packages);
# Per-system devenv configurations
devenv = {
# Default devenv for the current system
inherit (currentSystemDevenv)
config
options
build
shell
packages
project
;
# Per-system devenv configurations
inherit perSystem;
};
# Legacy build output
build = currentSystemDevenv.build currentSystemDevenv.options currentSystemDevenv.config;
};
}