This does break the API of being able to import any lib file and get its libs, however I'm not sure people did this. I made this while exploring being able to swap out docFn with a stub in #2305, to avoid functor performance problems. I don't know if that is going to move forward (or if it is a problem or not,) but after doing all this work figured I'd put it up anyway :) Two notable advantages to this approach: 1. when a lib inherits another lib's functions, it doesn't automatically get put in to the scope of lib 2. when a lib implements a new obscure functions, it doesn't automatically get put in to the scope of lib Using the test script (later in this commit) I got the following diff on the API: + diff master fixed-lib 11764a11765,11766 > .types.defaultFunctor > .types.defaultTypeMerge 11774a11777,11778 > .types.isOptionType > .types.isType 11781a11786 > .types.mkOptionType 11788a11794 > .types.setType 11795a11802 > .types.types This means that this commit _adds_ to the API, however I can't find a way to fix these last remaining discrepancies. At least none are _removed_. Test script (run with nix-repl in the PATH): #!/bin/sh set -eux repl() { suff=${1:-} echo "(import ./lib)$suff" \ | nix-repl 2>&1 } attrs_to_check() { repl "${1:-}" \ | tr ';' $'\n' \ | grep "\.\.\." \ | cut -d' ' -f2 \ | sed -e "s/^/${1:-}./" \ | sort } summ() { repl "${1:-}" \ | tr ' ' $'\n' \ | sort \ | uniq } deep_summ() { suff="${1:-}" depth="${2:-4}" depth=$((depth - 1)) summ "$suff" for attr in $(attrs_to_check "$suff" | grep -v "types.types"); do if [ $depth -eq 0 ]; then summ "$attr" | sed -e "s/^/$attr./" else deep_summ "$attr" "$depth" | sed -e "s/^/$attr./" fi done } ( cd nixpkgs #git add . #git commit -m "Auto-commit, sorry" || true git checkout fixed-lib deep_summ > ../fixed-lib git checkout master deep_summ > ../master ) if diff master fixed-lib; then echo "SHALLOW MATCH!" fi ( cd nixpkgs git checkout fixed-lib repl .types )
		
			
				
	
	
		
			133 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			133 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
# Nixpkgs/NixOS option handling.
 | 
						||
{ lib }:
 | 
						||
 | 
						||
with lib.trivial;
 | 
						||
with lib.lists;
 | 
						||
with lib.attrsets;
 | 
						||
with lib.strings;
 | 
						||
 | 
						||
