nix flake init: Add a '--template' flag
The initial contents of the flake is specified by the 'templates.<name>' or 'defaultTemplate' output of another flake. E.g. outputs = { self }: { templates = { nixos-container = { path = ./nixos-container; description = "An example of a NixOS container"; }; }; }; allows $ nix flake init -t templates#nixos-container Also add a command 'nix flake new', which is identical to 'nix flake init' except that it initializes a specified directory rather than the current directory.
This commit is contained in:
parent
dc305500c3
commit
810b2c6a48
|
@ -320,6 +320,8 @@ Value & AttrCursor::forceValue()
|
|||
if (root->db && (!cachedValue || std::get_if<placeholder_t>(&cachedValue->second))) {
|
||||
if (v.type == tString)
|
||||
cachedValue = {root->db->setString(getKey(), v.string.s), v.string.s};
|
||||
else if (v.type == tPath)
|
||||
cachedValue = {root->db->setString(getKey(), v.path), v.path};
|
||||
else if (v.type == tBool)
|
||||
cachedValue = {root->db->setBool(getKey(), v.boolean), v.boolean};
|
||||
else if (v.type == tAttrs)
|
||||
|
@ -434,10 +436,10 @@ std::string AttrCursor::getString()
|
|||
|
||||
auto & v = forceValue();
|
||||
|
||||
if (v.type != tString)
|
||||
throw TypeError("'%s' is not a string", getAttrPathStr());
|
||||
if (v.type != tString && v.type != tPath)
|
||||
throw TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type));
|
||||
|
||||
return v.string.s;
|
||||
return v.type == tString ? v.string.s : v.path;
|
||||
}
|
||||
|
||||
bool AttrCursor::getBool()
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
description = "A flake for building Hello World";
|
||||
|
||||
outputs = { self, nixpkgs }: {
|
||||
|
||||
packages.x86_64-linux.hello = nixpkgs.legacyPackages.x86_64-linux.hello;
|
||||
|
||||
defaultPackage.x86_64-linux = self.packages.x86_64-linux.hello;
|
||||
|
||||
};
|
||||
}
|
192
src/nix/flake.cc
192
src/nix/flake.cc
|
@ -207,6 +207,8 @@ struct CmdFlakeCheck : FlakeCommand
|
|||
auto state = getEvalState();
|
||||
auto flake = lockFlake();
|
||||
|
||||
// FIXME: rewrite to use EvalCache.
|
||||
|
||||
auto checkSystemName = [&](const std::string & system, const Pos & pos) {
|
||||
// FIXME: what's the format of "system"?
|
||||
if (system.find('-') == std::string::npos)
|
||||
|
@ -320,6 +322,40 @@ struct CmdFlakeCheck : FlakeCommand
|
|||
}
|
||||
};
|
||||
|
||||
auto checkTemplate = [&](const std::string & attrPath, Value & v, const Pos & pos) {
|
||||
try {
|
||||
Activity act(*logger, lvlChatty, actUnknown,
|
||||
fmt("checking template '%s'", attrPath));
|
||||
|
||||
state->forceAttrs(v, pos);
|
||||
|
||||
if (auto attr = v.attrs->get(state->symbols.create("path"))) {
|
||||
if (attr->name == state->symbols.create("path")) {
|
||||
PathSet context;
|
||||
auto path = state->coerceToPath(*attr->pos, *attr->value, context);
|
||||
if (!store->isInStore(path))
|
||||
throw Error("template '%s' has a bad 'path' attribute");
|
||||
// TODO: recursively check the flake in 'path'.
|
||||
}
|
||||
} else
|
||||
throw Error("template '%s' lacks attribute 'path'", attrPath);
|
||||
|
||||
if (auto attr = v.attrs->get(state->symbols.create("description")))
|
||||
state->forceStringNoCtx(*attr->value, *attr->pos);
|
||||
else
|
||||
throw Error("template '%s' lacks attribute 'description'", attrPath);
|
||||
|
||||
for (auto & attr : *v.attrs) {
|
||||
std::string name(attr.name);
|
||||
if (name != "path" && name != "description")
|
||||
throw Error("template '%s' has unsupported attribute '%s'", attrPath, name);
|
||||
}
|
||||
} catch (Error & e) {
|
||||
e.addPrefix(fmt("while checking the template '" ANSI_BOLD "%s" ANSI_NORMAL "' at %s:\n", attrPath, pos));
|
||||
throw;
|
||||
}
|
||||
};
|
||||
|
||||
{
|
||||
Activity act(*logger, lvlInfo, actUnknown, "evaluating flake");
|
||||
|
||||
|
@ -432,6 +468,16 @@ struct CmdFlakeCheck : FlakeCommand
|
|||
else if (name == "hydraJobs")
|
||||
checkHydraJobs(name, vOutput, pos);
|
||||
|
||||
else if (name == "defaultTemplate")
|
||||
checkTemplate(name, vOutput, pos);
|
||||
|
||||
else if (name == "templates") {
|
||||
state->forceAttrs(vOutput, pos);
|
||||
for (auto & attr : *vOutput.attrs)
|
||||
checkTemplate(fmt("%s.%s", name, attr.name),
|
||||
*attr.value, *attr.pos);
|
||||
}
|
||||
|
||||
else
|
||||
warn("unknown flake output '%s'", name);
|
||||
|
||||
|
@ -449,29 +495,135 @@ struct CmdFlakeCheck : FlakeCommand
|
|||
}
|
||||
};
|
||||
|
||||
struct CmdFlakeInit : virtual Args, Command
|
||||
struct CmdFlakeInitCommon : virtual Args, EvalCommand
|
||||
{
|
||||
std::string templateUrl = "templates";
|
||||
Path destDir;
|
||||
|
||||
CmdFlakeInitCommon()
|
||||
{
|
||||
addFlag({
|
||||
.longName = "template",
|
||||
.shortName = 't',
|
||||
.description = "the template to use",
|
||||
.labels = {"template"},
|
||||
.handler = {&templateUrl},
|
||||
.completer = {[&](size_t, std::string_view prefix) {
|
||||
completeFlakeRef(prefix);
|
||||
}}
|
||||
});
|
||||
}
|
||||
|
||||
void run(nix::ref<nix::Store> store) override
|
||||
{
|
||||
auto flakeDir = absPath(destDir);
|
||||
|
||||
auto evalState = getEvalState();
|
||||
|
||||
auto [templateFlakeRef, templateName] = parseFlakeRefWithFragment(templateUrl, absPath("."));
|
||||
|
||||
auto installable = InstallableFlake(
|
||||
evalState, std::move(templateFlakeRef),
|
||||
Strings{templateName == "" ? "defaultTemplate" : templateName},
|
||||
Strings{"templates."}, { .writeLockFile = false });
|
||||
|
||||
auto cursor = installable.getCursor(*evalState, true);
|
||||
|
||||
auto templateDir = cursor.first->getAttr("path")->getString();
|
||||
|
||||
assert(store->isInStore(templateDir));
|
||||
|
||||
std::vector<Path> files;
|
||||
|
||||
std::function<void(const Path & from, const Path & to)> copyDir;
|
||||
copyDir = [&](const Path & from, const Path & to)
|
||||
{
|
||||
createDirs(to);
|
||||
|
||||
for (auto & entry : readDirectory(from)) {
|
||||
auto from2 = from + "/" + entry.name;
|
||||
auto to2 = to + "/" + entry.name;
|
||||
auto st = lstat(from2);
|
||||
if (S_ISDIR(st.st_mode))
|
||||
copyDir(from2, to2);
|
||||
else if (S_ISREG(st.st_mode)) {
|
||||
auto contents = readFile(from2);
|
||||
if (pathExists(to2)) {
|
||||
auto contents2 = readFile(to2);
|
||||
if (contents != contents2)
|
||||
throw Error("refusing to overwrite existing file '%s'", to2);
|
||||
} else
|
||||
writeFile(to2, contents);
|
||||
}
|
||||
else if (S_ISLNK(st.st_mode)) {
|
||||
auto target = readLink(from2);
|
||||
if (pathExists(to2)) {
|
||||
if (readLink(to2) != target)
|
||||
throw Error("refusing to overwrite existing symlink '%s'", to2);
|
||||
} else
|
||||
createSymlink(target, to2);
|
||||
}
|
||||
else
|
||||
throw Error("file '%s' has unsupported type", from2);
|
||||
files.push_back(to2);
|
||||
}
|
||||
};
|
||||
|
||||
copyDir(templateDir, flakeDir);
|
||||
|
||||
if (pathExists(flakeDir + "/.git")) {
|
||||
Strings args = { "-C", flakeDir, "add", "--intent-to-add", "--" };
|
||||
for (auto & s : files) args.push_back(s);
|
||||
runProgram("git", true, args);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct CmdFlakeInit : CmdFlakeInitCommon
|
||||
{
|
||||
std::string description() override
|
||||
{
|
||||
return "create a skeleton 'flake.nix' file in the current directory";
|
||||
return "create a flake in the current directory from a template";
|
||||
}
|
||||
|
||||
void run() override
|
||||
Examples examples() override
|
||||
{
|
||||
Path flakeDir = absPath(".");
|
||||
return {
|
||||
Example{
|
||||
"To create a flake using the default template:",
|
||||
"nix flake init"
|
||||
},
|
||||
Example{
|
||||
"To see available templates:",
|
||||
"nix flake show templates"
|
||||
},
|
||||
Example{
|
||||
"To create a flake from a specific template:",
|
||||
"nix flake init -t templates#nixos-container"
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Path flakePath = flakeDir + "/flake.nix";
|
||||
CmdFlakeInit()
|
||||
{
|
||||
destDir = ".";
|
||||
}
|
||||
};
|
||||
|
||||
if (pathExists(flakePath))
|
||||
throw Error("file '%s' already exists", flakePath);
|
||||
struct CmdFlakeNew : CmdFlakeInitCommon
|
||||
{
|
||||
std::string description() override
|
||||
{
|
||||
return "create a flake in the specified directory from a template";
|
||||
}
|
||||
|
||||
writeFile(flakePath,
|
||||
#include "flake-template.nix.gen.hh"
|
||||
);
|
||||
|
||||
if (pathExists(flakeDir + "/.git"))
|
||||
runProgram("git", true,
|
||||
{ "-C", flakeDir, "add", "--intent-to-add", "flake.nix" });
|
||||
CmdFlakeNew()
|
||||
{
|
||||
expectArgs({
|
||||
.label = "dest-dir",
|
||||
.handler = {&destDir},
|
||||
.completer = completePath
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -662,7 +814,8 @@ struct CmdFlakeShow : FlakeCommand
|
|||
|| attrPath[0] == "devShell"
|
||||
|| attrPath[0] == "nixosConfigurations"
|
||||
|| attrPath[0] == "nixosModules"
|
||||
|| attrPath[0] == "defaultApp"))
|
||||
|| attrPath[0] == "defaultApp"
|
||||
|| attrPath[0] == "templates"))
|
||||
|| ((attrPath.size() == 1 || attrPath.size() == 2)
|
||||
&& (attrPath[0] == "checks"
|
||||
|| attrPath[0] == "packages"
|
||||
|
@ -714,6 +867,14 @@ struct CmdFlakeShow : FlakeCommand
|
|||
logger->stdout("%s: app", headerPrefix);
|
||||
}
|
||||
|
||||
else if (
|
||||
(attrPath.size() == 1 && attrPath[0] == "defaultTemplate") ||
|
||||
(attrPath.size() == 2 && attrPath[0] == "templates"))
|
||||
{
|
||||
auto description = visitor.getAttr("description")->getString();
|
||||
logger->stdout("%s: template: " ANSI_BOLD "%s" ANSI_NORMAL, headerPrefix, description);
|
||||
}
|
||||
|
||||
else {
|
||||
logger->stdout("%s: %s",
|
||||
headerPrefix,
|
||||
|
@ -743,6 +904,7 @@ struct CmdFlake : virtual MultiCommand, virtual Command
|
|||
{"list-inputs", []() { return make_ref<CmdFlakeListInputs>(); }},
|
||||
{"check", []() { return make_ref<CmdFlakeCheck>(); }},
|
||||
{"init", []() { return make_ref<CmdFlakeInit>(); }},
|
||||
{"new", []() { return make_ref<CmdFlakeNew>(); }},
|
||||
{"clone", []() { return make_ref<CmdFlakeClone>(); }},
|
||||
{"archive", []() { return make_ref<CmdFlakeArchive>(); }},
|
||||
{"show", []() { return make_ref<CmdFlakeShow>(); }},
|
||||
|
|
|
@ -237,7 +237,7 @@ App Installable::toApp(EvalState & state)
|
|||
}
|
||||
|
||||
std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>>
|
||||
Installable::getCursor(EvalState & state, bool useEvalCache)
|
||||
Installable::getCursors(EvalState & state, bool useEvalCache)
|
||||
{
|
||||
auto evalCache =
|
||||
std::make_shared<nix::eval_cache::EvalCache>(false, Hash(), state,
|
||||
|
@ -245,6 +245,15 @@ Installable::getCursor(EvalState & state, bool useEvalCache)
|
|||
return {{evalCache->getRoot(), ""}};
|
||||
}
|
||||
|
||||
std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>
|
||||
Installable::getCursor(EvalState & state, bool useEvalCache)
|
||||
{
|
||||
auto cursors = getCursors(state, useEvalCache);
|
||||
if (cursors.empty())
|
||||
throw Error("cannot find flake attribute '%s'", what());
|
||||
return cursors[0];
|
||||
}
|
||||
|
||||
struct InstallableStorePath : Installable
|
||||
{
|
||||
ref<Store> store;
|
||||
|
@ -474,7 +483,7 @@ std::pair<Value *, Pos> InstallableFlake::toValue(EvalState & state)
|
|||
}
|
||||
|
||||
std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>>
|
||||
InstallableFlake::getCursor(EvalState & state, bool useEvalCache)
|
||||
InstallableFlake::getCursors(EvalState & state, bool useEvalCache)
|
||||
{
|
||||
auto evalCache = openEvalCache(state,
|
||||
std::make_shared<flake::LockedFlake>(lockFlake(state, flakeRef, lockFlags)),
|
||||
|
|
|
@ -56,6 +56,9 @@ struct Installable
|
|||
}
|
||||
|
||||
virtual std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>>
|
||||
getCursors(EvalState & state, bool useEvalCache);
|
||||
|
||||
std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>
|
||||
getCursor(EvalState & state, bool useEvalCache);
|
||||
|
||||
virtual FlakeRef nixpkgsFlakeRef() const
|
||||
|
@ -109,7 +112,7 @@ struct InstallableFlake : InstallableValue
|
|||
std::pair<Value *, Pos> toValue(EvalState & state) override;
|
||||
|
||||
std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>>
|
||||
getCursor(EvalState & state, bool useEvalCache) override;
|
||||
getCursors(EvalState & state, bool useEvalCache) override;
|
||||
|
||||
std::shared_ptr<flake::LockedFlake> getLockedFlake() const;
|
||||
|
||||
|
|
|
@ -29,5 +29,3 @@ $(eval $(call install-symlink, $(bindir)/nix, $(libexecdir)/nix/build-remote))
|
|||
src/nix-env/user-env.cc: src/nix-env/buildenv.nix.gen.hh
|
||||
|
||||
src/nix/develop.cc: src/nix/get-env.sh.gen.hh
|
||||
|
||||
$(d)/flake.cc: $(d)/flake-template.nix.gen.hh
|
||||
|
|
|
@ -177,7 +177,7 @@ struct CmdSearch : InstallableCommand, MixJSON
|
|||
}
|
||||
};
|
||||
|
||||
for (auto & [cursor, prefix] : installable->getCursor(*state, true))
|
||||
for (auto & [cursor, prefix] : installable->getCursors(*state, true))
|
||||
visit(*cursor, parseAttrPath(*state, prefix));
|
||||
|
||||
if (!json && !results)
|
||||
|
|
|
@ -20,12 +20,14 @@ flake2Dir=$TEST_ROOT/flake2
|
|||
flake3Dir=$TEST_ROOT/flake3
|
||||
flake4Dir=$TEST_ROOT/flake4
|
||||
flake5Dir=$TEST_ROOT/flake5
|
||||
flake6Dir=$TEST_ROOT/flake6
|
||||
flake7Dir=$TEST_ROOT/flake7
|
||||
templatesDir=$TEST_ROOT/templates
|
||||
nonFlakeDir=$TEST_ROOT/nonFlake
|
||||
flakeA=$TEST_ROOT/flakeA
|
||||
flakeB=$TEST_ROOT/flakeB
|
||||
|
||||
for repo in $flake1Dir $flake2Dir $flake3Dir $flake7Dir $nonFlakeDir $flakeA $flakeB; do
|
||||
for repo in $flake1Dir $flake2Dir $flake3Dir $flake7Dir $templatesDir $nonFlakeDir $flakeA $flakeB; do
|
||||
rm -rf $repo $repo.tmp
|
||||
mkdir $repo
|
||||
git -C $repo init
|
||||
|
@ -145,13 +147,22 @@ cat > $registry <<EOF
|
|||
"type": "indirect",
|
||||
"id": "flake1"
|
||||
}
|
||||
},
|
||||
{ "from": {
|
||||
"type": "indirect",
|
||||
"id": "templates"
|
||||
},
|
||||
"to": {
|
||||
"type": "git",
|
||||
"url": "file://$templatesDir"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
|
||||
# Test 'nix flake list'.
|
||||
[[ $(nix registry list | wc -l) == 6 ]]
|
||||
[[ $(nix registry list | wc -l) == 7 ]]
|
||||
|
||||
# Test 'nix flake info'.
|
||||
nix flake info flake1 | grep -q 'URL: .*flake1.*'
|
||||
|
@ -392,18 +403,61 @@ nix build -o $TEST_ROOT/result flake4/removeXyzzy#sth
|
|||
|
||||
# Testing the nix CLI
|
||||
nix registry add flake1 flake3
|
||||
[[ $(nix registry list | wc -l) == 7 ]]
|
||||
[[ $(nix registry list | wc -l) == 8 ]]
|
||||
nix registry pin flake1
|
||||
[[ $(nix registry list | wc -l) == 7 ]]
|
||||
[[ $(nix registry list | wc -l) == 8 ]]
|
||||
nix registry remove flake1
|
||||
[[ $(nix registry list | wc -l) == 6 ]]
|
||||
[[ $(nix registry list | wc -l) == 7 ]]
|
||||
|
||||
# Test 'nix flake init'.
|
||||
cat > $templatesDir/flake.nix <<EOF
|
||||
{
|
||||
description = "Some templates";
|
||||
|
||||
outputs = { self }: {
|
||||
templates = {
|
||||
trivial = {
|
||||
path = ./trivial;
|
||||
description = "A trivial flake";
|
||||
};
|
||||
};
|
||||
defaultTemplate = self.templates.trivial;
|
||||
};
|
||||
}
|
||||
EOF
|
||||
|
||||
mkdir $templatesDir/trivial
|
||||
|
||||
cat > $templatesDir/trivial/flake.nix <<EOF
|
||||
{
|
||||
description = "A flake for building Hello World";
|
||||
|
||||
outputs = { self, nixpkgs }: {
|
||||
packages.x86_64-linux.hello = nixpkgs.legacyPackages.x86_64-linux.hello;
|
||||
defaultPackage.x86_64-linux = self.packages.x86_64-linux.hello;
|
||||
};
|
||||
}
|
||||
EOF
|
||||
|
||||
git -C $templatesDir add flake.nix trivial/flake.nix
|
||||
git -C $templatesDir commit -m 'Initial'
|
||||
|
||||
nix flake check templates
|
||||
nix flake show templates
|
||||
|
||||
(cd $flake7Dir && nix flake init)
|
||||
(cd $flake7Dir && nix flake init) # check idempotence
|
||||
git -C $flake7Dir add flake.nix
|
||||
nix flake check $flake7Dir
|
||||
nix flake show $flake7Dir
|
||||
git -C $flake7Dir commit -a -m 'Initial'
|
||||
|
||||
# Test 'nix flake new'.
|
||||
rm -rf $flake6Dir
|
||||
nix flake new -t templates#trivial $flake6Dir
|
||||
nix flake new -t templates#trivial $flake6Dir # check idempotence
|
||||
nix flake check $flake6Dir
|
||||
|
||||
# Test 'nix flake clone'.
|
||||
rm -rf $TEST_ROOT/flake1-v2
|
||||
nix flake clone flake1 --dest $TEST_ROOT/flake1-v2
|
||||
|
@ -663,4 +717,4 @@ git -C $flakeB commit -a -m 'Foo'
|
|||
[[ $(nix eval --update-input b $flakeA#foo) = 1912 ]]
|
||||
|
||||
# Test list-inputs with circular dependencies
|
||||
nix flake list-inputs $flakeA
|
||||
nix flake list-inputs $flakeA
|
||||
|
|
Loading…
Reference in a new issue