aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthew Bauer <mjbauer95@gmail.com>2020-07-30 11:33:22 -0500
committerMatthew Bauer <mjbauer95@gmail.com>2020-07-30 11:33:22 -0500
commit2f4250a4167f2b1c10e5fa9c6163f84bb9bbd740 (patch)
tree2b4fc633b7d32df26f222a75033d0866526b9f06
parentc159f48a39835d5b2fe7c1ddd4467bc093ee251f (diff)
Add "export" to Nix
This adds a ‘nix export’ command which hooks into nix-bundle. It can be used in a similar way as nix-bundle, with the benefit of hooking into the new “app” functionality. For instance, $ nix export nixpkgs#jq $ ./jq --help jq - commandline JSON processor [version 1.6] ... $ scp jq machine-without-nix: $ ssh machine-without-nix ./jq jq - commandline JSON processor [version 1.6] ... Note that nix-bundle currently requires Linux to run. Other exporters might not have that requirement. “exporters” are meant to be reusable, so that, other repos can implement their own bundling. Fixes #3705
-rw-r--r--src/nix/export.cc126
-rw-r--r--src/nix/flake.cc25
2 files changed, 151 insertions, 0 deletions
diff --git a/src/nix/export.cc b/src/nix/export.cc
new file mode 100644
index 000000000..9e7816605
--- /dev/null
+++ b/src/nix/export.cc
@@ -0,0 +1,126 @@
+#include "command.hh"
+#include "common-args.hh"
+#include "shared.hh"
+#include "store-api.hh"
+#include "fs-accessor.hh"
+
+using namespace nix;
+
+struct CmdExport : InstallableCommand
+{
+ std::string exporter = "github:matthewbauer/nix-bundle";
+ Path outLink;
+
+ CmdExport()
+ {
+ addFlag({
+ .longName = "exporter",
+ .description = "use custom exporter",
+ .labels = {"flake-url"},
+ .handler = {&exporter},
+ .completer = {[&](size_t, std::string_view prefix) {
+ completeFlakeRef(getStore(), prefix);
+ }}
+ });
+
+ addFlag({
+ .longName = "out-link",
+ .shortName = 'o',
+ .description = "path of the symlink to the build result",
+ .labels = {"path"},
+ .handler = {&outLink},
+ .completer = completePath
+ });
+ }
+
+ std::string description() override
+ {
+ return "export an application out of the Nix store";
+ }
+
+ Examples examples() override
+ {
+ return {
+ Example{
+ "To export Hello:",
+ "nix export hello"
+ },
+ };
+ }
+
+ Strings getDefaultFlakeAttrPaths() override
+ {
+ Strings res{"defaultApp." + settings.thisSystem.get()};
+ for (auto & s : SourceExprCommand::getDefaultFlakeAttrPaths())
+ res.push_back(s);
+ return res;
+ }
+
+ Strings getDefaultFlakeAttrPathPrefixes() override
+ {
+ Strings res{"apps." + settings.thisSystem.get() + ".", "packages"};
+ for (auto & s : SourceExprCommand::getDefaultFlakeAttrPathPrefixes())
+ res.push_back(s);
+ return res;
+ }
+
+ void run(ref<Store> store) override
+ {
+ auto evalState = getEvalState();
+
+ auto app = installable->toApp(*evalState);
+ store->buildPaths(app.context);
+
+ auto [exporterFlakeRef, exporterName] = parseFlakeRefWithFragment(exporter, absPath("."));
+ const flake::LockFlags lockFlags{ .writeLockFile = false };
+ auto exporter = InstallableFlake(
+ evalState, std::move(exporterFlakeRef),
+ Strings{exporterName == "" ? ("defaultExporter." + settings.thisSystem.get()) : exporterName},
+ Strings({"exporters." + settings.thisSystem.get() + "."}), lockFlags);
+
+ Value * arg = evalState->allocValue();
+ evalState->mkAttrs(*arg, 1);
+
+ PathSet context;
+ for (auto & i : app.context)
+ context.insert("=" + store->printStorePath(i.path));
+ mkString(*evalState->allocAttr(*arg, evalState->symbols.create("program")), app.program, context);
+
+ auto vRes = evalState->allocValue();
+ evalState->callFunction(*exporter.toValue(*evalState).first, *arg, *vRes, noPos);
+
+ if (!evalState->isDerivation(*vRes))
+ throw Error("the exporter '%s' does not produce a derivation", exporter.what());
+
+ Bindings::iterator i = vRes->attrs->find(evalState->sDrvPath);
+ if (i == vRes->attrs->end())
+ throw Error("the exporter '%s' does not produce a derivation", exporter.what());
+
+ PathSet context2;
+ StorePath drvPath = store->parseStorePath(evalState->coerceToPath(*i->pos, *i->value, context2));
+
+ i = vRes->attrs->find(evalState->sOutPath);
+ if (i == vRes->attrs->end())
+ throw Error("the exporter '%s' does not produce a derivation", exporter.what());
+
+ StorePath outPath = store->parseStorePath(evalState->coerceToPath(*i->pos, *i->value, context2));
+
+ store->buildPaths({{drvPath}});
+
+ auto accessor = store->getFSAccessor();
+ auto outPathS = store->printStorePath(outPath);
+ if (accessor->stat(outPathS).type != FSAccessor::tRegular)
+ throw Error("'%s' is not a file; an exporter must only create a single file", outPathS);
+
+ auto info = store->queryPathInfo(outPath);
+ if (!info->references.empty())
+ throw Error("'%s' has references; an exporter must not leave any references", outPathS);
+
+ if (outLink == "")
+ outLink = baseNameOf(app.program);
+
+ store.dynamic_pointer_cast<LocalFSStore>()->addPermRoot(outPath, absPath(outLink), true);
+ }
+};
+
+static auto r2 = registerCommand<CmdExport>("export");
diff --git a/src/nix/flake.cc b/src/nix/flake.cc
index 027a9871e..db3ebdc2a 100644
--- a/src/nix/flake.cc
+++ b/src/nix/flake.cc
@@ -368,6 +368,21 @@ struct CmdFlakeCheck : FlakeCommand
}
};
+ auto checkExporter = [&](const std::string & attrPath, Value & v, const Pos & pos) {
+ try {
+ state->forceValue(v, pos);
+ if (v.type != tLambda)
+ throw Error("exporter must be a function");
+ if (!v.lambda.fun->formals ||
+ v.lambda.fun->formals->argNames.find(state->symbols.create("program")) == v.lambda.fun->formals->argNames.end() ||
+ v.lambda.fun->formals->argNames.find(state->symbols.create("args")) == v.lambda.fun->formals->argNames.end())
+ throw Error("exporter must take formal arguments 'program' and 'args'");
+ } catch (Error & e) {
+ e.addTrace(pos, hintfmt("while checking the template '%s'", attrPath));
+ throw;
+ }
+ };
+
{
Activity act(*logger, lvlInfo, actUnknown, "evaluating flake");
@@ -490,6 +505,16 @@ struct CmdFlakeCheck : FlakeCommand
*attr.value, *attr.pos);
}
+ else if (name == "defaultExporter")
+ checkExporter(name, vOutput, pos);
+
+ else if (name == "exporters") {
+ state->forceAttrs(vOutput, pos);
+ for (auto & attr : *vOutput.attrs)
+ checkExporter(fmt("%s.%s", name, attr.name),
+ *attr.value, *attr.pos);
+ }
+
else
warn("unknown flake output '%s'", name);