Merge pull request #2909 from NixOS/flake-apps

Add 'nix app' command
This commit is contained in:
Eelco Dolstra 2019-06-03 09:34:36 +02:00 committed by GitHub
commit 507c150034
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 124 additions and 30 deletions

View file

@ -72,6 +72,14 @@ public:
return {};
}
Attr & need(const Symbol & name, const Pos & pos = noPos)
{
auto a = get(name);
if (!a)
throw Error("attribute '%s' missing, at %s", name, pos);
return **a;
}
iterator begin() { return &attrs[0]; }
iterator end() { return &attrs[size_]; }

View file

@ -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());

View file

@ -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;

View file

@ -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,43 +185,60 @@ 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();
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, cmd };
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(cmd.c_str(), stringsToCharPtrs(args).data());
throw SysError("unable to exec '%s'", cmd);
runProgram(store, *command.begin(), args);
}
};
static RegisterCommand r1(make_ref<CmdRun>());
struct CmdApp : InstallableCommand, RunCommon
{
CmdApp()
{
}
std::string name() override
{
return "app";
}
std::string description() override
{
return "run a Nix application";
}
Examples examples() override
{
return {
Example{
"To run Blender:",
"nix app blender-bin"
},
};
}
Strings getDefaultFlakeAttrPaths() override
{
return {"defaultApp"};
}
void run(ref<Store> store) override
{
auto state = getEvalState();
auto app = installable->toApp(*state);
state->realiseContext(app.context);
runProgram(store, app.program, {app.program});
}
};
static RegisterCommand r2(make_ref<CmdApp>());
void chrootHelper(int argc, char * * argv)
{
int p = 1;