rec {
 | 
						||
 | 
						||
  isOption = lib.isType "option";
 | 
						||
  mkOption =
 | 
						||
    { default ? null # Default value used when no definition is given in the configuration.
 | 
						||
    , defaultText ? null # Textual representation of the default, for in the manual.
 | 
						||
    , example ? null # Example value used in the manual.
 | 
						||
    , description ? null # String describing the option.
 | 
						||
    , type ? null # Option type, providing type-checking and value merging.
 | 
						||
    , apply ? null # Function that converts the option value to something else.
 | 
						||
    , internal ? null # Whether the option is for NixOS developers only.
 | 
						||
    , visible ? null # Whether the option shows up in the manual.
 | 
						||
    , readOnly ? null # Whether the option can be set only once
 | 
						||
    , options ? null # Obsolete, used by types.optionSet.
 | 
						||
    } @ attrs:
 | 
						||
    attrs // { _type = "option"; };
 | 
						||
 | 
						||
  mkEnableOption = name: mkOption {
 | 
						||
    default = false;
 | 
						||
    example = true;
 | 
						||
    description = "Whether to enable ${name}.";
 | 
						||
    type = lib.types.bool;
 | 
						||
  };
 | 
						||
 | 
						||
  # This option accept anything, but it does not produce any result.  This
 | 
						||
  # is useful for sharing a module across different module sets without
 | 
						||
  # having to implement similar features as long as the value of the options
 | 
						||
  # are not expected.
 | 
						||
  mkSinkUndeclaredOptions = attrs: mkOption ({
 | 
						||
    internal = true;
 | 
						||
    visible = false;
 | 
						||
    default = false;
 | 
						||
    description = "Sink for option definitions.";
 | 
						||
    type = mkOptionType {
 | 
						||
      name = "sink";
 | 
						||
      check = x: true;
 | 
						||
      merge = loc: defs: false;
 | 
						||
    };
 | 
						||
    apply = x: throw "Option value is not readable because the option is not declared.";
 | 
						||
  } // attrs);
 | 
						||
 | 
						||
  mergeDefaultOption = loc: defs:
 | 
						||
    let list = getValues defs; in
 | 
						||
    if length list == 1 then head list
 | 
						||
    else if all isFunction list then x: mergeDefaultOption loc (map (f: f x) list)
 | 
						||
    else if all isList list then concatLists list
 | 
						||
    else if all isAttrs list then foldl' lib.mergeAttrs {} list
 | 
						||
    else if all isBool list then foldl' lib.or false list
 | 
						||
    else if all isString list then lib.concatStrings list
 | 
						||
    else if all isInt list && all (x: x == head list) list then head list
 | 
						||
    else throw "Cannot merge definitions of `${showOption loc}' given in ${showFiles (getFiles defs)}.";
 | 
						||
 | 
						||
  mergeOneOption = loc: defs:
 | 
						||
    if defs == [] then abort "This case should never happen."
 | 
						||
    else if length defs != 1 then
 | 
						||
      throw "The unique option `${showOption loc}' is defined multiple times, in ${showFiles (getFiles defs)}."
 | 
						||
    else (head defs).value;
 | 
						||
 | 
						||
  /* "Merge" option definitions by checking that they all have the same value. */
 | 
						||
  mergeEqualOption = loc: defs:
 | 
						||
    if defs == [] then abort "This case should never happen."
 | 
						||
    else foldl' (val: def:
 | 
						||
      if def.value != val then
 | 
						||
        throw "The option `${showOption loc}' has conflicting definitions, in ${showFiles (getFiles defs)}."
 | 
						||
      else
 | 
						||
        val) (head defs).value defs;
 | 
						||
 | 
						||
  getValues = map (x: x.value);
 | 
						||
  getFiles = map (x: x.file);
 | 
						||
 | 
						||
 | 
						||
  # Generate documentation template from the list of option declaration like
 | 
						||
  # the set generated with filterOptionSets.
 | 
						||
  optionAttrSetToDocList = optionAttrSetToDocList' [];
 | 
						||
 | 
						||
  optionAttrSetToDocList' = prefix: options:
 | 
						||
    concatMap (opt:
 | 
						||
      let
 | 
						||
        docOption = rec {
 | 
						||
          name = showOption opt.loc;
 | 
						||
          description = opt.description or (throw "Option `${name}' has no description.");
 | 
						||
          declarations = filter (x: x != unknownModule) opt.declarations;
 | 
						||
          internal = opt.internal or false;
 | 
						||
          visible = opt.visible or true;
 | 
						||
          readOnly = opt.readOnly or false;
 | 
						||
          type = opt.type.description or null;
 | 
						||
        }
 | 
						||
        // (if opt ? example then { example = scrubOptionValue opt.example; } else {})
 | 
						||
        // (if opt ? default then { default = scrubOptionValue opt.default; } else {})
 | 
						||
        // (if opt ? defaultText then { default = opt.defaultText; } else {});
 | 
						||
 | 
						||
        subOptions =
 | 
						||
          let ss = opt.type.getSubOptions opt.loc;
 | 
						||
          in if ss != {} then optionAttrSetToDocList' opt.loc ss else [];
 | 
						||
      in
 | 
						||
        [ docOption ] ++ subOptions) (collect isOption options);
 | 
						||
 | 
						||
 | 
						||
  /* This function recursively removes all derivation attributes from
 | 
						||
     `x' except for the `name' attribute.  This is to make the
 | 
						||
     generation of `options.xml' much more efficient: the XML
 | 
						||
     representation of derivations is very large (on the order of
 | 
						||
     megabytes) and is not actually used by the manual generator. */
 | 
						||
  scrubOptionValue = x:
 | 
						||
    if isDerivation x then
 | 
						||
      { type = "derivation"; drvPath = x.name; outPath = x.name; name = x.name; }
 | 
						||
    else if isList x then map scrubOptionValue x
 | 
						||
    else if isAttrs x then mapAttrs (n: v: scrubOptionValue v) (removeAttrs x ["_args"])
 | 
						||
    else x;
 | 
						||
 | 
						||
 | 
						||
  /* For use in the ‘example’ option attribute.  It causes the given
 | 
						||
     text to be included verbatim in documentation.  This is necessary
 | 
						||
     for example values that are not simple values, e.g.,
 | 
						||
     functions. */
 | 
						||
  literalExample = text: { _type = "literalExample"; inherit text; };
 | 
						||
 | 
						||
 | 
						||
  /* Helper functions. */
 | 
						||
  showOption = concatStringsSep ".";
 | 
						||
  showFiles = files: concatStringsSep " and " (map (f: "`${f}'") files);
 | 
						||
  unknownModule = "<unknown-file>";
 | 
						||
 | 
						||
}
 |