Compare commits

...

28 Commits

Author SHA1 Message Date
Eelco Dolstra
92c35d5303 Fix build 2020-10-15 21:35:14 +02:00
Eelco Dolstra
974e0367df Merge remote-tracking branch 'origin/master' into configs 2020-10-15 21:30:39 +02:00
Eelco Dolstra
3632593bfc Add 'nix set-option' command for setting module options 2020-09-25 10:59:47 +02:00
Eelco Dolstra
9f113fe6bf Merge remote-tracking branch 'origin/master' into configs 2020-09-25 10:28:20 +02:00
Eelco Dolstra
8f50523c38 nix run: Support overriding module options
E.g.

  $ nix run /home/eelco/Tweag/nix-ux/configs#hello --argstr who Everybody
  warning: creating lock file '/tmp/nix-shell.Jr8WDs/nix-9520-0/flake.lock'
  Hello Everybody
2020-09-25 10:11:36 +02:00
Eelco Dolstra
dc4a280318 Allow overring module options from the command line
E.g.

  $ nix build github:tweag/nix-ux/configs?dir=configs#hello --argstr who Everybody
  $ ./result/bin/hello
  Hello Everybody

This works by generating a new flake that imports the specified one
and sets the specified module options.
2020-09-24 22:55:30 +02:00
Eelco Dolstra
b068f96b92 Merge remote-tracking branch 'origin/master' into configs 2020-09-24 13:05:27 +02:00
Eelco Dolstra
1dc3f5355a Support flakes in TOML format
So instead of a 'flake.nix', flakes can now contain a 'nix.toml' file
like this:

  description = "My own Hello World"

  [inputs]
  configs.url = "github:tweag/nix-ux/configs?dir=configs"

  [my-hello]
  extends = [ "configs#hello" ]
  doc = '''
    A specialized version of the Hello package!
  '''
  who = "Springfield"

'my-hello' defines an output named 'modules.my-hello', which can be
built as follows:

  $ nix build /path/to/flake#my-hello

  $ ./result/bin/hello
  Hello Springfield
2020-09-23 14:57:47 +02:00
Eelco Dolstra
08992ab6bc Merge remote-tracking branch 'origin/master' into configs 2020-09-23 14:09:03 +02:00
Eelco Dolstra
c559570c08 Merge remote-tracking branch 'origin/master' into configs 2020-09-22 15:35:44 +02:00
Eelco Dolstra
ca85bc5924 Merge remote-tracking branch 'origin/master' into configs 2020-09-22 14:48:07 +02:00
Eelco Dolstra
41795b43a8 Merge remote-tracking branch 'origin/master' into configs 2020-09-21 13:56:51 +02:00
Eelco Dolstra
0b337fca34 Add 'nix doc' command
This command generates HTML docs (using mdbook) for a flake.
2020-09-18 13:42:29 +02:00
Eelco Dolstra
f731443384 Use std::optional 2020-09-18 13:39:17 +02:00
Eelco Dolstra
73176ab160 Merge remote-tracking branch 'origin/master' into configs 2020-09-18 12:14:09 +02:00
Eelco Dolstra
22c96a784c Merge remote-tracking branch 'origin/master' into configs 2020-09-15 13:59:34 +02:00
Eelco Dolstra
7f11bc1b06 Merge remote-tracking branch 'origin/master' into configs 2020-09-14 19:16:58 +02:00
Eelco Dolstra
b7d263b79a Support modules in toDerivation() and toApp() 2020-09-03 13:11:22 +02:00
Eelco Dolstra
22b8e07f09 Merge remote-tracking branch 'origin/master' into configs 2020-09-03 13:06:24 +02:00
Eelco Dolstra
d28c7b0982 Merge remote-tracking branch 'origin/master' into configs 2020-09-02 14:48:08 +02:00
Eelco Dolstra
d8a4a9f418 Merge remote-tracking branch 'origin/master' into configs 2020-09-01 16:16:23 +02:00
Eelco Dolstra
afebbb876f nix list-options: Ignore eval errors for now 2020-09-01 13:46:39 +02:00
Eelco Dolstra
db204f40d2 Fix build 2020-09-01 13:46:22 +02:00
Eelco Dolstra
f79b90f7ea Merge remote-tracking branch 'origin/master' into configs 2020-09-01 13:42:55 +02:00
Eelco Dolstra
16cf4e8dca Merge remote-tracking branch 'origin/master' into configs 2020-08-31 16:40:35 +02:00
Eelco Dolstra
9f8c0040b6 Merge remote-tracking branch 'origin/master' into configs 2020-08-31 14:53:35 +02:00
Eelco Dolstra
13735a63b4 Add poor man's module system 2020-08-26 09:44:12 +02:00
Eelco Dolstra
38b339d447 Add 'nix list-options' command 2020-08-11 09:39:40 +02:00
21 changed files with 713 additions and 124 deletions

View File

