aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEelco Dolstra <edolstra@gmail.com>2019-05-31 23:45:13 +0200
committerEelco Dolstra <edolstra@gmail.com>2019-06-03 09:27:03 +0200
commit5fbd9fee0b4b26cc7bcceb350e56e808c7a70e8c (patch)
treece0d254d9464714febd52df19fb402d7187103ee
parentfb692e5f7b34def8cf590298036ab63e40747062 (diff)
Add 'nix app' command
This is like 'nix run', except that the command to execute is defined in a flake output, e.g. defaultApp = { type = "app"; program = "${packages.blender_2_80}/bin/blender"; }; Thus you can do $ nix app blender-bin to start Blender from the 'blender-bin' flake. In the future, we can extend this with sandboxing. (For example we would want to be able to specify that Blender should not have network access by default and should only have access to certain paths in the user's home directory.)
-rw-r--r--src/nix/command.hh9
-rw-r--r--src/nix/installables.cc22
-rw-r--r--src/nix/run.cc101
3 files changed, 109 insertions, 23 deletions
diff --git a/src/nix/command.hh b/src/nix/command.hh
index 26c308331..659b724c3 100644
--- a/src/nix/command.hh
+++ b/src/nix/command.hh
@@ -38,6 +38,13 @@ struct Buildable
typedef std::vector<Buildable> Buildables;
+struct App
+{
+ PathSet context;
+ Path program;
+ // FIXME: add args, sandbox settings, metadata, ...
+};
+
struct Installable
{
virtual std::string what() = 0;
@@ -49,6 +56,8 @@ struct Installable
Buildable toBuildable();
+ App toApp(EvalState & state);
+
virtual Value * toValue(EvalState & state)
{
throw Error("argument '%s' cannot be evaluated", what());
diff --git a/src/nix/installables.cc b/src/nix/installables.cc
index eb3c27d6b..b6f05b314 100644
--- a/src/nix/installables.cc
+++ b/src/nix/installables.cc
@@ -68,6 +68,28 @@ Buildable Installable::toBuildable()
return std::move(buildables[0]);
}
+App Installable::toApp(EvalState & state)
+{
+ auto v = toValue(state);
+
+ state.forceAttrs(*v);
+
+ auto aType = v->attrs->need(state.sType);
+ if (state.forceStringNoCtx(*aType.value, *aType.pos) != "app")
+ throw Error("value does not have type 'app', at %s", *aType.pos);
+
+ App app;
+
+ auto aProgram = v->attrs->need(state.symbols.create("program"));
+ app.program = state.forceString(*aProgram.value, app.context, *aProgram.pos);
+
+ // FIXME: check that 'program' is in the closure of 'context'.
+ if (!state.store->isInStore(app.program))
+ throw Error("app program '%s' is not in the Nix store", app.program);
+
+ return app;
+}
+
struct InstallableStorePath : Installable
{
Path storePath;
diff --git a/src/nix/run.cc b/src/nix/run.cc
index 35b763345..00a682832 100644
--- a/src/nix/run.cc
+++ b/src/nix/run.cc
@@ -8,6 +8,7 @@
#include "fs-accessor.hh"
#include "progress-bar.hh"
#include "affinity.hh"
+#include "eval.hh"
#if __linux__
#include <sys/mount.h>
@@ -19,7 +20,44 @@ using namespace nix;
std::string chrootHelperName = "__run_in_chroot";
-struct CmdRun : InstallablesCommand
+struct RunCommon : virtual Command
+{
+ void runProgram(ref<Store> store,
+ const std::string & program,
+ const Strings & args)
+ {
+ stopProgressBar();
+
+ restoreSignals();
+
+ restoreAffinity();
+
+ /* If this is a diverted store (i.e. its "logical" location
+ (typically /nix/store) differs from its "physical" location
+ (e.g. /home/eelco/nix/store), then run the command in a
+ chroot. For non-root users, this requires running it in new
+ mount and user namespaces. Unfortunately,
+ unshare(CLONE_NEWUSER) doesn't work in a multithreaded
+ program (which "nix" is), so we exec() a single-threaded
+ helper program (chrootHelper() below) to do the work. */
+ auto store2 = store.dynamic_pointer_cast<LocalStore>();
+
+ if (store2 && store->storeDir != store2->realStoreDir) {
+ Strings helperArgs = { chrootHelperName, store->storeDir, store2->realStoreDir, program };
+ for (auto & arg : args) helperArgs.push_back(arg);
+
+ execv(readLink("/proc/self/exe").c_str(), stringsToCharPtrs(helperArgs).data());
+
+ throw SysError("could not execute chroot helper");
+ }
+
+ execvp(program.c_str(), stringsToCharPtrs(args).data());
+
+ throw SysError("unable to execute '%s'", program);
+ }
+};
+
+struct CmdRun : InstallablesCommand, RunCommon
{
std::vector<std::string> command = { "bash" };
StringSet keep, unset;
@@ -147,42 +185,59 @@ struct CmdRun : InstallablesCommand
setenv("PATH", concatStringsSep(":", unixPath).c_str(), 1);
- std::string cmd = *command.begin();
Strings args;
for (auto & arg : command) args.push_back(arg);
- stopProgressBar();
+ runProgram(store, *command.begin(), args);
+ }
+};
- restoreSignals();
+static RegisterCommand r1(make_ref<CmdRun>());
- restoreAffinity();
+struct CmdApp : InstallableCommand, RunCommon
+{
+ CmdApp()
+ {
+ }
- /* If this is a diverted store (i.e. its "logical" location
- (typically /nix/store) differs from its "physical" location
- (e.g. /home/eelco/nix/store), then run the command in a
- chroot. For non-root users, this requires running it in new
- mount and user namespaces. Unfortunately,
- unshare(CLONE_NEWUSER) doesn't work in a multithreaded
- program (which "nix" is), so we exec() a single-threaded
- helper program (chrootHelper() below) to do the work. */
- auto store2 = store.dynamic_pointer_cast<LocalStore>();
+ std::string name() override
+ {
+ return "app";
+ }
- if (store2 && store->storeDir != store2->realStoreDir) {
- Strings helperArgs = { chrootHelperName, store->storeDir, store2->realStoreDir, cmd };
- for (auto & arg : args) helperArgs.push_back(arg);
+ std::string description() override
+ {
+ return "run a Nix application";
+ }
- execv(readLink("/proc/self/exe").c_str(), stringsToCharPtrs(helperArgs).data());
+ Examples examples() override
+ {
+ return {
+ Example{
+ "To run Blender:",
+ "nix app blender-bin"
+ },
+ };
+ }
- throw SysError("could not execute chroot helper");
- }
+ Strings getDefaultFlakeAttrPaths() override
+ {
+ return {"defaultApp"};
+ }
- execvp(cmd.c_str(), stringsToCharPtrs(args).data());
+ void run(ref<Store> store) override
+ {
+ auto state = getEvalState();
+
+ auto app = installable->toApp(*state);
- throw SysError("unable to exec '%s'", cmd);
+ state->realiseContext(app.context);
+
+ runProgram(store, app.program, {app.program});
}
};
-static RegisterCommand r1(make_ref<CmdRun>());
+static RegisterCommand r2(make_ref<CmdApp>());
void chrootHelper(int argc, char * * argv)
{