Skip to content

Commit

Permalink
Backport 'nix dev-shell' from the flakes branch
Browse files Browse the repository at this point in the history
This also adds a '--profile' option to 'nix build' (replacing 'nix-env
--set').
  • Loading branch information
edolstra committed Mar 30, 2020
1 parent 367577d commit e1a94ad
Show file tree
Hide file tree
Showing 9 changed files with 569 additions and 116 deletions.
11 changes: 11 additions & 0 deletions src/libutil/util.cc
Expand Up @@ -478,6 +478,17 @@ Path createTempDir(const Path & tmpRoot, const Path & prefix,
}


std::pair<AutoCloseFD, Path> createTempFile(const Path & prefix)
{
Path tmpl(getEnv("TMPDIR").value_or("/tmp") + "/" + prefix + ".XXXXXX");
// Strictly speaking, this is UB, but who cares...
AutoCloseFD fd(mkstemp((char *) tmpl.c_str()));
if (!fd)
throw SysError("creating temporary file '%s'", tmpl);
return {std::move(fd), tmpl};
}


std::string getUserName()
{
auto pw = getpwuid(geteuid());
Expand Down
12 changes: 8 additions & 4 deletions src/libutil/util.hh
Expand Up @@ -122,10 +122,6 @@ void deletePath(const Path & path);

void deletePath(const Path & path, unsigned long long & bytesFreed);

/* Create a temporary directory. */
Path createTempDir(const Path & tmpRoot = "", const Path & prefix = "nix",
bool includePid = true, bool useGlobalCounter = true, mode_t mode = 0755);

std::string getUserName();

/* Return $HOME or the user's home directory from /etc/passwd. */
Expand Down Expand Up @@ -205,6 +201,14 @@ public:
};


/* Create a temporary directory. */
Path createTempDir(const Path & tmpRoot = "", const Path & prefix = "nix",
bool includePid = true, bool useGlobalCounter = true, mode_t mode = 0755);

/* Create a temporary file, returning a file handle and its path. */
std::pair<AutoCloseFD, Path> createTempFile(const Path & prefix = "nix");


class Pipe
{
public:
Expand Down
17 changes: 11 additions & 6 deletions src/nix/build.cc
Expand Up @@ -5,7 +5,7 @@

using namespace nix;

struct CmdBuild : MixDryRun, InstallablesCommand
struct CmdBuild : InstallablesCommand, MixDryRun, MixProfile
{
Path outLink = "result";

Expand Down Expand Up @@ -40,6 +40,10 @@ struct CmdBuild : MixDryRun, InstallablesCommand
"To build the build.x86_64-linux attribute from release.nix:",
"nix build -f release.nix build.x86_64-linux"
},
Example{
"To make a profile point at GNU Hello:",
"nix build --profile /tmp/profile nixpkgs.hello"
},
};
}

Expand All @@ -49,18 +53,19 @@ struct CmdBuild : MixDryRun, InstallablesCommand

if (dryRun) return;

for (size_t i = 0; i < buildables.size(); ++i) {
auto & b(buildables[i]);

if (outLink != "")
for (auto & output : b.outputs)
if (outLink != "") {
for (size_t i = 0; i < buildables.size(); ++i) {
for (auto & output : buildables[i].outputs)
if (auto store2 = store.dynamic_pointer_cast<LocalFSStore>()) {
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), true);
}
}
}

updateProfile(buildables);
}
};

Expand Down
94 changes: 94 additions & 0 deletions src/nix/command.cc
Expand Up @@ -2,6 +2,9 @@
#include "store-api.hh"
#include "derivations.hh"
#include "nixexpr.hh"
#include "profiles.hh"

extern char * * environ;

namespace nix {

Expand Down Expand Up @@ -96,4 +99,95 @@ Strings editorFor(const Pos & pos)
return args;
}

MixProfile::MixProfile()
{
mkFlag()
.longName("profile")
.description("profile to update")
.labels({"path"})
.dest(&profile);
}

void MixProfile::updateProfile(const StorePath & storePath)
{
if (!profile) return;
auto store = getStore().dynamic_pointer_cast<LocalFSStore>();
if (!store) throw Error("'--profile' is not supported for this Nix store");
auto profile2 = absPath(*profile);
switchLink(profile2,
createGeneration(
ref<LocalFSStore>(store),
profile2, store->printStorePath(storePath)));
}

void MixProfile::updateProfile(const Buildables & buildables)
{
if (!profile) return;

std::optional<StorePath> result;

for (auto & buildable : buildables) {
for (auto & output : buildable.outputs) {
if (result)
throw Error("'--profile' requires that the arguments produce a single store path, but there are multiple");
result = output.second.clone();
}
}

if (!result)
throw Error("'--profile' requires that the arguments produce a single store path, but there are none");

updateProfile(*result);
}