@@ -1,4 +1,5 @@
corepkgs_FILES = \
fetchurl.nix
fetchurl.nix \
module.nix
$(foreach file,$(corepkgs_FILES),$(eval $(call install-data-in,$(d)/$(file),$(datadir)/nix/corepkgs)))

48
corepkgs/module.nix Normal file
View File

@@ -0,0 +1,48 @@
with builtins;
let
showPos = pos:
if pos == null
then "<unknown location>"
else "${pos.file}:${toString pos.line}:${toString pos.column}";
getAnyPos = attrs:
builtins.foldl' (prev: name: if prev == null then builtins.unsafeGetAttrPos name attrs else prev) null (builtins.attrNames attrs);
in
{ doc ? null, extends ? [], options ? {}, config ? ({ config }: {}) } @ inArgs:
let thisModule = rec {
type = "module";
_module = {
inherit extends options config;
} // (if doc != null then { inherit doc; } else {});
_allModules = [thisModule] ++ builtins.concatLists (map (mod: assert mod.type or "<untyped>" == "module"; mod._allModules) extends);
_allOptions = builtins.foldl' (xs: mod: xs // mod._module.options) {} _allModules;
_allConfigs = map (mod: mod._module.config { config = final; }) _allModules;
_allDefinitions = builtins.mapAttrs (name: value: map (x: x) (builtins.catAttrs name _allConfigs)) _allOptions;
final = builtins.mapAttrs
(name: defs:
if defs == []
then
_allOptions.${name}.default
or (throw "Option '${name}' is not defined by module at ${showPos (getAnyPos inArgs)} and has no default value.")
else
# FIXME: support merge functions.
if builtins.isList (builtins.head defs)
then builtins.concatLists defs
else
if builtins.isAttrs (builtins.head defs)
then builtins.foldl' (xs: ys: xs // ys) {} defs
else builtins.head defs)
_allDefinitions;
}; in thisModule

View File

