Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92c35d5303 | ||
|
|
974e0367df | ||
|
|
3632593bfc | ||
|
|
9f113fe6bf | ||
|
|
8f50523c38 | ||
|
|
dc4a280318 | ||
|
|
b068f96b92 | ||
|
|
1dc3f5355a | ||
|
|
08992ab6bc | ||
|
|
c559570c08 | ||
|
|
ca85bc5924 | ||
|
|
41795b43a8 | ||
|
|
0b337fca34 | ||
|
|
f731443384 | ||
|
|
73176ab160 | ||
|
|
22c96a784c | ||
|
|
7f11bc1b06 | ||
|
|
b7d263b79a | ||
|
|
22b8e07f09 | ||
|
|
d28c7b0982 | ||
|
|
d8a4a9f418 | ||
|
|
afebbb876f | ||
|
|
db204f40d2 | ||
|
|
f79b90f7ea | ||
|
|
16cf4e8dca | ||
|
|
9f8c0040b6 | ||
|
|
13735a63b4 | ||
|
|
38b339d447 |
@@ -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
48
corepkgs/module.nix
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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
102
src/nix/doc.cc
Normal 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
94
src/nix/doc.nix
Normal 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
|
||||
'';
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
@@ -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
154
src/nix/options.cc
Normal 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");
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user