Merge pull request #68193 from chkno/nixos-option-rewrite
nixos-option C++ rewrite with --all
This commit is contained in:
commit
6f41b1c842
@ -326,6 +326,8 @@ rec {
|
|||||||
|
|
||||||
# The value with a check that it is defined
|
# The value with a check that it is defined
|
||||||
valueDefined = if res.isDefined then res.mergedValue else
|
valueDefined = if res.isDefined then res.mergedValue else
|
||||||
|
# (nixos-option detects this specific error message and gives it special
|
||||||
|
# handling. If changed here, please change it there too.)
|
||||||
throw "The option `${showOption loc}' is used but not defined.";
|
throw "The option `${showOption loc}' is used but not defined.";
|
||||||
|
|
||||||
# Apply the 'apply' function to the merged value. This allows options to
|
# Apply the 'apply' function to the merged value. This allows options to
|
||||||
|
@ -1194,6 +1194,12 @@
|
|||||||
githubId = 30435868;
|
githubId = 30435868;
|
||||||
name = "Okina Matara";
|
name = "Okina Matara";
|
||||||
};
|
};
|
||||||
|
chkno = {
|
||||||
|
email = "chuck@intelligence.org";
|
||||||
|
github = "chkno";
|
||||||
|
githubId = 1118859;
|
||||||
|
name = "Scott Worley";
|
||||||
|
};
|
||||||
choochootrain = {
|
choochootrain = {
|
||||||
email = "hurshal@imap.cc";
|
email = "hurshal@imap.cc";
|
||||||
github = "choochootrain";
|
github = "choochootrain";
|
||||||
|
@ -19,14 +19,10 @@
|
|||||||
</arg>
|
</arg>
|
||||||
|
|
||||||
<arg>
|
<arg>
|
||||||
<option>--verbose</option>
|
<option>--all</option>
|
||||||
</arg>
|
</arg>
|
||||||
|
|
||||||
<arg>
|
<arg>
|
||||||
<option>--xml</option>
|
|
||||||
</arg>
|
|
||||||
|
|
||||||
<arg choice="plain">
|
|
||||||
<replaceable>option.name</replaceable>
|
<replaceable>option.name</replaceable>
|
||||||
</arg>
|
</arg>
|
||||||
</cmdsynopsis>
|
</cmdsynopsis>
|
||||||
@ -62,22 +58,11 @@
|
|||||||
</varlistentry>
|
</varlistentry>
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term>
|
<term>
|
||||||
<option>--verbose</option>
|
<option>--all</option>
|
||||||
</term>
|
</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
This option enables verbose mode, which currently is just the Bash
|
Print the values of all options.
|
||||||
<command>set</command> <option>-x</option> debug mode.
|
|
||||||
</para>
|
|
||||||
</listitem>
|
|
||||||
</varlistentry>
|
|
||||||
<varlistentry>
|
|
||||||
<term>
|
|
||||||
<option>--xml</option>
|
|
||||||
</term>
|
|
||||||
<listitem>
|
|
||||||
<para>
|
|
||||||
This option causes the output to be rendered as XML.
|
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
@ -49,6 +49,12 @@
|
|||||||
zfs as soon as any zfs mountpoint is configured in <varname>fileSystems</varname>.
|
zfs as soon as any zfs mountpoint is configured in <varname>fileSystems</varname>.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<command>nixos-option</command> has been rewritten in C++, speeding it up, improving correctness,
|
||||||
|
and adding a <option>--all</option> option which prints all options and their values.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
</itemizedlist>
|
</itemizedlist>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
@ -1,327 +0,0 @@
|
|||||||
#! @shell@ -e
|
|
||||||
|
|
||||||
# FIXME: rewrite this in a more suitable language.
|
|
||||||
|
|
||||||
usage () {
|
|
||||||
exec man nixos-option
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
#####################
|
|
||||||
# Process Arguments #
|
|
||||||
#####################
|
|
||||||
|
|
||||||
xml=false
|
|
||||||
verbose=false
|
|
||||||
nixPath=""
|
|
||||||
|
|
||||||
option=""
|
|
||||||
exit_code=0
|
|
||||||
|
|
||||||
argfun=""
|
|
||||||
for arg; do
|
|
||||||
if test -z "$argfun"; then
|
|
||||||
case $arg in
|
|
||||||
-*)
|
|
||||||
sarg="$arg"
|
|
||||||
longarg=""
|
|
||||||
while test "$sarg" != "-"; do
|
|
||||||
case $sarg in
|
|
||||||
--*) longarg=$arg; sarg="--";;
|
|
||||||
-I) argfun="include_nixpath";;
|
|
||||||
-*) usage;;
|
|
||||||
esac
|
|
||||||
# remove the first letter option
|
|
||||||
sarg="-${sarg#??}"
|
|
||||||
done
|
|
||||||
;;
|
|
||||||
*) longarg=$arg;;
|
|
||||||
esac
|
|
||||||
for larg in $longarg; do
|
|
||||||
case $larg in
|
|
||||||
--xml) xml=true;;
|
|
||||||
--verbose) verbose=true;;
|
|
||||||
--help) usage;;
|
|
||||||
-*) usage;;
|
|
||||||
*) if test -z "$option"; then
|
|
||||||
option="$larg"
|
|
||||||
else
|
|
||||||
usage
|
|
||||||
fi;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
else
|
|
||||||
case $argfun in
|
|
||||||
set_*)
|
|
||||||
var=$(echo $argfun | sed 's,^set_,,')
|
|
||||||
eval $var=$arg
|
|
||||||
;;
|
|
||||||
include_nixpath)
|
|
||||||
nixPath="-I $arg $nixPath"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
argfun=""
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if $verbose; then
|
|
||||||
set -x
|
|
||||||
else
|
|
||||||
set +x
|
|
||||||
fi
|
|
||||||
|
|
||||||
#############################
|
|
||||||
# Process the configuration #
|
|
||||||
#############################
|
|
||||||
|
|
||||||
evalNix(){
|
|
||||||
# disable `-e` flag, it's possible that the evaluation of `nix-instantiate` fails (e.g. due to broken pkgs)
|
|
||||||
set +e
|
|
||||||
result=$(nix-instantiate ${nixPath:+$nixPath} - --eval-only "$@" 2>&1)
|
|
||||||
exit_code=$?
|
|
||||||
set -e
|
|
||||||
|
|
||||||
if test $exit_code -eq 0; then
|
|
||||||
sed '/^warning: Nix search path/d' <<EOF
|
|
||||||
$result
|
|
||||||
EOF
|
|
||||||
return 0;
|
|
||||||
else
|
|
||||||
sed -n '
|
|
||||||
/^error/ { s/, at (string):[0-9]*:[0-9]*//; p; };
|
|
||||||
/^warning: Nix search path/ { p; };
|
|
||||||
' >&2 <<EOF
|
|
||||||
$result
|
|
||||||
EOF
|
|
||||||
exit_code=1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
header="let
|
|
||||||
nixos = import <nixpkgs/nixos> {};
|
|
||||||
nixpkgs = import <nixpkgs> {};
|
|
||||||
in with nixpkgs.lib;
|
|
||||||
"
|
|
||||||
|
|
||||||
# This function is used for converting the option definition path given by
|
|
||||||
# the user into accessors for reaching the definition and the declaration
|
|
||||||
# corresponding to this option.
|
|
||||||
generateAccessors(){
|
|
||||||
if result=$(evalNix --strict --show-trace <<EOF
|
|
||||||
$header
|
|
||||||
|
|
||||||
let
|
|
||||||
path = "${option:+$option}";
|
|
||||||
pathList = splitString "." path;
|
|
||||||
|
|
||||||
walkOptions = attrsNames: result:
|
|
||||||
if attrsNames == [] then
|
|
||||||
result
|
|
||||||
else
|
|
||||||
let name = head attrsNames; rest = tail attrsNames; in
|
|
||||||
if isOption result.options then
|
|
||||||
walkOptions rest {
|
|
||||||
options = result.options.type.getSubOptions "";
|
|
||||||
opt = ''(\${result.opt}.type.getSubOptions "")'';
|
|
||||||
cfg = ''\${result.cfg}."\${name}"'';
|
|
||||||
}
|
|
||||||
else
|
|
||||||
walkOptions rest {
|
|
||||||
options = result.options.\${name};
|
|
||||||
opt = ''\${result.opt}."\${name}"'';
|
|
||||||
cfg = ''\${result.cfg}."\${name}"'';
|
|
||||||
}
|
|
||||||
;
|
|
||||||
|
|
||||||
walkResult = (if path == "" then x: x else walkOptions pathList) {
|
|
||||||
options = nixos.options;
|
|
||||||
opt = ''nixos.options'';
|
|
||||||
cfg = ''nixos.config'';
|
|
||||||
};
|
|
||||||
|
|
||||||
in
|
|
||||||
''let option = \${walkResult.opt}; config = \${walkResult.cfg}; in''
|
|
||||||
EOF
|
|
||||||
)
|
|
||||||
then
|
|
||||||
echo $result
|
|
||||||
else
|
|
||||||
# In case of error we want to ignore the error message roduced by the
|
|
||||||
# script above, as it is iterating over each attribute, which does not
|
|
||||||
# produce a nice error message. The following code is a fallback
|
|
||||||
# solution which is cause a nicer error message in the next
|
|
||||||
# evaluation.
|
|
||||||
echo "\"let option = nixos.options${option:+.$option}; config = nixos.config${option:+.$option}; in\""
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
header="$header
|
|
||||||
$(eval echo $(generateAccessors))
|
|
||||||
"
|
|
||||||
|
|
||||||
evalAttr(){
|
|
||||||
local prefix="$1"
|
|
||||||
local strict="$2"
|
|
||||||
local suffix="$3"
|
|
||||||
|
|
||||||
# If strict is set, then set it to "true".
|
|
||||||
test -n "$strict" && strict=true
|
|
||||||
|
|
||||||
evalNix ${strict:+--strict} <<EOF
|
|
||||||
$header
|
|
||||||
|
|
||||||
let
|
|
||||||
value = $prefix${suffix:+.$suffix};
|
|
||||||
strict = ${strict:-false};
|
|
||||||
cleanOutput = x: with nixpkgs.lib;
|
|
||||||
if isDerivation x then x.outPath
|
|
||||||
else if isFunction x then "<CODE>"
|
|
||||||
else if strict then
|
|
||||||
if isAttrs x then mapAttrs (n: cleanOutput) x
|
|
||||||
else if isList x then map cleanOutput x
|
|
||||||
else x
|
|
||||||
else x;
|
|
||||||
in
|
|
||||||
cleanOutput value
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
evalOpt(){
|
|
||||||
evalAttr "option" "" "$@"
|
|
||||||
}
|
|
||||||
|
|
||||||
evalCfg(){
|
|
||||||
local strict="$1"
|
|
||||||
evalAttr "config" "$strict"
|
|
||||||
}
|
|
||||||
|
|
||||||
findSources(){
|
|
||||||
local suffix=$1
|
|
||||||
evalNix --strict <<EOF
|
|
||||||
$header
|
|
||||||
|
|
||||||
option.$suffix
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
# Given a result from nix-instantiate, recover the list of attributes it
|
|
||||||
# contains.
|
|
||||||
attrNames() {
|
|
||||||
local attributeset=$1
|
|
||||||
# sed is used to replace un-printable subset by 0s, and to remove most of
|
|
||||||
# the inner-attribute set, which reduce the likelyhood to encounter badly
|
|
||||||
# pre-processed input.
|
|
||||||
echo "builtins.attrNames $attributeset" | \
|
|
||||||
sed 's,<[A-Z]*>,0,g; :inner; s/{[^\{\}]*};/0;/g; t inner;' | \
|
|
||||||
evalNix --strict
|
|
||||||
}
|
|
||||||
|
|
||||||
# map a simple list which contains strings or paths.
|
|
||||||
nixMap() {
|
|
||||||
local fun="$1"
|
|
||||||
local list="$2"
|
|
||||||
local elem
|
|
||||||
for elem in $list; do
|
|
||||||
test $elem = '[' -o $elem = ']' && continue;
|
|
||||||
$fun $elem
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
# This duplicates the work made below, but it is useful for processing
|
|
||||||
# the output of nixos-option with other tools such as nixos-gui.
|
|
||||||
if $xml; then
|
|
||||||
evalNix --xml --no-location <<EOF
|
|
||||||
$header
|
|
||||||
|
|
||||||
let
|
|
||||||
sources = builtins.map (f: f.source);
|
|
||||||
opt = option;
|
|
||||||
cfg = config;
|
|
||||||
in
|
|
||||||
|
|
||||||
with nixpkgs.lib;
|
|
||||||
|
|
||||||
let
|
|
||||||
optStrict = v:
|
|
||||||
let
|
|
||||||
traverse = x :
|
|
||||||
if isAttrs x then
|
|
||||||
if x ? outPath then true
|
|
||||||
else all id (mapAttrsFlatten (n: traverseNoAttrs) x)
|
|
||||||
else traverseNoAttrs x;
|
|
||||||
traverseNoAttrs = x:
|
|
||||||
# do not continue in attribute sets
|
|
||||||
if isAttrs x then true
|
|
||||||
else if isList x then all id (map traverse x)
|
|
||||||
else true;
|
|
||||||
in assert traverse v; v;
|
|
||||||
in
|
|
||||||
|
|
||||||
if isOption opt then
|
|
||||||
optStrict ({}
|
|
||||||
// optionalAttrs (opt ? default) { inherit (opt) default; }
|
|
||||||
// optionalAttrs (opt ? example) { inherit (opt) example; }
|
|
||||||
// optionalAttrs (opt ? description) { inherit (opt) description; }
|
|
||||||
// optionalAttrs (opt ? type) { typename = opt.type.description; }
|
|
||||||
// optionalAttrs (opt ? options) { inherit (opt) options; }
|
|
||||||
// {
|
|
||||||
# to disambiguate the xml output.
|
|
||||||
_isOption = true;
|
|
||||||
declarations = sources opt.declarations;
|
|
||||||
definitions = sources opt.definitions;
|
|
||||||
value = cfg;
|
|
||||||
})
|
|
||||||
else
|
|
||||||
opt
|
|
||||||
EOF
|
|
||||||
exit $?
|
|
||||||
fi
|
|
||||||
|
|
||||||
if test "$(evalOpt "_type" 2> /dev/null)" = '"option"'; then
|
|
||||||
echo "Value:"
|
|
||||||
evalCfg 1
|
|
||||||
|
|
||||||
echo
|
|
||||||
|
|
||||||
echo "Default:"
|
|
||||||
if default=$(evalOpt "default" - 2> /dev/null); then
|
|
||||||
echo "$default"
|
|
||||||
else
|
|
||||||
echo "<None>"
|
|
||||||
fi
|
|
||||||
echo
|
|
||||||
if example=$(evalOpt "example" - 2> /dev/null); then
|
|
||||||
echo "Example:"
|
|
||||||
echo "$example"
|
|
||||||
echo
|
|
||||||
fi
|
|
||||||
echo "Description:"
|
|
||||||
echo
|
|
||||||
echo $(evalOpt "description")
|
|
||||||
|
|
||||||
echo $desc;
|
|
||||||
|
|
||||||
printPath () { echo " $1"; }
|
|
||||||
|
|
||||||
echo "Declared by:"
|
|
||||||
nixMap printPath "$(findSources "declarations")"
|
|
||||||
echo
|
|
||||||
echo "Defined by:"
|
|
||||||
nixMap printPath "$(findSources "files")"
|
|
||||||
echo
|
|
||||||
|
|
||||||
else
|
|
||||||
# echo 1>&2 "Warning: This value is not an option."
|
|
||||||
|
|
||||||
result=$(evalCfg "")
|
|
||||||
if [ ! -z "$result" ]; then
|
|
||||||
names=$(attrNames "$result" 2> /dev/null)
|
|
||||||
echo 1>&2 "This attribute set contains:"
|
|
||||||
escapeQuotes () { eval echo "$1"; }
|
|
||||||
nixMap escapeQuotes "$names"
|
|
||||||
else
|
|
||||||
echo 1>&2 "An error occurred while looking for attribute names. Are you sure that '$option' exists?"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit $exit_code
|
|
@ -0,0 +1,8 @@
|
|||||||
|
cmake_minimum_required (VERSION 2.6)
|
||||||
|
project (nixos-option)
|
||||||
|
|
||||||
|
add_executable(nixos-option nixos-option.cc libnix-copy-paste.cc)
|
||||||
|
target_link_libraries(nixos-option PRIVATE -lnixmain -lnixexpr -lnixstore -lnixutil)
|
||||||
|
target_compile_features(nixos-option PRIVATE cxx_std_17)
|
||||||
|
|
||||||
|
install (TARGETS nixos-option DESTINATION bin)
|
11
nixos/modules/installer/tools/nixos-option/default.nix
Normal file
11
nixos/modules/installer/tools/nixos-option/default.nix
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{lib, stdenv, boost, cmake, pkgconfig, nix, ... }:
|
||||||
|
stdenv.mkDerivation rec {
|
||||||
|
name = "nixos-option";
|
||||||
|
src = ./.;
|
||||||
|
nativeBuildInputs = [ cmake pkgconfig ];
|
||||||
|
buildInputs = [ boost nix ];
|
||||||
|
meta = {
|
||||||
|
license = stdenv.lib.licenses.lgpl2Plus;
|
||||||
|
maintainers = with lib.maintainers; [ chkno ];
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,83 @@
|
|||||||
|
// These are useful methods inside the nix library that ought to be exported.
|
||||||
|
// Since they are not, copy/paste them here.
|
||||||
|
// TODO: Delete these and use the ones in the library as they become available.
|
||||||
|
|
||||||
|
#include <nix/config.h> // for nix/globals.hh's reference to SYSTEM
|
||||||
|
|
||||||
|
#include "libnix-copy-paste.hh"
|
||||||
|
#include <boost/format/alt_sstream.hpp> // for basic_altstringbuf...
|
||||||
|
#include <boost/format/alt_sstream_impl.hpp> // for basic_altstringbuf...
|
||||||
|
#include <boost/format/format_class.hpp> // for basic_format
|
||||||
|
#include <boost/format/format_fwd.hpp> // for format
|
||||||
|
#include <boost/format/format_implementation.hpp> // for basic_format::basi...
|
||||||
|
#include <boost/optional/optional.hpp> // for get_pointer
|
||||||
|
#include <iostream> // for operator<<, basic_...
|
||||||
|
#include <nix/types.hh> // for Strings, Error
|
||||||
|
#include <string> // for string, basic_string
|
||||||
|
|
||||||
|
using boost::format;
|
||||||
|
using nix::Error;
|
||||||
|
using nix::Strings;
|
||||||
|
using std::string;
|
||||||
|
|
||||||
|
// From nix/src/libexpr/attr-path.cc
|
||||||
|
Strings parseAttrPath(const string & s)
|
||||||
|
{
|
||||||
|
Strings res;
|
||||||
|
string cur;
|
||||||
|
string::const_iterator i = s.begin();
|
||||||
|
while (i != s.end()) {
|
||||||
|
if (*i == '.') {
|
||||||
|
res.push_back(cur);
|
||||||
|
cur.clear();
|
||||||
|
} else if (*i == '"') {
|
||||||
|
++i;
|
||||||
|
while (1) {
|
||||||
|
if (i == s.end())
|
||||||
|
throw Error(format("missing closing quote in selection path '%1%'") % s);
|
||||||
|
if (*i == '"')
|
||||||
|
break;
|
||||||
|
cur.push_back(*i++);
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
cur.push_back(*i);
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
if (!cur.empty())
|
||||||
|
res.push_back(cur);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// From nix/src/nix/repl.cc
|
||||||
|
bool isVarName(const string & s)
|
||||||
|
{
|
||||||
|
if (s.size() == 0)
|
||||||
|
return false;
|
||||||
|
char c = s[0];
|
||||||
|
if ((c >= '0' && c <= '9') || c == '-' || c == '\'')
|
||||||
|
return false;
|
||||||
|
for (auto & i : s)
|
||||||
|
if (!((i >= 'a' && i <= 'z') || (i >= 'A' && i <= 'Z') || (i >= '0' && i <= '9') || i == '_' || i == '-' ||
|
||||||
|
i == '\''))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// From nix/src/nix/repl.cc
|
||||||
|
std::ostream & printStringValue(std::ostream & str, const char * string)
|
||||||
|
{
|
||||||
|
str << "\"";
|
||||||
|
for (const char * i = string; *i; i++)
|
||||||
|
if (*i == '\"' || *i == '\\')
|
||||||
|
str << "\\" << *i;
|
||||||
|
else if (*i == '\n')
|
||||||
|
str << "\\n";
|
||||||
|
else if (*i == '\r')
|
||||||
|
str << "\\r";
|
||||||
|
else if (*i == '\t')
|
||||||
|
str << "\\t";
|
||||||
|
else
|
||||||
|
str << *i;
|
||||||
|
str << "\"";
|
||||||
|
return str;
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <nix/types.hh>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
nix::Strings parseAttrPath(const std::string & s);
|
||||||
|
bool isVarName(const std::string & s);
|
||||||
|
std::ostream & printStringValue(std::ostream & str, const char * string);
|
618
nixos/modules/installer/tools/nixos-option/nixos-option.cc
Normal file
618
nixos/modules/installer/tools/nixos-option/nixos-option.cc
Normal file
@ -0,0 +1,618 @@
|
|||||||
|
#include <nix/config.h> // for nix/globals.hh's reference to SYSTEM
|
||||||
|
|
||||||
|
#include <exception> // for exception_ptr, current_exception
|
||||||
|
#include <functional> // for function
|
||||||
|
#include <iostream> // for operator<<, basic_ostream, ostrin...
|
||||||
|
#include <iterator> // for next
|
||||||
|
#include <list> // for _List_iterator
|
||||||
|
#include <memory> // for allocator, unique_ptr, make_unique
|
||||||
|
#include <new> // for operator new
|
||||||
|
#include <nix/args.hh> // for argvToStrings, UsageError
|
||||||
|
#include <nix/attr-path.hh> // for findAlongAttrPath
|
||||||
|
#include <nix/attr-set.hh> // for Attr, Bindings, Bindings::iterator
|
||||||
|
#include <nix/common-eval-args.hh> // for MixEvalArgs
|
||||||
|
#include <nix/eval-inline.hh> // for EvalState::forceValue
|
||||||
|
#include <nix/eval.hh> // for EvalState, initGC, operator<<
|
||||||
|
#include <nix/globals.hh> // for initPlugins, Settings, settings
|
||||||
|
#include <nix/nixexpr.hh> // for Pos
|
||||||
|
#include <nix/shared.hh> // for getArg, LegacyArgs, printVersion
|
||||||
|
#include <nix/store-api.hh> // for openStore
|
||||||
|
#include <nix/symbol-table.hh> // for Symbol, SymbolTable
|
||||||
|
#include <nix/types.hh> // for Error, Path, Strings, PathSet
|
||||||
|
#include <nix/util.hh> // for absPath, baseNameOf
|
||||||
|
#include <nix/value.hh> // for Value, Value::(anonymous), Value:...
|
||||||
|
#include <string> // for string, operator+, operator==
|
||||||
|
#include <utility> // for move
|
||||||
|
#include <variant> // for get, holds_alternative, variant
|
||||||
|
#include <vector> // for vector<>::iterator, vector
|
||||||
|
|
||||||
|
#include "libnix-copy-paste.hh"
|
||||||
|
|
||||||
|
using nix::absPath;
|
||||||
|
using nix::Bindings;
|
||||||
|
using nix::Error;
|
||||||
|
using nix::EvalError;
|
||||||
|
using nix::EvalState;
|
||||||
|
using nix::Path;
|
||||||
|
using nix::PathSet;
|
||||||
|
using nix::Strings;
|
||||||
|
using nix::Symbol;
|
||||||
|
using nix::tAttrs;
|
||||||
|
using nix::ThrownError;
|
||||||
|
using nix::tLambda;
|
||||||
|
using nix::tString;
|
||||||
|
using nix::UsageError;
|
||||||
|
using nix::Value;
|
||||||
|
|
||||||
|
// An ostream wrapper to handle nested indentation
|
||||||
|
class Out
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
class Separator
|
||||||
|
{};
|
||||||
|
const static Separator sep;
|
||||||
|
enum LinePolicy
|
||||||
|
{
|
||||||
|
ONE_LINE,
|
||||||
|
MULTI_LINE
|
||||||
|
};
|
||||||
|
explicit Out(std::ostream & ostream) : ostream(ostream), policy(ONE_LINE), writeSinceSep(true) {}
|
||||||
|
Out(Out & o, const std::string & start, const std::string & end, LinePolicy policy);
|
||||||
|
Out(Out & o, const std::string & start, const std::string & end, int count)
|
||||||
|
: Out(o, start, end, count < 2 ? ONE_LINE : MULTI_LINE)
|
||||||
|
{}
|
||||||
|
Out(const Out &) = delete;
|
||||||
|
Out(Out &&) = default;
|
||||||
|
Out & operator=(const Out &) = delete;
|
||||||
|
Out & operator=(Out &&) = delete;
|
||||||
|
~Out() { ostream << end; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::ostream & ostream;
|
||||||
|
std::string indentation;
|
||||||
|
std::string end;
|
||||||
|
LinePolicy policy;
|
||||||
|
bool writeSinceSep;
|
||||||
|
template <typename T> friend Out & operator<<(Out & o, T thing);
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T> Out & operator<<(Out & o, T thing)
|
||||||
|
{
|
||||||
|
if (!o.writeSinceSep && o.policy == Out::MULTI_LINE) {
|
||||||
|
o.ostream << o.indentation;
|
||||||
|
}
|
||||||
|
o.writeSinceSep = true;
|
||||||
|
o.ostream << thing;
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <> Out & operator<<<Out::Separator>(Out & o, Out::Separator /* thing */)
|
||||||
|
{
|
||||||
|
o.ostream << (o.policy == Out::ONE_LINE ? " " : "\n");
|
||||||
|
o.writeSinceSep = false;
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
Out::Out(Out & o, const std::string & start, const std::string & end, LinePolicy policy)
|
||||||
|
: ostream(o.ostream), indentation(policy == ONE_LINE ? o.indentation : o.indentation + " "),
|
||||||
|
end(policy == ONE_LINE ? end : o.indentation + end), policy(policy), writeSinceSep(true)
|
||||||
|
{
|
||||||
|
o << start;
|
||||||
|
*this << Out::sep;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stuff needed for evaluation
|
||||||
|
struct Context
|
||||||
|
{
|
||||||
|
Context(EvalState & state, Bindings & autoArgs, Value optionsRoot, Value configRoot)
|
||||||
|
: state(state), autoArgs(autoArgs), optionsRoot(optionsRoot), configRoot(configRoot),
|
||||||
|
underscoreType(state.symbols.create("_type"))
|
||||||
|
{}
|
||||||
|
EvalState & state;
|
||||||
|
Bindings & autoArgs;
|
||||||
|
Value optionsRoot;
|
||||||
|
Value configRoot;
|
||||||
|
Symbol underscoreType;
|
||||||
|
};
|
||||||
|
|
||||||
|
Value evaluateValue(Context & ctx, Value & v)
|
||||||
|
{
|
||||||
|
ctx.state.forceValue(v);
|
||||||
|
if (ctx.autoArgs.empty()) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
Value called{};
|
||||||
|
ctx.state.autoCallFunction(ctx.autoArgs, v, called);
|
||||||
|
return called;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isOption(Context & ctx, const Value & v)
|
||||||
|
{
|
||||||
|
if (v.type != tAttrs) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto & atualType = v.attrs->find(ctx.underscoreType);
|
||||||
|
if (atualType == v.attrs->end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Value evaluatedType = evaluateValue(ctx, *atualType->value);
|
||||||
|
if (evaluatedType.type != tString) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return static_cast<std::string>(evaluatedType.string.s) == "option";
|
||||||
|
} catch (Error &) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add quotes to a component of a path.
|
||||||
|
// These are needed for paths like:
|
||||||
|
// fileSystems."/".fsType
|
||||||
|
// systemd.units."dbus.service".text
|
||||||
|
std::string quoteAttribute(const std::string & attribute)
|
||||||
|
{
|
||||||
|
if (isVarName(attribute)) {
|
||||||
|
return attribute;
|
||||||
|
}
|
||||||
|
std::ostringstream buf;
|
||||||
|
printStringValue(buf, attribute.c_str());
|
||||||
|
return buf.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string appendPath(const std::string & prefix, const std::string & suffix)
|
||||||
|
{
|
||||||
|
if (prefix.empty()) {
|
||||||
|
return quoteAttribute(suffix);
|
||||||
|
}
|
||||||
|
return prefix + "." + quoteAttribute(suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool forbiddenRecursionName(std::string name) { return (!name.empty() && name[0] == '_') || name == "haskellPackages"; }
|
||||||
|
|
||||||
|
void recurse(const std::function<bool(const std::string & path, std::variant<Value, std::exception_ptr>)> & f,
|
||||||
|
Context & ctx, Value v, const std::string & path)
|
||||||
|
{
|
||||||
|
std::variant<Value, std::exception_ptr> evaluated;
|
||||||
|
try {
|
||||||
|
evaluated = evaluateValue(ctx, v);
|
||||||
|
} catch (Error &) {
|
||||||
|
evaluated = std::current_exception();
|
||||||
|
}
|
||||||
|
if (!f(path, evaluated)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (std::holds_alternative<std::exception_ptr>(evaluated)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const Value & evaluated_value = std::get<Value>(evaluated);
|
||||||
|
if (evaluated_value.type != tAttrs) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const auto & child : evaluated_value.attrs->lexicographicOrder()) {
|
||||||
|
if (forbiddenRecursionName(child->name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
recurse(f, ctx, *child->value, appendPath(path, child->name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calls f on all the option names
|
||||||
|
void mapOptions(const std::function<void(const std::string & path)> & f, Context & ctx, Value root)
|
||||||
|
{
|
||||||
|
recurse(
|
||||||
|
[f, &ctx](const std::string & path, std::variant<Value, std::exception_ptr> v) {
|
||||||
|
bool isOpt = std::holds_alternative<std::exception_ptr>(v) || isOption(ctx, std::get<Value>(v));
|
||||||
|
if (isOpt) {
|
||||||
|
f(path);
|
||||||
|
}
|
||||||
|
return !isOpt;
|
||||||
|
},
|
||||||
|
ctx, root, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calls f on all the config values inside one option.
|
||||||
|
// Simple options have one config value inside, like sound.enable = true.
|
||||||
|
// Compound options have multiple config values. For example, the option
|
||||||
|
// "users.users" has about 1000 config values inside it:
|
||||||
|
// users.users.avahi.createHome = false;
|
||||||
|
// users.users.avahi.cryptHomeLuks = null;
|
||||||
|
// users.users.avahi.description = "`avahi-daemon' privilege separation user";
|
||||||
|
// ...
|
||||||
|
// users.users.avahi.openssh.authorizedKeys.keyFiles = [ ];
|
||||||
|
// users.users.avahi.openssh.authorizedKeys.keys = [ ];
|
||||||
|
// ...
|
||||||
|
// users.users.avahi.uid = 10;
|
||||||
|
// users.users.avahi.useDefaultShell = false;
|
||||||
|
// users.users.cups.createHome = false;
|
||||||
|
// ...
|
||||||
|
// users.users.cups.useDefaultShell = false;
|
||||||
|
// users.users.gdm = ... ... ...
|
||||||
|
// users.users.messagebus = ... .. ...
|
||||||
|
// users.users.nixbld1 = ... .. ...
|
||||||
|
// ...
|
||||||
|
// users.users.systemd-timesync = ... .. ...
|
||||||
|
void mapConfigValuesInOption(
|
||||||
|
const std::function<void(const std::string & path, std::variant<Value, std::exception_ptr> v)> & f,
|
||||||
|
const std::string & path, Context & ctx)
|
||||||
|
{
|
||||||
|
Value * option;
|
||||||
|
try {
|
||||||
|
option = findAlongAttrPath(ctx.state, path, ctx.autoArgs, ctx.configRoot);
|
||||||
|
} catch (Error &) {
|
||||||
|
f(path, std::current_exception());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
recurse(
|
||||||
|
[f, ctx](const std::string & path, std::variant<Value, std::exception_ptr> v) {
|
||||||
|
bool leaf = std::holds_alternative<std::exception_ptr>(v) || std::get<Value>(v).type != tAttrs ||
|
||||||
|
ctx.state.isDerivation(std::get<Value>(v));
|
||||||
|
if (!leaf) {
|
||||||
|
return true; // Keep digging
|
||||||
|
}
|
||||||
|
f(path, v);
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
ctx, *option, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string describeError(const Error & e) { return "«error: " + e.msg() + "»"; }
|
||||||
|
|
||||||
|
void describeDerivation(Context & ctx, Out & out, Value v)
|
||||||
|
{
|
||||||
|
// Copy-pasted from nix/src/nix/repl.cc :(
|
||||||
|
Bindings::iterator i = v.attrs->find(ctx.state.sDrvPath);
|
||||||
|
PathSet pathset;
|
||||||
|
try {
|
||||||
|
Path drvPath = i != v.attrs->end() ? ctx.state.coerceToPath(*i->pos, *i->value, pathset) : "???";
|
||||||
|
out << "«derivation " << drvPath << "»";
|
||||||
|
} catch (Error & e) {
|
||||||
|
out << describeError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value parseAndEval(EvalState & state, const std::string & expression, const std::string & path)
|
||||||
|
{
|
||||||
|
Value v{};
|
||||||
|
state.eval(state.parseExprFromString(expression, absPath(path)), v);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
void printValue(Context & ctx, Out & out, std::variant<Value, std::exception_ptr> maybeValue, const std::string & path);
|
||||||
|
|
||||||
|
void printList(Context & ctx, Out & out, Value & v)
|
||||||
|
{
|
||||||
|
Out listOut(out, "[", "]", v.listSize());
|
||||||
|
for (unsigned int n = 0; n < v.listSize(); ++n) {
|
||||||
|
printValue(ctx, listOut, *v.listElems()[n], "");
|
||||||
|
listOut << Out::sep;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void printAttrs(Context & ctx, Out & out, Value & v, const std::string & path)
|
||||||
|
{
|
||||||
|
Out attrsOut(out, "{", "}", v.attrs->size());
|
||||||
|
for (const auto & a : v.attrs->lexicographicOrder()) {
|
||||||
|
std::string name = a->name;
|
||||||
|
attrsOut << name << " = ";
|
||||||
|
printValue(ctx, attrsOut, *a->value, appendPath(path, name));
|
||||||
|
attrsOut << ";" << Out::sep;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void multiLineStringEscape(Out & out, const std::string & s)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
for (i = 1; i < s.size(); i++) {
|
||||||
|
if (s[i - 1] == '$' && s[i] == '{') {
|
||||||
|
out << "''${";
|
||||||
|
i++;
|
||||||
|
} else if (s[i - 1] == '\'' && s[i] == '\'') {
|
||||||
|
out << "'''";
|
||||||
|
i++;
|
||||||
|
} else {
|
||||||
|
out << s[i - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i == s.size()) {
|
||||||
|
out << s[i - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void printMultiLineString(Out & out, const Value & v)
|
||||||
|
{
|
||||||
|
std::string s = v.string.s;
|
||||||
|
Out strOut(out, "''", "''", Out::MULTI_LINE);
|
||||||
|
std::string::size_type begin = 0;
|
||||||
|
while (begin < s.size()) {
|
||||||
|
std::string::size_type end = s.find('\n', begin);
|
||||||
|
if (end == std::string::npos) {
|
||||||
|
multiLineStringEscape(strOut, s.substr(begin, s.size() - begin));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
multiLineStringEscape(strOut, s.substr(begin, end - begin));
|
||||||
|
strOut << Out::sep;
|
||||||
|
begin = end + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void printValue(Context & ctx, Out & out, std::variant<Value, std::exception_ptr> maybeValue, const std::string & path)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (auto ex = std::get_if<std::exception_ptr>(&maybeValue)) {
|
||||||
|
std::rethrow_exception(*ex);
|
||||||
|
}
|
||||||
|
Value v = evaluateValue(ctx, std::get<Value>(maybeValue));
|
||||||
|
if (ctx.state.isDerivation(v)) {
|
||||||
|
describeDerivation(ctx, out, v);
|
||||||
|
} else if (v.isList()) {
|
||||||
|
printList(ctx, out, v);
|
||||||
|
} else if (v.type == tAttrs) {
|
||||||
|
printAttrs(ctx, out, v, path);
|
||||||
|
} else if (v.type == tString && std::string(v.string.s).find('\n') != std::string::npos) {
|
||||||
|
printMultiLineString(out, v);
|
||||||
|
} else {
|
||||||
|
ctx.state.forceValueDeep(v);
|
||||||
|
out << v;
|
||||||
|
}
|
||||||
|
} catch (ThrownError & e) {
|
||||||
|
if (e.msg() == "The option `" + path + "' is used but not defined.") {
|
||||||
|
// 93% of errors are this, and just letting this message through would be
|
||||||
|
// misleading. These values may or may not actually be "used" in the
|
||||||
|
// config. The thing throwing the error message assumes that if anything
|
||||||
|
// ever looks at this value, it is a "use" of this value. But here in
|
||||||
|
// nixos-option, we are looking at this value only to print it.
|
||||||
|
// In order to avoid implying that this undefined value is actually
|
||||||
|
// referenced, eat the underlying error message and emit "«not defined»".
|
||||||
|
out << "«not defined»";
|
||||||
|
} else {
|
||||||
|
out << describeError(e);
|
||||||
|
}
|
||||||
|
} catch (Error & e) {
|
||||||
|
out << describeError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void printConfigValue(Context & ctx, Out & out, const std::string & path, std::variant<Value, std::exception_ptr> v)
|
||||||
|
{
|
||||||
|
out << path << " = ";
|
||||||
|
printValue(ctx, out, std::move(v), path);
|
||||||
|
out << ";\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void printAll(Context & ctx, Out & out)
|
||||||
|
{
|
||||||
|
mapOptions(
|
||||||
|
[&ctx, &out](const std::string & optionPath) {
|
||||||
|
mapConfigValuesInOption(
|
||||||
|
[&ctx, &out](const std::string & configPath, std::variant<Value, std::exception_ptr> v) {
|
||||||
|
printConfigValue(ctx, out, configPath, v);
|
||||||
|
},
|
||||||
|
optionPath, ctx);
|
||||||
|
},
|
||||||
|
ctx, ctx.optionsRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
void printAttr(Context & ctx, Out & out, const std::string & path, Value & root)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
printValue(ctx, out, *findAlongAttrPath(ctx.state, path, ctx.autoArgs, root), path);
|
||||||
|
} catch (Error & e) {
|
||||||
|
out << describeError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasExample(Context & ctx, Value & option)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
findAlongAttrPath(ctx.state, "example", ctx.autoArgs, option);
|
||||||
|
return true;
|
||||||
|
} catch (Error &) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void printOption(Context & ctx, Out & out, const std::string & path, Value & option)
|
||||||
|
{
|
||||||
|
out << "Value:\n";
|
||||||
|
printAttr(ctx, out, path, ctx.configRoot);
|
||||||
|
|
||||||
|
out << "\n\nDefault:\n";
|
||||||
|
printAttr(ctx, out, "default", option);
|
||||||
|
|
||||||
|
out << "\n\nType:\n";
|
||||||
|
printAttr(ctx, out, "type.description", option);
|
||||||
|
|
||||||
|
if (hasExample(ctx, option)) {
|
||||||
|
out << "\n\nExample:\n";
|
||||||
|
printAttr(ctx, out, "example", option);
|
||||||
|
}
|
||||||
|
|
||||||
|
out << "\n\nDescription:\n";
|
||||||
|
printAttr(ctx, out, "description", option);
|
||||||
|
|
||||||
|
out << "\n\nDeclared by:\n";
|
||||||
|
printAttr(ctx, out, "declarations", option);
|
||||||
|
|
||||||
|
out << "\n\nDefined by:\n";
|
||||||
|
printAttr(ctx, out, "files", option);
|
||||||
|
out << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void printListing(Out & out, Value & v)
|
||||||
|
{
|
||||||
|
out << "This attribute set contains:\n";
|
||||||
|
for (const auto & a : v.attrs->lexicographicOrder()) {
|
||||||
|
std::string name = a->name;
|
||||||
|
if (!name.empty() && name[0] != '_') {
|
||||||
|
out << name << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool optionTypeIs(Context & ctx, Value & v, const std::string & soughtType)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
const auto & typeLookup = v.attrs->find(ctx.state.sType);
|
||||||
|
if (typeLookup == v.attrs->end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Value type = evaluateValue(ctx, *typeLookup->value);
|
||||||
|
if (type.type != tAttrs) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto & nameLookup = type.attrs->find(ctx.state.sName);
|
||||||
|
if (nameLookup == type.attrs->end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Value name = evaluateValue(ctx, *nameLookup->value);
|
||||||
|
if (name.type != tString) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return name.string.s == soughtType;
|
||||||
|
} catch (Error &) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isAggregateOptionType(Context & ctx, Value & v)
|
||||||
|
{
|
||||||
|
return optionTypeIs(ctx, v, "attrsOf") || optionTypeIs(ctx, v, "listOf") || optionTypeIs(ctx, v, "loaOf");
|
||||||
|
}
|
||||||
|
|
||||||
|
MakeError(OptionPathError, EvalError);
|
||||||
|
|
||||||
|
Value getSubOptions(Context & ctx, Value & option)
|
||||||
|
{
|
||||||
|
Value getSubOptions = evaluateValue(ctx, *findAlongAttrPath(ctx.state, "type.getSubOptions", ctx.autoArgs, option));
|
||||||
|
if (getSubOptions.type != tLambda) {
|
||||||
|
throw OptionPathError("Option's type.getSubOptions isn't a function");
|
||||||
|
}
|
||||||
|
Value emptyString{};
|
||||||
|
nix::mkString(emptyString, "");
|
||||||
|
Value v;
|
||||||
|
ctx.state.callFunction(getSubOptions, emptyString, v, nix::Pos{});
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Carefully walk an option path, looking for sub-options when a path walks past
|
||||||
|
// an option value.
|
||||||
|
Value findAlongOptionPath(Context & ctx, const std::string & path)
|
||||||
|
{
|
||||||
|
Strings tokens = parseAttrPath(path);
|
||||||
|
Value v = ctx.optionsRoot;
|
||||||
|
for (auto i = tokens.begin(); i != tokens.end(); i++) {
|
||||||
|
const auto & attr = *i;
|
||||||
|
try {
|
||||||
|
bool lastAttribute = std::next(i) == tokens.end();
|
||||||
|
v = evaluateValue(ctx, v);
|
||||||
|
if (attr.empty()) {
|
||||||
|
throw OptionPathError("empty attribute name");
|
||||||
|
}
|
||||||
|
if (isOption(ctx, v) && optionTypeIs(ctx, v, "submodule")) {
|
||||||
|
v = getSubOptions(ctx, v);
|
||||||
|
}
|
||||||
|
if (isOption(ctx, v) && isAggregateOptionType(ctx, v) && !lastAttribute) {
|
||||||
|
v = getSubOptions(ctx, v);
|
||||||
|
// Note that we've consumed attr, but didn't actually use it. This is the path component that's looked
|
||||||
|
// up in the list or attribute set that doesn't name an option -- the "root" in "users.users.root.name".
|
||||||
|
} else if (v.type != tAttrs) {
|
||||||
|
throw OptionPathError("Value is %s while a set was expected", showType(v));
|
||||||
|
} else {
|
||||||
|
const auto & next = v.attrs->find(ctx.state.symbols.create(attr));
|
||||||
|
if (next == v.attrs->end()) {
|
||||||
|
throw OptionPathError("Attribute not found", attr, path);
|
||||||
|
}
|
||||||
|
v = *next->value;
|
||||||
|
}
|
||||||
|
} catch (OptionPathError & e) {
|
||||||
|
throw OptionPathError("At '%s' in path '%s': %s", attr, path, e.msg());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
void printOne(Context & ctx, Out & out, const std::string & path)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
Value option = findAlongOptionPath(ctx, path);
|
||||||
|
option = evaluateValue(ctx, option);
|
||||||
|
if (isOption(ctx, option)) {
|
||||||
|
printOption(ctx, out, path, option);
|
||||||
|
} else {
|
||||||
|
printListing(out, option);
|
||||||
|
}
|
||||||
|
} catch (Error & e) {
|
||||||
|
std::cerr << "error: " << e.msg()
|
||||||
|
<< "\nAn error occurred while looking for attribute names. Are "
|
||||||
|
"you sure that '"
|
||||||
|
<< path << "' exists?\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char ** argv)
|
||||||
|
{
|
||||||
|
bool all = false;
|
||||||
|
std::string path = ".";
|
||||||
|
std::string optionsExpr = "(import <nixpkgs/nixos> {}).options";
|
||||||
|
std::string configExpr = "(import <nixpkgs/nixos> {}).config";
|
||||||
|
std::vector<std::string> args;
|
||||||
|
|
||||||
|
struct MyArgs : nix::LegacyArgs, nix::MixEvalArgs
|
||||||
|
{
|
||||||
|
using nix::LegacyArgs::LegacyArgs;
|
||||||
|
};
|
||||||
|
|
||||||
|
MyArgs myArgs(nix::baseNameOf(argv[0]), [&](Strings::iterator & arg, const Strings::iterator & end) {
|
||||||
|
if (*arg == "--help") {
|
||||||
|
nix::showManPage("nixos-option");
|
||||||
|
} else if (*arg == "--version") {
|
||||||
|
nix::printVersion("nixos-option");
|
||||||
|
} else if (*arg == "--all") {
|
||||||
|
all = true;
|
||||||
|
} else if (*arg == "--path") {
|
||||||
|
path = nix::getArg(*arg, arg, end);
|
||||||
|
} else if (*arg == "--options_expr") {
|
||||||
|
optionsExpr = nix::getArg(*arg, arg, end);
|
||||||
|
} else if (*arg == "--config_expr") {
|
||||||
|
configExpr = nix::getArg(*arg, arg, end);
|
||||||
|
} else if (!arg->empty() && arg->at(0) == '-') {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
args.push_back(*arg);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
myArgs.parseCmdline(nix::argvToStrings(argc, argv));
|
||||||
|
|
||||||
|
nix::initPlugins();
|
||||||
|
nix::initGC();
|
||||||
|
nix::settings.readOnlyMode = true;
|
||||||
|
auto store = nix::openStore();
|
||||||
|
auto state = std::make_unique<EvalState>(myArgs.searchPath, store);
|
||||||
|
|
||||||
|
Value optionsRoot = parseAndEval(*state, optionsExpr, path);
|
||||||
|
Value configRoot = parseAndEval(*state, configExpr, path);
|
||||||
|
|
||||||
|
Context ctx{*state, *myArgs.getAutoArgs(*state), optionsRoot, configRoot};
|
||||||
|
Out out(std::cout);
|
||||||
|
|
||||||
|
if (all) {
|
||||||
|
if (!args.empty()) {
|
||||||
|
throw UsageError("--all cannot be used with arguments");
|
||||||
|
}
|
||||||
|
printAll(ctx, out);
|
||||||
|
} else {
|
||||||
|
if (args.empty()) {
|
||||||
|
printOne(ctx, out, "");
|
||||||
|
}
|
||||||
|
for (const auto & arg : args) {
|
||||||
|
printOne(ctx, out, arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.state.printStats();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
@ -41,10 +41,7 @@ let
|
|||||||
inherit (config.system.nixos-generate-config) configuration;
|
inherit (config.system.nixos-generate-config) configuration;
|
||||||
};
|
};
|
||||||
|
|
||||||
nixos-option = makeProg {
|
nixos-option = pkgs.callPackage ./nixos-option { };
|
||||||
name = "nixos-option";
|
|
||||||
src = ./nixos-option.sh;
|
|
||||||
};
|
|
||||||
|
|
||||||
nixos-version = makeProg {
|
nixos-version = makeProg {
|
||||||
name = "nixos-version";
|
name = "nixos-version";
|
||||||
|
Loading…
x
Reference in New Issue
Block a user