@@ -1,11 +1,13 @@
with builtins;
lockFileStr: rootSrc: rootSubdir:
let
lockFile = builtins.fromJSON lockFileStr;
lockFile = fromJSON lockFileStr;
allNodes =
builtins.mapAttrs
mapAttrs
(key: node:
let
@@ -16,9 +18,55 @@ let
subdir = if key == lockFile.root then rootSubdir else node.locked.dir or "";
flake = import (sourceInfo + (if subdir != "" then "/" else "") + subdir + "/flake.nix");
flakeDir = sourceInfo + (if subdir != "" then "/" else "") + subdir;
inputs = builtins.mapAttrs
flake =
if pathExists (flakeDir + "/flake.nix")
then import (flakeDir + "/flake.nix")
else if pathExists (flakeDir + "/nix.toml")
then
# Convert nix.toml to a flake containing a 'modules'
# output.
let
toml = fromTOML (readFile (flakeDir + "/nix.toml"));
in {
inputs = toml.inputs or {};
outputs = inputs: {
modules =
listToAttrs (
map (moduleName:
let
m = toml.${moduleName};
in {
name = moduleName;
value = module {
extends =
map (flakeRef:
let
tokens = match ''(.*)#(.*)'' flakeRef;
in
assert tokens != null;
inputs.${elemAt tokens 0}.modules.${elemAt tokens 1}
) (m.extends or []);
config = { config }: listToAttrs (map
(optionName:
{ name = optionName;
value = m.${optionName};
}
)
(filter
(n: n != "extends" && n != "doc")
(attrNames m)));
};
})
(filter
(n: isAttrs toml.${n} && n != "inputs")
(attrNames toml)));
};
}
else throw "flake does not contain a 'flake.nix' or 'nix.toml'";
inputs = mapAttrs
(inputName: inputSpec: allNodes.${resolveInput inputSpec})
(node.inputs or {});
@@ -26,7 +74,7 @@ let
# either a node name, or a 'follows' path from the root
# node.
resolveInput = inputSpec:
if builtins.isList inputSpec
if isList inputSpec
then getInputByPath lockFile.root inputSpec
else inputSpec;
@@ -38,15 +86,15 @@ let
else
getInputByPath
# Since this could be a 'follows' input, call resolveInput.
(resolveInput lockFile.nodes.${nodeName}.inputs.${builtins.head path})
(builtins.tail path);
(resolveInput lockFile.nodes.${nodeName}.inputs.${head path})
(tail path);
outputs = flake.outputs (inputs // { self = result; });
result = outputs // sourceInfo // { inherit inputs; inherit outputs; inherit sourceInfo; };
in
if node.flake or true then
assert builtins.isFunction flake.outputs;
assert isFunction flake.outputs;
result
else
sourceInfo

View File

@@ -175,11 +175,8 @@ static Flake getFlake(
auto [sourceInfo, resolvedRef, lockedRef] = fetchOrSubstituteTree(
state, originalRef, allowLookup, flakeCache);
// Guard against symlink attacks.
auto flakeFile = canonPath(sourceInfo.actualPath + "/" + lockedRef.subdir + "/flake.nix");
if (!isInDir(flakeFile, sourceInfo.actualPath))
throw Error("'flake.nix' file of flake '%s' escapes from '%s'",
lockedRef, state.store->printStorePath(sourceInfo.storePath));
auto tomlFile = canonPath(sourceInfo.actualPath + "/" + lockedRef.subdir + "/nix.toml");
Flake flake {
.originalRef = originalRef,
@@ -188,55 +185,85 @@ static Flake getFlake(
.sourceInfo = std::make_shared<fetchers::Tree>(std::move(sourceInfo))
};
if (!pathExists(flakeFile))
throw Error("source tree referenced by '%s' does not contain a '%s/flake.nix' file", lockedRef, lockedRef.subdir);
Value vInfo;
state.evalFile(flakeFile, vInfo, true); // FIXME: symlink attack
expectType(state, tAttrs, vInfo, Pos(foFile, state.symbols.create(flakeFile), 0, 0));
auto sEdition = state.symbols.create("edition"); // FIXME: remove soon
if (vInfo.attrs->get(sEdition))
warn("flake '%s' has deprecated attribute 'edition'", lockedRef);
if (auto description = vInfo.attrs->get(state.sDescription)) {
expectType(state, tString, *description->value, *description->pos);
flake.description = description->value->string.s;
}
auto sInputs = state.symbols.create("inputs");
if (auto inputs = vInfo.attrs->get(sInputs))
flake.inputs = parseFlakeInputs(state, inputs->value, *inputs->pos);
auto sOutputs = state.symbols.create("outputs");
if (auto outputs = vInfo.attrs->get(sOutputs)) {
expectType(state, tLambda, *outputs->value, *outputs->pos);
if (pathExists(flakeFile)) {
// Guard against symlink attacks.
if (!isInDir(flakeFile, sourceInfo.actualPath))
throw Error("'flake.nix' file of flake '%s' escapes from '%s'",
lockedRef, state.store->printStorePath(sourceInfo.storePath));
if (outputs->value->lambda.fun->matchAttrs) {
for (auto & formal : outputs->value->lambda.fun->formals->formals) {
if (formal.name != state.sSelf)
flake.inputs.emplace(formal.name, FlakeInput {
.ref = parseFlakeRef(formal.name)
});
}
Value vInfo;
state.evalFile(flakeFile, vInfo, true); // FIXME: symlink attack
expectType(state, tAttrs, vInfo, Pos(foFile, state.symbols.create(flakeFile), 0, 0));
auto sEdition = state.symbols.create("edition"); // FIXME: remove soon
if (vInfo.attrs->get(sEdition))
warn("flake '%s' has deprecated attribute 'edition'", lockedRef);
if (auto description = vInfo.attrs->get(state.sDescription)) {
expectType(state, tString, *description->value, *description->pos);
flake.description = description->value->string.s;
}
} else
throw Error("flake '%s' lacks attribute 'outputs'", lockedRef);
if (auto inputs = vInfo.attrs->get(sInputs))
flake.inputs = parseFlakeInputs(state, inputs->value, *inputs->pos);
if (auto outputs = vInfo.attrs->get(sOutputs)) {
expectType(state, tLambda, *outputs->value, *outputs->pos);
if (outputs->value->lambda.fun->matchAttrs) {
for (auto & formal : outputs->value->lambda.fun->formals->formals) {
if (formal.name != state.sSelf)
flake.inputs.emplace(formal.name, FlakeInput {
.ref = parseFlakeRef(formal.name)
});
}
}
} else
throw Error("flake '%s' lacks attribute 'outputs'", lockedRef);
for (auto & attr : *vInfo.attrs) {
if (attr.name != sEdition &&
attr.name != state.sDescription &&
attr.name != sInputs &&
attr.name != sOutputs)
throw Error("flake '%s' has an unsupported attribute '%s', at %s",
lockedRef, attr.name, *attr.pos);
}
for (auto & attr : *vInfo.attrs) {
if (attr.name != sEdition &&
attr.name != state.sDescription &&
attr.name != sInputs &&
attr.name != sOutputs)
throw Error("flake '%s' has an unsupported attribute '%s', at %s",
lockedRef, attr.name, *attr.pos);
}
else if (pathExists(tomlFile)) {
// Guard against symlink attacks.
if (!isInDir(tomlFile, sourceInfo.actualPath))
throw Error("'nix.toml' file of flake '%s' escapes from '%s'",
lockedRef, state.store->printStorePath(sourceInfo.storePath));
auto vToml = state.allocValue();
mkString(*vToml, readFile(tomlFile));
auto vFlake = state.allocValue();
prim_fromTOML(state, noPos, &vToml, *vFlake);
state.forceAttrs(*vFlake);
if (auto description = vFlake->attrs->get(state.sDescription)) {
expectType(state, tString, *description->value, *description->pos);
flake.description = description->value->string.s;
}
if (auto inputs = vFlake->attrs->get(sInputs))
flake.inputs = parseFlakeInputs(state, inputs->value, *inputs->pos);
// FIXME: complain about unknown attributes.
}
else
throw Error("source tree referenced by '%1%' does not contain a '%2%/flake.nix' or '%2%/nix.toml' file %3%", lockedRef, lockedRef.subdir, flakeFile);
return flake;
}

View File

@@ -117,8 +117,8 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
if (!S_ISDIR(lstat(path).st_mode))
throw BadURL("path '%s' is not a flake (because it's not a directory)", path);
if (!allowMissing && !pathExists(path + "/flake.nix"))
throw BadURL("path '%s' is not a flake (because it doesn't contain a 'flake.nix' file)", path);
if (!allowMissing && !(pathExists(path + "/flake.nix") || pathExists(path + "/nix.toml")))
throw BadURL("path '%s' is not a flake (because it doesn't contain a 'flake.nix' or 'nix.toml' file)", path);
auto flakeRoot = path;
std::string subdir;

View File

@@ -3552,6 +3552,9 @@ void EvalState::createBaseEnv()
addPrimOp("__exec", 1, prim_exec);
}
evalFile(canonPath(settings.nixDataDir + "/nix/corepkgs/module.nix", true), v);
addConstant("module", v);
/* Add a value containing the current Nix expression search path. */
mkList(v, searchPath.size());
int n = 0;

View File

@@ -43,4 +43,6 @@ void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value
/* Execute a program and parse its output */
void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v);
void prim_fromTOML(EvalState & state, const Pos & pos, Value * * args, Value & v);
}

View File

@@ -5,7 +5,7 @@
namespace nix {
static void prim_fromTOML(EvalState & state, const Pos & pos, Value * * args, Value & v)
void prim_fromTOML(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
using namespace cpptoml;
@@ -17,6 +17,8 @@ static void prim_fromTOML(EvalState & state, const Pos & pos, Value * * args, Va
visit = [&](Value & v, std::shared_ptr<base> t) {
// FIXME: set attribute positions
if (auto t2 = t->as_table()) {
size_t size = 0;

View File

@@ -28,19 +28,30 @@ App Installable::toApp(EvalState & state)
};
}
else if (type == "derivation") {
auto drvPath = cursor->forceDerivation();
auto outPath = cursor->getAttr(state.sOutPath)->getString();
auto outputName = cursor->getAttr(state.sOutputName)->getString();
auto name = cursor->getAttr(state.sName)->getString();
auto getDerivation = [&](std::shared_ptr<eval_cache::AttrCursor> attr)
{
auto drvPath = attr->forceDerivation();
auto outPath = attr->getAttr(state.sOutPath)->getString();
auto outputName = attr->getAttr(state.sOutputName)->getString();
auto name = attr->getAttr(state.sName)->getString();
return App {
.context = { { drvPath, {outputName} } },
.program = outPath + "/bin/" + DrvName(name).name,
};
};
if (type == "derivation")
return getDerivation(cursor);
if (type == "module") {
// FIXME: define an 'app' option.
auto aDerivation = cursor->findAlongAttrPath(
{state.symbols.create("final"), state.symbols.create("derivation")});
if (aDerivation)
return getDerivation(aDerivation);
}
else
throw Error("attribute '%s' has unsupported type '%s'", attrPath, type);
throw Error("attribute '%s' has unsupported type '%s'", attrPath, type);
}
}

View File

@@ -9,7 +9,7 @@ using namespace nix;
struct CmdBuild : InstallablesCommand, MixDryRun, MixProfile
{
Path outLink = "result";
std::optional<Path> outLink = "result";
BuildMode buildMode = bmNormal;
CmdBuild()
@@ -26,7 +26,7 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixProfile
addFlag({
.longName = "no-link",
.description = "do not create a symlink to the build result",
.handler = {&outLink, Path("")},
.handler = {&outLink, {}},
});
addFlag({
@@ -65,19 +65,19 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixProfile
if (dryRun) return;
if (outLink != "")
if (outLink)
if (auto store2 = store.dynamic_pointer_cast<LocalFSStore>())
for (size_t i = 0; i < buildables.size(); ++i)
std::visit(overloaded {
[&](BuildableOpaque bo) {
std::string symlink = outLink;
std::string symlink = *outLink;
if (i) symlink += fmt("-%d", i);
store2->addPermRoot(bo.path, absPath(symlink));
},
[&](BuildableFromDrv bfd) {
auto builtOutputs = store->queryDerivationOutputMap(bfd.drvPath);
for (auto & output : builtOutputs) {
std::string symlink = outLink;
std::string symlink = *outLink;
if (i) symlink += fmt("-%d", i);
if (output.first != "out") symlink += fmt("-%s", output.first);
store2->addPermRoot(output.second, absPath(symlink));

View File

@@ -79,7 +79,7 @@ struct CmdBundle : InstallableCommand
auto bundler = InstallableFlake(
evalState, std::move(bundlerFlakeRef),
Strings{bundlerName == "" ? "defaultBundler" : bundlerName},
Strings({"bundlers."}), lockFlags);
Strings({"bundlers."}), lockFlags, nullptr);
Value * arg = evalState->allocValue();
evalState->mkAttrs(*arg, 2);

View File

@@ -58,6 +58,23 @@ struct MixFlakeOptions : virtual Args, EvalCommand
{ return {}; }
};
class FlakeCommand : virtual Args, public MixFlakeOptions
{
std::string flakeUrl = ".";
public:
FlakeCommand();
FlakeRef getFlakeRef();
flake::Flake getFlake();
flake::LockedFlake lockFlake();
std::optional<FlakeRef> getFlakeRefForCompletion() override;
};
/* How to handle derivations in commands that operate on store paths. */
enum class OperateOn {
/* Operate on the output path. */

View File

@@ -400,7 +400,8 @@ struct CmdDevelop : Common, MixEnvironment
installable->nixpkgsFlakeRef(),
Strings{"bashInteractive"},
Strings{"legacyPackages." + settings.thisSystem.get() + "."},
lockFlags);
lockFlags,
nullptr);
shell = state->store->printStorePath(
toStorePath(state->store, Realise::Outputs, OperateOn::Output, bashInstallable)) + "/bin/bash";

102
src/nix/doc.cc Normal file
View File

@@ -0,0 +1,102 @@
#include "command.hh"
#include "common-args.hh"
#include "eval-cache.hh"
#include "eval-inline.hh"
#include "local-fs-store.hh"
using namespace nix;
using namespace nix::flake;
// FIXME: move
StorePath buildDerivation(EvalState & state, Value & vDerivation)
{
state.forceValue(vDerivation);
if (!state.isDerivation(vDerivation))
throw Error("value did not evaluate to a derivation");
auto aDrvPath = vDerivation.attrs->get(state.sDrvPath);
assert(aDrvPath);
PathSet context;
auto drvPath = state.store->parseStorePath(state.coerceToPath(*aDrvPath->pos, *aDrvPath->value, context));
state.store->buildPaths({{drvPath}});
auto aOutPath = vDerivation.attrs->get(state.sOutPath);
assert(aOutPath);
auto outPath = state.store->parseStorePath(state.coerceToPath(*aOutPath->pos, *aOutPath->value, context));
assert(state.store->isValidPath(outPath));
return outPath;
}
struct CmdDoc : FlakeCommand
{
std::optional<Path> outLink = "flake-doc";
bool printMarkdown = false;
CmdDoc()
{
// FIXME: cut&paste from 'nix build'.
addFlag({
.longName = "out-link",
.shortName = 'o',
.description = "path of the symlink to the build result",
.labels = {"path"},
.handler = {&outLink},
.completer = completePath
});
addFlag({
.longName = "no-link",
.description = "do not create a symlink to the build result",
.handler = {&outLink, {}},
});
addFlag({
.longName = "print-markdown",
.description = "show markdown, don't generate an HTML book",
.handler = {&this->printMarkdown, true},
});
}
void run(nix::ref<nix::Store> store) override
{
auto state = getEvalState();
auto flake = std::make_shared<LockedFlake>(lockFlake());
auto vFlake = state->allocValue();
flake::callFlake(*state, *flake, *vFlake);
auto vFun = state->allocValue();
state->eval(state->parseExprFromString(
#include "doc.nix.gen.hh"
, "/"), *vFun);
auto vRes = state->allocValue();
state->callFunction(*vFun, *vFlake, *vRes, noPos);
state->forceAttrs(*vRes, noPos);
auto markdown = vRes->attrs->get(state->symbols.create("markdown"));
assert(markdown);
if (printMarkdown) {
logger->stdout(state->forceString(*markdown->value));
return;
}
auto mdbook = vRes->attrs->get(state->symbols.create("mdbook"));
assert(mdbook);
// FIXME: ugly, needed for getFlake.
evalSettings.pureEval = false;
auto path = buildDerivation(*state, *mdbook->value);
if (outLink)
if (auto store2 = store.dynamic_pointer_cast<LocalFSStore>())
store2->addPermRoot(path, absPath(*outLink));
}
};
static auto r1 = registerCommand<CmdDoc>("doc");

94
src/nix/doc.nix Normal file
View File

@@ -0,0 +1,94 @@
with builtins;
flake:
let
splitLines = s: filter (x: !isList x) (split "\n" s);
concatStrings = concatStringsSep "";
modules = flake.modules or {};
in
rec {
markdown =
# FIXME: split into multiple files.
"# Outputs\n\n"
+ concatStrings (map
(outputName:
" - `${outputName}` \n\n")
(attrNames flake.outputs))
+ (if modules != {} then
"# Modules\n\n"
+ concatStrings (map
(moduleName:
let
module = modules.${moduleName};
in
"## `${moduleName}`\n\n"
+ (if module._module.doc or "" != ""
then "### Synopsis\n\n${module._module.doc}\n\n"
else "")
+ (if module._module.options != {}
then
"### Options\n\n"
+ concatStrings (map
(optionName:
let option = module._module.options.${optionName}; in
" - `${optionName}` \n\n"
+ concatStrings (map (l: " ${l}\n") (splitLines option.doc))
+ "\n"
)
(attrNames module._module.options))
else "")
)
(attrNames modules))
else "");
mdbook =
let
nixpkgs = getFlake "nixpkgs";
pkgs = nixpkgs.legacyPackages.x86_64-linux; # FIXME
in
pkgs.runCommand "flake-doc"
{ buildInputs = [ pkgs.mdbook ];
}
''
mkdir $out
mkdir -p book/src
cat > book/book.toml <<EOF
[output.html]
additional-css = ["custom.css"]
EOF
cat > book/custom.css <<EOF
h1:not(:first-of-type) {
margin-top: 1.3em;
}
h2 {
margin-top: 1em;
}
h3 {
margin-top: 0.7em;
}
EOF
cat > book/src/SUMMARY.md <<EOF
# Table of Contents
- [Overview](overview.md)
EOF
cp ${toFile "flake.md" markdown} book/src/overview.md
mdbook build -d $out book
'';
}

View File

@@ -20,45 +20,38 @@
using namespace nix;
using namespace nix::flake;
class FlakeCommand : virtual Args, public MixFlakeOptions
FlakeCommand::FlakeCommand()
{
std::string flakeUrl = ".";
expectArgs({
.label = "flake-url",
.optional = true,
.handler = {&flakeUrl},
.completer = {[&](size_t, std::string_view prefix) {
completeFlakeRef(getStore(), prefix);
}}
});
}
public:
FlakeRef FlakeCommand::getFlakeRef()
{
return parseFlakeRef(flakeUrl, absPath(".")); //FIXME
}
FlakeCommand()
{
expectArgs({
.label = "flake-url",
.optional = true,
.handler = {&flakeUrl},
.completer = {[&](size_t, std::string_view prefix) {
completeFlakeRef(getStore(), prefix);
}}
});
}
Flake FlakeCommand::getFlake()
{
auto evalState = getEvalState();
return flake::getFlake(*evalState, getFlakeRef(), lockFlags.useRegistries);
}
FlakeRef getFlakeRef()
{
return parseFlakeRef(flakeUrl, absPath(".")); //FIXME
}
LockedFlake FlakeCommand::lockFlake()
{
return flake::lockFlake(*getEvalState(), getFlakeRef(), lockFlags);
}
Flake getFlake()
{
auto evalState = getEvalState();
return flake::getFlake(*evalState, getFlakeRef(), lockFlags.useRegistries);
}
LockedFlake lockFlake()
{
return flake::lockFlake(*getEvalState(), getFlakeRef(), lockFlags);
}
std::optional<FlakeRef> getFlakeRefForCompletion() override
{
return getFlakeRef();
}
};
std::optional<FlakeRef> FlakeCommand::getFlakeRefForCompletion()
{
return getFlakeRef();
}
static void printFlakeInfo(const Store & store, const Flake & flake)
{
@@ -570,7 +563,7 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand
auto installable = InstallableFlake(
evalState, std::move(templateFlakeRef),
Strings{templateName == "" ? "defaultTemplate" : templateName},
Strings(attrsPathPrefixes), lockFlags);
Strings(attrsPathPrefixes), lockFlags, nullptr);
auto [cursor, attrPath] = installable.getCursor(*evalState);

View File

@@ -13,6 +13,8 @@
#include "url.hh"
#include "registry.hh"
#include "../cpptoml/cpptoml.h"
#include <regex>
#include <queue>
@@ -149,7 +151,8 @@ Strings SourceExprCommand::getDefaultFlakeAttrPathPrefixes()
"packages." + settings.thisSystem.get() + ".",
// As a temporary hack until Nixpkgs is properly converted
// to provide a clean 'packages' set, look in 'legacyPackages'.
"legacyPackages." + settings.thisSystem.get() + "."
"legacyPackages." + settings.thisSystem.get() + ".",
"modules.",
};
}
@@ -464,21 +467,36 @@ std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo> InstallableF
auto root = cache->getRoot();
for (auto & attrPath : getActualAttrPaths()) {
auto getDerivation = [&](std::shared_ptr<eval_cache::AttrCursor> attr) -> std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo>
{
auto drvPath = attr->forceDerivation();
auto drvInfo = DerivationInfo{
std::move(drvPath),
state->store->parseStorePath(attr->getAttr(state->sOutPath)->getString()),
attr->getAttr(state->sOutputName)->getString()
};
return {attrPath, lockedFlake->flake.lockedRef, std::move(drvInfo)};
};
auto attr = root->findAlongAttrPath(parseAttrPath(*state, attrPath));
if (!attr) continue;
if (!attr->isDerivation())
throw Error("flake output attribute '%s' is not a derivation", attrPath);
auto aType = attr->maybeGetAttr(state->sType);
auto drvPath = attr->forceDerivation();
if (aType && aType->getString() == "derivation")
return getDerivation(attr);
auto drvInfo = DerivationInfo{
std::move(drvPath),
state->store->parseStorePath(attr->getAttr(state->sOutPath)->getString()),
attr->getAttr(state->sOutputName)->getString()
};
else if (aType && aType->getString() == "module") {
if (auto aDerivation = attr->findAlongAttrPath(
{state->symbols.create("final"), state->symbols.create("derivation")}))
if (aDerivation)
return getDerivation(aDerivation);
}
return {attrPath, lockedFlake->flake.lockedRef, std::move(drvInfo)};
throw Error("flake output attribute '%s' is not a derivation", attrPath);
}
throw Error("flake '%s' does not provide attribute %s",
@@ -516,8 +534,7 @@ std::pair<Value *, Pos> InstallableFlake::toValue(EvalState & state)
std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>>
InstallableFlake::getCursors(EvalState & state)
{
auto evalCache = openEvalCache(state,
std::make_shared<flake::LockedFlake>(lockFlake(state, flakeRef, lockFlags)));
auto evalCache = openEvalCache(state, getLockedFlake());
auto root = evalCache->getRoot();
@@ -533,8 +550,61 @@ InstallableFlake::getCursors(EvalState & state)
std::shared_ptr<flake::LockedFlake> InstallableFlake::getLockedFlake() const
{
if (!_lockedFlake)
if (!_lockedFlake) {
_lockedFlake = std::make_shared<flake::LockedFlake>(lockFlake(*state, flakeRef, lockFlags));
if (options && options->size()) {
/* Get the modules defined by this flake. */
auto cache = openEvalCache(*state, _lockedFlake);
auto root = cache->getRoot();
auto aModules = root->maybeGetAttr(state->symbols.create("modules"));
if (!aModules)
throw Error("flake '%s' has no modules, so --arg cannot override anything", flakeRef);
auto toml = cpptoml::make_table();
auto base = cpptoml::make_table();
base->insert("type", "path");
base->insert("path", _lockedFlake->flake.sourceInfo->actualPath);
if (_lockedFlake->flake.lockedRef.subdir != "")
base->insert("dir", _lockedFlake->flake.lockedRef.subdir);
// FIXME: copy rev etc.
auto inputs = cpptoml::make_table();
inputs->insert("base", base);
toml->insert("inputs", inputs);
for (auto & moduleName : aModules->getAttrs()) {
auto module = cpptoml::make_table();
auto extends = cpptoml::make_array();
extends->push_back("base#" + (std::string) moduleName);
module->insert("extends", extends);
for (auto & option : options->lexicographicOrder()) {
state->forceValue(*option->value);
if (option->value->type == tString)
module->insert(option->name, state->forceString(*option->value));
else if (option->value->type == tInt)
module->insert(option->name, option->value->integer);
else
throw Error("option '%s' is %s which is not supported",
option->name, showType(*option->value));
}
toml->insert(moduleName, module);
}
auto tempDir = createTempDir();
AutoDelete cleanup(tempDir);
std::ostringstream str;
str << *toml;
debug("writing temporary flake:\n%s", str.str());
writeFile(tempDir + "/nix.toml", str.str());
_lockedFlake = std::make_shared<flake::LockedFlake>(lockFlake(*state,
parseFlakeRef("path://" + tempDir), lockFlags));
}
}
return _lockedFlake;
}
@@ -584,10 +654,14 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
try {
auto [flakeRef, fragment] = parseFlakeRefWithFragment(s, absPath("."));
auto state = getEvalState();
result.push_back(std::make_shared<InstallableFlake>(
getEvalState(), std::move(flakeRef),
state,
std::move(flakeRef),
fragment == "" ? getDefaultFlakeAttrPaths() : Strings{fragment},
getDefaultFlakeAttrPathPrefixes(), lockFlags));
getDefaultFlakeAttrPathPrefixes(),
lockFlags,
getAutoArgs(*state)));
continue;
} catch (...) {
ex = std::current_exception();

View File

@@ -98,11 +98,17 @@ struct InstallableFlake : InstallableValue
Strings prefixes;
const flake::LockFlags & lockFlags;
mutable std::shared_ptr<flake::LockedFlake> _lockedFlake;
Bindings * options;
InstallableFlake(ref<EvalState> state, FlakeRef && flakeRef,
Strings && attrPaths, Strings && prefixes, const flake::LockFlags & lockFlags)
InstallableFlake(
ref<EvalState> state,
FlakeRef && flakeRef,
Strings && attrPaths,
Strings && prefixes, const
flake::LockFlags & lockFlags,
Bindings * options)
: InstallableValue(state), flakeRef(flakeRef), attrPaths(attrPaths),
prefixes(prefixes), lockFlags(lockFlags)
prefixes(prefixes), lockFlags(lockFlags), options(options)
{ }
std::string what() override { return flakeRef.to_string() + "#" + *attrPaths.begin(); }

View File

@@ -31,3 +31,5 @@ src/nix-env/user-env.cc: src/nix-env/buildenv.nix.gen.hh
src/nix/develop.cc: src/nix/get-env.sh.gen.hh
src/nix-channel/nix-channel.cc: src/nix-channel/unpack-channel.nix.gen.hh
src/nix/doc.cc: src/nix/doc.nix.gen.hh

154
src/nix/options.cc Normal file
View File

@@ -0,0 +1,154 @@
#include "command.hh"
#include "common-args.hh"
#include "shared.hh"
#include "store-api.hh"
#include "eval.hh"
#include "eval-inline.hh"
#include "json.hh"
#include "value-to-json.hh"
#include "../cpptoml/cpptoml.h"
using namespace nix;
struct CmdListOptions : InstallableCommand
{
CmdListOptions()
{
}
std::string description() override
{
return "show the options provided by a Nix configuration";
}
//Category category() override { return catSecondary; }
void run(ref<Store> store) override
{
auto state = getEvalState();
auto vModule = installable->toValue(*state).first;
state->forceAttrs(*vModule);
auto aAllOptions = vModule->attrs->get(state->symbols.create("_allOptions"));
if (!aAllOptions)
throw Error("module does not have an '_allOptions' attribute");
state->forceAttrs(*aAllOptions->value);
auto aFinal = vModule->attrs->get(state->symbols.create("final"));
if (!aFinal)
throw Error("module does not have an 'final' attribute");
state->forceAttrs(*aFinal->value);
for (const auto & [n, option] : enumerate(aAllOptions->value->attrs->lexicographicOrder())) {
if (n) logger->stdout("");
logger->stdout(ANSI_BOLD "%s" ANSI_NORMAL, option->name);
state->forceAttrs(*option->value);
std::string doc = ANSI_ITALIC "<no description>" ANSI_NORMAL;
auto aDoc = option->value->attrs->get(state->symbols.create("doc"));
if (aDoc)
// FIXME: render markdown.
doc = state->forceString(*aDoc->value);
logger->stdout(" " ANSI_BOLD "Description:" ANSI_NORMAL " %s", doc);
auto aValue = aFinal->value->attrs->get(option->name);
assert(aValue);
try {
std::ostringstream str;
JSONPlaceholder jsonOut(str);
PathSet context;
printValueAsJSON(*state, true, *aValue->value, jsonOut, context);
logger->stdout(" " ANSI_BOLD "Value:" ANSI_NORMAL " %s", str.str());
} catch (EvalError &) {
// FIXME: should ignore "no default" errors, print
// other errors.
logger->stdout(" " ANSI_BOLD "Value:" ANSI_NORMAL " " ANSI_ITALIC "none" ANSI_NORMAL);
}
}
}
};
static auto r1 = registerCommand<CmdListOptions>("list-options");
struct CmdSetOption : InstallableCommand
{
std::string name;
std::string value;
CmdSetOption()
{
// FIXME: type?
expectArg("name", &name);
expectArg("value", &value);
}
std::string description() override
{
return "set the value of an option in a Nix module";
}
Strings getDefaultFlakeAttrPathPrefixes()
{
return {
"modules.",
};
}
Strings getDefaultFlakeAttrPaths() override
{
// FIXME: support this elsewhere.
return {"modules.default"};
}
void run(ref<Store> store) override
{
auto state = getEvalState();
auto installable2 = std::dynamic_pointer_cast<InstallableFlake>(installable);
if (!installable2)
throw UsageError("'nix set-option' only works on flakes");
//auto flake = flake::getFlake(*state, installable2->flakeRef, false);
auto path = installable2->flakeRef.input.getSourcePath();
if (!path)
throw Error("'nix set-option' only works on writable flakes");
auto tomlPath = *path + "/" + installable2->flakeRef.subdir + "/nix.toml";
if (!pathExists(tomlPath))
throw Error("'nix set-option' only works on flakes that have a 'nix.toml' file");
std::istringstream tomlStream(readFile(tomlPath));
auto root = cpptoml::parser(tomlStream).parse();
auto moduleName = *installable2->attrPaths.begin();
//throw UsageError("'nix set-option' only works on modules, not '%s'", moduleName);
if (hasPrefix(moduleName, "modules."))
moduleName = std::string(moduleName, 8);
printError("PATH = %s %s", tomlPath, moduleName);
// FIXME: validate module name.
auto module = root->get_table(moduleName);
if (!module)
throw Error("flake '%s' does not have a module named '%s'",
installable2->flakeRef, moduleName);
// FIXME: check that option 'name' exists.
module->insert(name, value);
std::ostringstream str;
str << *root;
writeFile(tomlPath, str.str());
// FIXME: markChangedFile()?
}
};
static auto r2 = registerCommand<CmdSetOption>("set-option");

View File

@@ -342,7 +342,11 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf
Activity act(*logger, lvlChatty, actUnknown,
fmt("checking '%s' for updates", element.source->attrPath));
InstallableFlake installable(getEvalState(), FlakeRef(element.source->originalRef), {element.source->attrPath}, {}, lockFlags);
InstallableFlake installable(
getEvalState(),
FlakeRef(element.source->originalRef),
{element.source->attrPath},
{}, lockFlags, nullptr);
auto [attrPath, resolvedRef, drv] = installable.toDerivation();