1
0
Fork 1
mirror of https://github.com/NixOS/nixpkgs.git synced 2024-12-12 23:55:23 +00:00
nixpkgs/lib/options.nix

187 lines
7.6 KiB
Nix
Raw Normal View History

# Nixpkgs/NixOS option handling.
Convert libs to a fixed-point 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 )
2017-07-29 01:05:35 +01:00
{ lib }:
Convert libs to a fixed-point 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 )
2017-07-29 01:05:35 +01:00
with lib.trivial;
with lib.lists;
with lib.attrsets;
with lib.strings;
rec {
# Returns true when the given argument is an option
#
# Examples:
# isOption 1 // => false
# isOption (mkOption {}) // => true
isOption = lib.isType "option";
# Creates an Option attribute set. mkOption accepts an attribute set with the following keys:
#
# default: Default value used when no definition is given in the configuration.
# defaultText: Textual representation of the default, for in the manual.
# example: Example value used in the manual.
# description: String describing the option.
# type: Option type, providing type-checking and value merging.
# apply: Function that converts the option value to something else.
# internal: Whether the option is for NixOS developers only.
# visible: Whether the option shows up in the manual.
# readOnly: Whether the option can be set only once
# options: Obsolete, used by types.optionSet.
#
# All keys default to `null` when not given.
#
# Examples:
# mkOption { } // => { _type = "option"; }
# mkOption { defaultText = "foo"; } // => { _type = "option"; defaultText = "foo"; }
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.
, relatedPackages ? null # Related packages used in the manual (see `genRelatedPackages` in ../nixos/doc/manual/default.nix).
, 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"; };
# Creates a Option attribute set for a boolean value option i.e an option to be toggled on or off:
#
# Examples:
# mkEnableOption "foo" // => { _type = "option"; default = false; description = "Whether to enable foo."; example = true; type = { ... }; }
mkEnableOption = name: mkOption {
default = false;
example = true;
description = "Whether to enable ${name}.";
type = lib.types.bool;
};
2014-12-21 13:50:46 +00:00
# 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
2012-08-13 19:19:31 +01:00
if length list == 1 then head list
2013-11-12 12:48:19 +00:00
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
2013-11-12 12:48:19 +00:00
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;
# Extracts values of all "value" keys of the given list
#
# Examples:
# getValues [ { value = 1; } { value = 2; } ] // => [ 1 2 ]
# getValues [ ] // => [ ]
getValues = map (x: x.value);
# Extracts values of all "file" keys of the given list
#
# Examples:
# getFiles [ { file = "file1"; } { file = "file2"; } ] // => [ "file1" "file2" ]
# getFiles [ ] // => [ ]
getFiles = map (x: x.file);
# Generate documentation template from the list of option declaration like
# the set generated with filterOptionSets.
2013-10-28 13:25:58 +00:00
optionAttrSetToDocList = optionAttrSetToDocList' [];
optionAttrSetToDocList' = prefix: options:
concatMap (opt:
2013-10-28 13:25:58 +00:00
let
docOption = rec {
loc = opt.loc;
2013-10-28 13:25:58 +00:00
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;
2016-09-07 02:03:32 +01:00
type = opt.type.description or null;
2013-10-28 13:25:58 +00:00
}
// optionalAttrs (opt ? example) { example = scrubOptionValue opt.example; }
// optionalAttrs (opt ? default) { default = scrubOptionValue opt.default; }
// optionalAttrs (opt ? defaultText) { default = opt.defaultText; }
// optionalAttrs (opt ? relatedPackages && opt.relatedPackages != null) { inherit (opt) relatedPackages; };
2013-10-28 13:25:58 +00:00
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:
2013-10-30 15:19:07 +00:00
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. */
# Convert an option, described as a list of the option parts in to a
# safe, human readable version. ie:
#
# (showOption ["foo" "bar" "baz"]) == "foo.bar.baz"
# (showOption ["foo" "bar.baz" "tux"]) == "foo.\"bar.baz\".tux"
showOption = parts: let
escapeOptionPart = part:
let
escaped = lib.strings.escapeNixString part;
in if escaped == "\"${part}\""
then part
else escaped;
in (concatStringsSep ".") (map escapeOptionPart parts);
showFiles = files: concatStringsSep " and " (map (f: "`${f}'") files);
2013-10-28 13:25:58 +00:00
unknownModule = "<unknown-file>";
}