lol

Merge pull request #249243 from lf-/jade/declarationsWithLocations

nixos/modules: Add declarationPositions

authored by

Robert Hensing and committed by
GitHub
00e54879 5cbbc68e

+79 -5
+11 -4
lib/modules.nix
··· 537 537 mergeModules' prefix modules 538 538 (concatMap (m: map (config: { file = m._file; inherit config; }) (pushDownProperties m.config)) modules); 539 539 540 - mergeModules' = prefix: options: configs: 540 + mergeModules' = prefix: modules: configs: 541 541 let 542 542 # an attrset 'name' => list of submodules that declare ‘name’. 543 543 declsByName = ··· 554 554 else 555 555 mapAttrs 556 556 (n: option: 557 - [{ inherit (module) _file; options = option; }] 557 + [{ inherit (module) _file; pos = builtins.unsafeGetAttrPos n subtree; options = option; }] 558 558 ) 559 559 subtree 560 560 ) 561 - options); 561 + modules); 562 562 563 563 # The root of any module definition must be an attrset. 564 564 checkedConfigs = ··· 762 762 else res.options; 763 763 in opt.options // res // 764 764 { declarations = res.declarations ++ [opt._file]; 765 + # In the case of modules that are generated dynamically, we won't 766 + # have exact declaration lines; fall back to just the file being 767 + # evaluated. 768 + declarationPositions = res.declarationPositions 769 + ++ (if opt.pos != null 770 + then [opt.pos] 771 + else [{ file = opt._file; line = null; column = null; }]); 765 772 options = submodules; 766 773 } // typeSet 767 - ) { inherit loc; declarations = []; options = []; } opts; 774 + ) { inherit loc; declarations = []; declarationPositions = []; options = []; } opts; 768 775 769 776 /* Merge all the definitions of an option to produce the final 770 777 config value. */
+19 -1
lib/tests/modules.sh
··· 39 39 checkConfigOutput() { 40 40 local outputContains=$1 41 41 shift 42 - if evalConfig "$@" 2>/dev/null | grep --silent "$outputContains" ; then 42 + if evalConfig "$@" 2>/dev/null | grep -E --silent "$outputContains" ; then 43 43 ((++pass)) 44 44 else 45 45 echo 2>&1 "error: Expected result matching '$outputContains', while evaluating" ··· 443 443 # Anonymous modules get deduplicated by key 444 444 checkConfigOutput '^"pear"$' config.once.raw ./merge-module-with-key.nix 445 445 checkConfigOutput '^"pear\\npear"$' config.twice.raw ./merge-module-with-key.nix 446 + 447 + # Declaration positions 448 + # Line should be present for direct options 449 + checkConfigOutput '^10$' options.imported.line10.declarationPositions.0.line ./declaration-positions.nix 450 + checkConfigOutput '/declaration-positions.nix"$' options.imported.line10.declarationPositions.0.file ./declaration-positions.nix 451 + # Generated options may not have line numbers but they will at least get the 452 + # right file 453 + checkConfigOutput '/declaration-positions.nix"$' options.generated.line18.declarationPositions.0.file ./declaration-positions.nix 454 + checkConfigOutput '^null$' options.generated.line18.declarationPositions.0.line ./declaration-positions.nix 455 + # Submodules don't break it 456 + checkConfigOutput '^39$' config.submoduleLine34.submodDeclLine39.0.line ./declaration-positions.nix 457 + checkConfigOutput '/declaration-positions.nix"$' config.submoduleLine34.submodDeclLine39.0.file ./declaration-positions.nix 458 + # New options under freeform submodules get collected into the parent submodule 459 + # (consistent with .declarations behaviour, but weird; notably appears in system.build) 460 + checkConfigOutput '^34|23$' options.submoduleLine34.declarationPositions.0.line ./declaration-positions.nix 461 + checkConfigOutput '^34|23$' options.submoduleLine34.declarationPositions.1.line ./declaration-positions.nix 462 + # nested options work 463 + checkConfigOutput '^30$' options.nested.nestedLine30.declarationPositions.0.line ./declaration-positions.nix 446 464 447 465 cat <<EOF 448 466 ====== module tests ======
+49
lib/tests/modules/declaration-positions.nix
··· 1 + { lib, options, ... }: 2 + let discardPositions = lib.mapAttrs (k: v: v); 3 + in 4 + # unsafeGetAttrPos is unspecified best-effort behavior, so we only want to consider this test on an evaluator that satisfies some basic assumptions about this function. 5 + assert builtins.unsafeGetAttrPos "a" { a = true; } != null; 6 + assert builtins.unsafeGetAttrPos "a" (discardPositions { a = true; }) == null; 7 + { 8 + imports = [ 9 + { 10 + options.imported.line10 = lib.mkOption { 11 + type = lib.types.int; 12 + }; 13 + 14 + # Simulates various patterns of generating modules such as 15 + # programs.firefox.nativeMessagingHosts.ff2mpv. We don't expect to get 16 + # line numbers for these, but we can fall back on knowing the file. 17 + options.generated = discardPositions { 18 + line18 = lib.mkOption { 19 + type = lib.types.int; 20 + }; 21 + }; 22 + 23 + options.submoduleLine34.extraOptLine23 = lib.mkOption { 24 + default = 1; 25 + type = lib.types.int; 26 + }; 27 + } 28 + ]; 29 + 30 + options.nested.nestedLine30 = lib.mkOption { 31 + type = lib.types.int; 32 + }; 33 + 34 + options.submoduleLine34 = lib.mkOption { 35 + default = { }; 36 + type = lib.types.submoduleWith { 37 + modules = [ 38 + ({ options, ... }: { 39 + options.submodDeclLine39 = lib.mkOption { }; 40 + }) 41 + { freeformType = with lib.types; lazyAttrsOf (uniq unspecified); } 42 + ]; 43 + }; 44 + }; 45 + 46 + config = { 47 + submoduleLine34.submodDeclLine39 = (options.submoduleLine34.type.getSubOptions [ ]).submodDeclLine39.declarationPositions; 48 + }; 49 + }