MixDefaultProfile::MixDefaultProfile()
{
profile = getDefaultProfile();
}

MixEnvironment::MixEnvironment() : ignoreEnvironment(false) {
mkFlag()
.longName("ignore-environment")
.shortName('i')
.description("clear the entire environment (except those specified with --keep)")
.set(&ignoreEnvironment, true);

mkFlag()
.longName("keep")
.shortName('k')
.description("keep specified environment variable")
.arity(1)
.labels({"name"})
.handler([&](std::vector<std::string> ss) { keep.insert(ss.front()); });

mkFlag()
.longName("unset")
.shortName('u')
.description("unset specified environment variable")
.arity(1)
.labels({"name"})
.handler([&](std::vector<std::string> ss) { unset.insert(ss.front()); });
}

void MixEnvironment::setEnviron() {
if (ignoreEnvironment) {
if (!unset.empty())
throw UsageError("--unset does not make sense with --ignore-environment");

for (const auto & var : keep) {
auto val = getenv(var.c_str());
if (val) stringsEnv.emplace_back(fmt("%s=%s", var.c_str(), val));
}

vectorEnv = stringsToCharPtrs(stringsEnv);
environ = vectorEnv.data();
} else {
if (!keep.empty())
throw UsageError("--keep does not make sense without --ignore-environment");

for (const auto & var : unset)
unsetenv(var.c_str());
}
}

}
62 changes: 34 additions & 28 deletions src/nix/command.hh
@@ -1,5 +1,6 @@
#pragma once

#include "installables.hh"
#include "args.hh"
#include "common-eval-args.hh"
#include "path.hh"
Expand All @@ -22,34 +23,7 @@ private:
std::shared_ptr<Store> _store;
};

struct Buildable
{
std::optional<StorePath> drvPath;
std::map<std::string, StorePath> outputs;
};

typedef std::vector<Buildable> Buildables;

struct Installable
{
virtual ~Installable() { }

virtual std::string what() = 0;

virtual Buildables toBuildables()
{
throw Error("argument '%s' cannot be built", what());
}

Buildable toBuildable();

virtual std::pair<Value *, Pos> toValue(EvalState & state)
{
throw Error("argument '%s' cannot be evaluated", what());
}
};

struct SourceExprCommand : virtual Args, StoreCommand, MixEvalArgs
struct SourceExprCommand : virtual StoreCommand, MixEvalArgs
{
Path file;

Expand Down Expand Up @@ -184,4 +158,36 @@ std::set<StorePath> toDerivations(ref<Store> store,
filename:lineno. */
Strings editorFor(const Pos & pos);

struct MixProfile : virtual StoreCommand
{
std::optional<Path> profile;

MixProfile();

/* If 'profile' is set, make it point at 'storePath'. */
void updateProfile(const StorePath & storePath);

/* If 'profile' is set, make it point at the store path produced
by 'buildables'. */
void updateProfile(const Buildables & buildables);
};

struct MixDefaultProfile : MixProfile
{
MixDefaultProfile();
};

struct MixEnvironment : virtual Args {

StringSet keep, unset;
Strings stringsEnv;
std::vector<char*> vectorEnv;
bool ignoreEnvironment;

MixEnvironment();

/* Modify global environ based on ignoreEnvironment, keep, and unset. It's expected that exec will be called before this class goes out of scope, otherwise environ will become invalid. */
void setEnviron();
};

}
5 changes: 5 additions & 0 deletions src/nix/installables.cc
Expand Up @@ -109,6 +109,11 @@ struct InstallableStorePath : Installable
bs.push_back(std::move(b));
return bs;
}

std::optional<StorePath> getStorePath() override
{
return storePath.clone();
}
};

struct InstallableValue : Installable
Expand Down
45 changes: 45 additions & 0 deletions src/nix/installables.hh
@@ -0,0 +1,45 @@
#pragma once

#include "util.hh"
#include "path.hh"
#include "eval.hh"

#include <optional>

namespace nix {

struct Buildable
{
std::optional<StorePath> drvPath;
std::map<std::string, StorePath> outputs;
};

typedef std::vector<Buildable> Buildables;

struct Installable
{
virtual ~Installable() { }

virtual std::string what() = 0;

virtual Buildables toBuildables()
{
throw Error("argument '%s' cannot be built", what());
}

Buildable toBuildable();

virtual std::pair<Value *, Pos> toValue(EvalState & state)
{
throw Error("argument '%s' cannot be evaluated", what());
}

/* Return a value only if this installable is a store path or a
symlink to it. */
virtual std::optional<StorePath> getStorePath()
{
return {};
}
};

}

0 comments on commit e1a94ad

Please sign in to comment.