Compare commits
34 Commits
getflake-p
...
precise-gc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a87ca51842 | ||
|
|
ae5deb16d0 | ||
|
|
3ce1ecd63b | ||
|
|
4d74e67aa8 | ||
|
|
594ae92644 | ||
|
|
c31b3d6c5c | ||
|
|
eb319c8078 | ||
|
|
ca2dee6e7d | ||
|
|
d53b8d7211 | ||
|
|
488ce10b4e | ||
|
|
351fbfcb9b | ||
|
|
c6ff34bd86 | ||
|
|
54e1ac6102 | ||
|
|
abbbad5679 | ||
|
|
b19b221f98 | ||
|
|
76325ce5cd | ||
|
|
c3b55a96a7 | ||
|
|
6d118419f2 | ||
|
|
2995f9c48f | ||
|
|
9b822de4ef | ||
|
|
14f7a60755 | ||
|
|
80accdcebe | ||
|
|
69adbf5c77 | ||
|
|
35b76b21ee | ||
|
|
ba36d43d46 | ||
|
|
93b3d25bbb | ||
|
|
f7f73cf5ae | ||
|
|
a38a7b495c | ||
|
|
742a8046de | ||
|
|
2160258cc4 | ||
|
|
e392ff53e9 | ||
|
|
ae5b76a5a4 | ||
|
|
7c716b4c49 | ||
|
|
4237414f4d |
@@ -1,5 +1,4 @@
|
||||
AR = @AR@
|
||||
BDW_GC_LIBS = @BDW_GC_LIBS@
|
||||
BUILD_SHARED_LIBS = @BUILD_SHARED_LIBS@
|
||||
CC = @CC@
|
||||
CFLAGS = @CFLAGS@
|
||||
|
||||
11
configure.ac
11
configure.ac
@@ -255,17 +255,6 @@ if test -n "$enable_s3"; then
|
||||
fi
|
||||
|
||||
|
||||
# Whether to use the Boehm garbage collector.
|
||||
AC_ARG_ENABLE(gc, AC_HELP_STRING([--enable-gc],
|
||||
[enable garbage collection in the Nix expression evaluator (requires Boehm GC) [default=yes]]),
|
||||
gc=$enableval, gc=yes)
|
||||
if test "$gc" = yes; then
|
||||
PKG_CHECK_MODULES([BDW_GC], [bdw-gc])
|
||||
CXXFLAGS="$BDW_GC_CFLAGS $CXXFLAGS"
|
||||
AC_DEFINE(HAVE_BOEHMGC, 1, [Whether to use the Boehm garbage collector.])
|
||||
fi
|
||||
|
||||
|
||||
# documentation generation switch
|
||||
AC_ARG_ENABLE(doc-gen, AC_HELP_STRING([--disable-doc-gen],
|
||||
[disable documentation generation]),
|
||||
|
||||
@@ -50,14 +50,6 @@
|
||||
or higher. If your distribution does not provide it, please install
|
||||
it from <link xlink:href="http://www.sqlite.org/" />.</para></listitem>
|
||||
|
||||
<listitem><para>The <link
|
||||
xlink:href="http://www.hboehm.info/gc/">Boehm
|
||||
garbage collector</link> to reduce the evaluator’s memory
|
||||
consumption (optional). To enable it, install
|
||||
<literal>pkgconfig</literal> and the Boehm garbage collector, and
|
||||
pass the flag <option>--enable-gc</option> to
|
||||
<command>configure</command>.</para></listitem>
|
||||
|
||||
<listitem><para>The <literal>boost</literal> library of version
|
||||
1.66.0 or higher. It can be obtained from the official web site
|
||||
<link xlink:href="https://www.boost.org/" />.</para></listitem>
|
||||
|
||||
@@ -48,7 +48,7 @@ rec {
|
||||
buildDeps =
|
||||
[ curl
|
||||
bzip2 xz brotli zlib editline
|
||||
openssl pkgconfig sqlite boehmgc
|
||||
openssl pkgconfig sqlite
|
||||
libarchive
|
||||
boost
|
||||
nlohmann_json
|
||||
|
||||
@@ -32,7 +32,7 @@ static Strings parseAttrPath(const string & s)
|
||||
}
|
||||
|
||||
|
||||
Value * findAlongAttrPath(EvalState & state, const string & attrPath,
|
||||
Ptr<Value> findAlongAttrPath(EvalState & state, const string & attrPath,
|
||||
Bindings & autoArgs, Value & vIn)
|
||||
{
|
||||
Strings tokens = parseAttrPath(attrPath);
|
||||
@@ -40,7 +40,7 @@ Value * findAlongAttrPath(EvalState & state, const string & attrPath,
|
||||
Error attrError =
|
||||
Error(format("attribute selection path '%1%' does not match expression") % attrPath);
|
||||
|
||||
Value * v = &vIn;
|
||||
Ptr<Value> v(&vIn);
|
||||
|
||||
for (auto & attr : tokens) {
|
||||
|
||||
@@ -50,7 +50,7 @@ Value * findAlongAttrPath(EvalState & state, const string & attrPath,
|
||||
if (string2Int(attr, attrIndex)) apType = apIndex;
|
||||
|
||||
/* Evaluate the expression. */
|
||||
Value * vNew = state.allocValue();
|
||||
auto vNew = state.allocValue();
|
||||
state.autoCallFunction(autoArgs, *v, *vNew);
|
||||
v = vNew;
|
||||
state.forceValue(*v);
|
||||
@@ -71,7 +71,7 @@ Value * findAlongAttrPath(EvalState & state, const string & attrPath,
|
||||
Bindings::iterator a = v->attrs->find(state.symbols.create(attr));
|
||||
if (a == v->attrs->end())
|
||||
throw Error(format("attribute '%1%' in selection path '%2%' not found") % attr % attrPath);
|
||||
v = &*a->value;
|
||||
v = a->value;
|
||||
}
|
||||
|
||||
else if (apType == apIndex) {
|
||||
@@ -97,8 +97,7 @@ Pos findDerivationFilename(EvalState & state, Value & v, std::string what)
|
||||
{
|
||||
Value * v2;
|
||||
try {
|
||||
auto dummyArgs = state.allocBindings(0);
|
||||
v2 = findAlongAttrPath(state, "meta.position", *dummyArgs, v);
|
||||
v2 = findAlongAttrPath(state, "meta.position", *state.emptyBindings, v);
|
||||
} catch (Error &) {
|
||||
throw Error("package '%s' has no source location information", what);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
namespace nix {
|
||||
|
||||
Value * findAlongAttrPath(EvalState & state, const string & attrPath,
|
||||
Ptr<Value> findAlongAttrPath(EvalState & state, const string & attrPath,
|
||||
Bindings & autoArgs, Value & vIn);
|
||||
|
||||
/* Heuristic to find the filename and lineno or a nix value. */
|
||||
|
||||
@@ -7,28 +7,28 @@
|
||||
namespace nix {
|
||||
|
||||
|
||||
/* Allocate a new array of attributes for an attribute set with a specific
|
||||
capacity. The space is implicitly reserved after the Bindings
|
||||
structure. */
|
||||
Bindings * EvalState::allocBindings(size_t capacity)
|
||||
/* Allocate a new array of attributes for an attribute set with a
|
||||
specific capacity. The space is implicitly reserved after the
|
||||
Bindings structure. */
|
||||
Ptr<Bindings> Bindings::allocBindings(size_t capacity)
|
||||
{
|
||||
if (capacity > std::numeric_limits<Bindings::size_t>::max())
|
||||
if (capacity >= 1UL << Object::miscBytes * 8)
|
||||
throw Error("attribute set of size %d is too big", capacity);
|
||||
return new (allocBytes(sizeof(Bindings) + sizeof(Attr) * capacity)) Bindings((Bindings::size_t) capacity);
|
||||
return gc.alloc<Bindings>(Bindings::wordsFor(capacity), capacity);
|
||||
}
|
||||
|
||||
|
||||
void EvalState::mkAttrs(Value & v, size_t capacity)
|
||||
{
|
||||
if (capacity == 0) {
|
||||
v = vEmptySet;
|
||||
return;
|
||||
v.attrs = emptyBindings;
|
||||
v.type = tAttrs;
|
||||
} else {
|
||||
v.attrs = Bindings::allocBindings(capacity);
|
||||
v.type = tAttrs;
|
||||
nrAttrsets++;
|
||||
nrAttrsInAttrsets += capacity;
|
||||
}
|
||||
clearValue(v);
|
||||
v.type = tAttrs;
|
||||
v.attrs = allocBindings(capacity);
|
||||
nrAttrsets++;
|
||||
nrAttrsInAttrsets += capacity;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ void EvalState::mkAttrs(Value & v, size_t capacity)
|
||||
this attribute. */
|
||||
Value * EvalState::allocAttr(Value & vAttrs, const Symbol & name)
|
||||
{
|
||||
Value * v = allocValue();
|
||||
auto v = allocValue();
|
||||
vAttrs.attrs->push_back(Attr(name, v));
|
||||
return v;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "nixexpr.hh"
|
||||
#include "symbol-table.hh"
|
||||
#include "gc.hh"
|
||||
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
@@ -31,19 +32,22 @@ struct Attr
|
||||
by its size and its capacity, the capacity being the number of Attr
|
||||
elements allocated after this structure, while the size corresponds to
|
||||
the number of elements already inserted in this structure. */
|
||||
class Bindings
|
||||
class Bindings : public Object
|
||||
{
|
||||
public:
|
||||
typedef uint32_t size_t;
|
||||
|
||||
private:
|
||||
size_t size_, capacity_;
|
||||
// FIXME: eliminate size_. We can just rely on capacity by sorting
|
||||
// null entries towards the end of the vector.
|
||||
size_t size_;
|
||||
Attr attrs[0];
|
||||
|
||||
Bindings(size_t capacity) : size_(0), capacity_(capacity) { }
|
||||
Bindings(size_t capacity) : Object(tBindings, capacity), size_(0) {}
|
||||
Bindings(const Bindings & bindings) = delete;
|
||||
|
||||
public:
|
||||
|
||||
size_t size() const { return size_; }
|
||||
|
||||
bool empty() const { return !size_; }
|
||||
@@ -52,7 +56,8 @@ public:
|
||||
|
||||
void push_back(const Attr & attr)
|
||||
{
|
||||
assert(size_ < capacity_);
|
||||
assert(size_ < capacity());
|
||||
gc.assertObject(attr.value);
|
||||
attrs[size_++] = attr;
|
||||
}
|
||||
|
||||
@@ -90,7 +95,7 @@ public:
|
||||
|
||||
void sort();
|
||||
|
||||
size_t capacity() { return capacity_; }
|
||||
size_t capacity() const { return getMisc(); }
|
||||
|
||||
/* Returns the attributes in lexicographically sorted order. */
|
||||
std::vector<const Attr *> lexicographicOrder() const
|
||||
@@ -105,7 +110,19 @@ public:
|
||||
return res;
|
||||
}
|
||||
|
||||
friend class EvalState;
|
||||
size_t words() const
|
||||
{
|
||||
return wordsFor(capacity());
|
||||
}
|
||||
|
||||
static size_t wordsFor(size_t capacity)
|
||||
{
|
||||
return 2 + 3 * capacity; // FIXME
|
||||
}
|
||||
|
||||
static Ptr<Bindings> allocBindings(size_t capacity);
|
||||
|
||||
friend class GC;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -28,19 +28,21 @@ MixEvalArgs::MixEvalArgs()
|
||||
.handler([&](std::string s) { searchPath.push_back(s); });
|
||||
}
|
||||
|
||||
Bindings * MixEvalArgs::getAutoArgs(EvalState & state)
|
||||
Ptr<Bindings> MixEvalArgs::getAutoArgs(EvalState & state)
|
||||
{
|
||||
Bindings * res = state.allocBindings(autoArgs.size());
|
||||
for (auto & i : autoArgs) {
|
||||
Value * v = state.allocValue();
|
||||
if (i.second[0] == 'E')
|
||||
state.mkThunk_(*v, state.parseExprFromString(string(i.second, 1), absPath(".")));
|
||||
else
|
||||
mkString(*v, string(i.second, 1));
|
||||
res->push_back(Attr(state.symbols.create(i.first), v));
|
||||
if (!bindings) {
|
||||
bindings = Bindings::allocBindings(autoArgs.size());
|
||||
for (auto & i : autoArgs) {
|
||||
Value * v = state.allocValue();
|
||||
if (i.second[0] == 'E')
|
||||
state.mkThunk_(*v, state.parseExprFromString(string(i.second, 1), absPath(".")));
|
||||
else
|
||||
mkString(*v, string(i.second, 1));
|
||||
bindings->push_back(Attr(state.symbols.create(i.first), v));
|
||||
}
|
||||
bindings->sort();
|
||||
}
|
||||
res->sort();
|
||||
return res;
|
||||
return bindings;
|
||||
}
|
||||
|
||||
Path lookupFileArg(EvalState & state, string s)
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "args.hh"
|
||||
#include "eval.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
class Store;
|
||||
class EvalState;
|
||||
class Bindings;
|
||||
|
||||
template<class T>
|
||||
struct Ptr;
|
||||
|
||||
struct MixEvalArgs : virtual Args
|
||||
{
|
||||
MixEvalArgs();
|
||||
|
||||
Bindings * getAutoArgs(EvalState & state);
|
||||
Ptr<Bindings> getAutoArgs(EvalState & state);
|
||||
|
||||
Strings searchPath;
|
||||
|
||||
private:
|
||||
|
||||
std::map<std::string, std::string> autoArgs;
|
||||
|
||||
Ptr<Bindings> bindings;
|
||||
};
|
||||
|
||||
Path lookupFileArg(EvalState & state, string s);
|
||||
|
||||
@@ -27,21 +27,27 @@ LocalNoInlineNoReturn(void throwTypeError(const char * s, const Value & v, const
|
||||
void EvalState::forceValue(Value & v, const Pos & pos)
|
||||
{
|
||||
if (v.type == tThunk) {
|
||||
Env * env = v.thunk.env;
|
||||
// FIXME: this is necessary because some values (like vList2)
|
||||
// are created non-atomically.
|
||||
Ptr<Env> env(v.thunk.env);
|
||||
Expr * expr = v.thunk.expr;
|
||||
try {
|
||||
v.type = tBlackhole;
|
||||
//checkInterrupt();
|
||||
expr->eval(*this, *env, v);
|
||||
} catch (...) {
|
||||
v.type = tThunk;
|
||||
v.thunk.env = env;
|
||||
v.thunk.expr = expr;
|
||||
v.type = tThunk;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
else if (v.type == tApp)
|
||||
else if (v.type == tApp) {
|
||||
// FIXME: idem.
|
||||
Ptr<Value> left(v.app.left);
|
||||
Ptr<Value> right(v.app.right);
|
||||
callFunction(*v.app.left, *v.app.right, v, noPos);
|
||||
}
|
||||
else if (v.type == tBlackhole)
|
||||
throwEvalError("infinite recursion encountered, at %1%", pos);
|
||||
}
|
||||
@@ -78,18 +84,5 @@ inline void EvalState::forceList(Value & v, const Pos & pos)
|
||||
throwTypeError("value is %1% while a list was expected, at %2%", v, pos);
|
||||
}
|
||||
|
||||
/* Note: Various places expect the allocated memory to be zeroed. */
|
||||
inline void * allocBytes(size_t n)
|
||||
{
|
||||
void * p;
|
||||
#if HAVE_BOEHMGC
|
||||
p = GC_MALLOC(n);
|
||||
#else
|
||||
p = calloc(n, 1);
|
||||
#endif
|
||||
if (!p) throw std::bad_alloc();
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@
|
||||
#include "symbol-table.hh"
|
||||
#include "hash.hh"
|
||||
#include "config.hh"
|
||||
#include "gc.hh"
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
@@ -17,6 +18,7 @@ namespace nix {
|
||||
|
||||
class Store;
|
||||
class EvalState;
|
||||
struct Derivation; // FIXME: remove
|
||||
struct StorePath;
|
||||
enum RepairFlag : bool;
|
||||
|
||||
@@ -34,18 +36,56 @@ struct PrimOp
|
||||
};
|
||||
|
||||
|
||||
struct Env
|
||||
struct Env : Object
|
||||
{
|
||||
Env * up;
|
||||
unsigned short prevWith:14; // nr of levels up to next `with' environment
|
||||
enum { Plain = 0, HasWithExpr, HasWithAttrs } type:2;
|
||||
Value * values[0];
|
||||
|
||||
private:
|
||||
|
||||
constexpr static size_t maxSize = 1 << 16;
|
||||
constexpr static size_t maxPrevWith = 1 << 10;
|
||||
|
||||
Env(Tag type, size_t size, size_t prevWith)
|
||||
: Object(type, size | (prevWith << 16))
|
||||
{
|
||||
if (size >= maxSize)
|
||||
throw Error("environment size %d is too big", size);
|
||||
|
||||
if (prevWith >= maxPrevWith)
|
||||
throw Error("too many nesting levels");
|
||||
|
||||
for (size_t i = 0; i < size; i++)
|
||||
values[i] = nullptr;
|
||||
}
|
||||
|
||||
friend class GC;
|
||||
|
||||
public:
|
||||
|
||||
unsigned short getPrevWith() const
|
||||
{
|
||||
return getMisc() >> 16;
|
||||
}
|
||||
|
||||
unsigned short getSize() const
|
||||
{
|
||||
return getMisc() & 0xffff;
|
||||
}
|
||||
|
||||
size_t words() const
|
||||
{
|
||||
return wordsFor(getSize());
|
||||
}
|
||||
|
||||
static size_t wordsFor(unsigned short size)
|
||||
{
|
||||
return 2 + size;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Value & mkString(Value & v, std::string_view s, const PathSet & context = PathSet());
|
||||
|
||||
void copyContext(const Value & v, PathSet & context);
|
||||
Value & mkString(Value & v, std::string_view s, const PathSet & context);
|
||||
|
||||
|
||||
/* Cache for calls to addToStore(); maps source paths to the store
|
||||
@@ -60,14 +100,10 @@ typedef std::pair<std::string, std::string> SearchPathElem;
|
||||
typedef std::list<SearchPathElem> SearchPath;
|
||||
|
||||
|
||||
/* Initialise the Boehm GC, if applicable. */
|
||||
void initGC();
|
||||
|
||||
|
||||
class EvalState
|
||||
{
|
||||
public:
|
||||
SymbolTable symbols;
|
||||
SymbolTable & symbols{nix::symbols}; // FIXME: remove
|
||||
|
||||
const Symbol sWith, sOutPath, sDrvPath, sType, sMeta, sName, sValue,
|
||||
sSystem, sOverrides, sOutputs, sOutputName, sIgnoreNulls,
|
||||
@@ -84,7 +120,7 @@ public:
|
||||
mode. */
|
||||
std::optional<PathSet> allowedPaths;
|
||||
|
||||
Value vEmptySet;
|
||||
Ptr<Bindings> emptyBindings;
|
||||
|
||||
const ref<Store> store;
|
||||
|
||||
@@ -92,19 +128,11 @@ private:
|
||||
SrcToStore srcToStore;
|
||||
|
||||
/* A cache from path names to parse trees. */
|
||||
#if HAVE_BOEHMGC
|
||||
typedef std::map<Path, Expr *, std::less<Path>, traceable_allocator<std::pair<const Path, Expr *> > > FileParseCache;
|
||||
#else
|
||||
typedef std::map<Path, Expr *> FileParseCache;
|
||||
#endif
|
||||
FileParseCache fileParseCache;
|
||||
|
||||
/* A cache from path names to values. */
|
||||
#if HAVE_BOEHMGC
|
||||
typedef std::map<Path, Value, std::less<Path>, traceable_allocator<std::pair<const Path, Value> > > FileEvalCache;
|
||||
#else
|
||||
typedef std::map<Path, Value> FileEvalCache;
|
||||
#endif
|
||||
typedef std::map<Path, Ptr<Value>> FileEvalCache;
|
||||
FileEvalCache fileEvalCache;
|
||||
|
||||
SearchPath searchPath;
|
||||
@@ -217,7 +245,7 @@ public:
|
||||
|
||||
/* The base environment, containing the builtin functions and
|
||||
values. */
|
||||
Env & baseEnv;
|
||||
Ptr<Env> baseEnv;
|
||||
|
||||
/* The same, but used during parsing to resolve variables. */
|
||||
StaticEnv staticBaseEnv; // !!! should be private
|
||||
@@ -264,13 +292,18 @@ public:
|
||||
void autoCallFunction(Bindings & args, Value & fun, Value & res);
|
||||
|
||||
/* Allocation primitives. */
|
||||
Value * allocValue();
|
||||
Env & allocEnv(size_t size);
|
||||
Ptr<Value> allocValue()
|
||||
{
|
||||
nrValues++;
|
||||
return gc.alloc<Value>(Value::words());
|
||||
}
|
||||
|
||||
Ptr<Env> allocEnv(size_t size, size_t prevWith = 0, Tag type = tEnv);
|
||||
|
||||
// Note: the resulting Value is only reachable as long as vAttrs
|
||||
// is reachable.
|
||||
Value * allocAttr(Value & vAttrs, const Symbol & name);
|
||||
|
||||
Bindings * allocBindings(size_t capacity);
|
||||
|
||||
void mkList(Value & v, size_t length);
|
||||
void mkAttrs(Value & v, size_t capacity);
|
||||
void mkThunk_(Value & v, Expr * expr);
|
||||
@@ -314,6 +347,10 @@ private:
|
||||
friend struct ExprOpConcatLists;
|
||||
friend struct ExprSelect;
|
||||
friend void prim_getAttr(EvalState & state, const Pos & pos, Value * * args, Value & v);
|
||||
|
||||
public:
|
||||
|
||||
std::function<void(const StorePath & drvPath, const Derivation & drv)> derivationHook;
|
||||
};
|
||||
|
||||
|
||||
|
||||
537
src/libexpr/gc.cc
Normal file
537
src/libexpr/gc.cc
Normal file
@@ -0,0 +1,537 @@
|
||||
#include "gc.hh"
|
||||
#include "value.hh"
|
||||
#include "attr-set.hh"
|
||||
#include "eval.hh"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace nix {
|
||||
|
||||
GC gc;
|
||||
|
||||
GC::GC()
|
||||
{
|
||||
nextSize = std::max((size_t) 2, parseSize<size_t>(getEnv("GC_INITIAL_HEAP_SIZE").value_or("131072")) / WORD_SIZE);
|
||||
|
||||
// FIXME: placement new
|
||||
frontPtrSentinel = (Ptr<Object> *) malloc(sizeof(Ptr<Object>));
|
||||
backPtrSentinel = (Ptr<Object> *) malloc(sizeof(Ptr<Object>));
|
||||
|
||||
frontPtrSentinel->prev = nullptr;
|
||||
frontPtrSentinel->next = backPtrSentinel;
|
||||
|
||||
backPtrSentinel->prev = frontPtrSentinel;
|
||||
backPtrSentinel->next = nullptr;
|
||||
|
||||
frontRootSentinel = (Root<Object> *) malloc(sizeof(Root<Object>));
|
||||
backRootSentinel = (Root<Object> *) malloc(sizeof(Root<Object>));
|
||||
|
||||
frontRootSentinel->prev = nullptr;
|
||||
frontRootSentinel->next = backRootSentinel;
|
||||
|
||||
backRootSentinel->prev = frontRootSentinel;
|
||||
backRootSentinel->next = nullptr;
|
||||
|
||||
freeLists[0].minSize = 2;
|
||||
freeLists[1].minSize = 3;
|
||||
freeLists[2].minSize = 4;
|
||||
freeLists[3].minSize = 8;
|
||||
freeLists[4].minSize = 16;
|
||||
freeLists[5].minSize = 32;
|
||||
freeLists[6].minSize = 64;
|
||||
freeLists[7].minSize = 128;
|
||||
|
||||
addArena(nextSize);
|
||||
}
|
||||
|
||||
GC::~GC()
|
||||
{
|
||||
debug("%d bytes in arenas, %d bytes allocated, %d bytes reclaimed, in %d ms",
|
||||
totalSize * WORD_SIZE,
|
||||
allTimeWordsAllocated * WORD_SIZE,
|
||||
allTimeWordsFreed * WORD_SIZE,
|
||||
totalDurationMs);
|
||||
|
||||
size_t n = 0;
|
||||
for (Ptr<Object> * p = frontPtrSentinel->next; p != backPtrSentinel; p = p->next)
|
||||
n++;
|
||||
if (n)
|
||||
warn("%d GC root pointers still exist on exit", n);
|
||||
|
||||
n = 0;
|
||||
for (Root<Object> * p = frontRootSentinel->next; p != backRootSentinel; p = p->next)
|
||||
n++;
|
||||
if (n)
|
||||
warn("%d GC root objects still exist on exit", n);
|
||||
|
||||
assert(!frontPtrSentinel->prev);
|
||||
assert(!backPtrSentinel->next);
|
||||
assert(!frontRootSentinel->prev);
|
||||
assert(!backRootSentinel->next);
|
||||
}
|
||||
|
||||
void GC::addArena(size_t arenaSize)
|
||||
{
|
||||
debug("allocating arena of %d bytes", arenaSize * WORD_SIZE);
|
||||
|
||||
auto arena = Arena(arenaSize);
|
||||
|
||||
// Add this arena to a freelist as a single block.
|
||||
addToFreeList(new (arena.start) Free(arenaSize));
|
||||
|
||||
arenas.emplace_back(std::move(arena));
|
||||
|
||||
totalSize += arenaSize;
|
||||
|
||||
nextSize = arenaSize * 1.5; // FIXME: overflow, clamp
|
||||
}
|
||||
|
||||
void GC::addToFreeList(Free * obj)
|
||||
{
|
||||
auto size = obj->words();
|
||||
for (auto i = freeLists.rbegin(); i != freeLists.rend(); ++i)
|
||||
if (size >= i->minSize) {
|
||||
obj->next = i->front;
|
||||
i->front = obj;
|
||||
return;
|
||||
}
|
||||
abort();
|
||||
}
|
||||
|
||||
void GC::gc()
|
||||
{
|
||||
typedef std::chrono::time_point<std::chrono::steady_clock> steady_time_point;
|
||||
|
||||
auto before = steady_time_point::clock::now();
|
||||
|
||||
size_t marked = 0;
|
||||
|
||||
std::stack<Object *> stack;
|
||||
|
||||
// FIXME: ensure this gets inlined.
|
||||
auto push = [&](Object * p) { if (p) { assertObject(p); stack.push(p); } };
|
||||
|
||||
auto pushPointers = [&](Object * obj) {
|
||||
switch (obj->type) {
|
||||
|
||||
case tFree:
|
||||
printError("reached a freed object at %x", obj);
|
||||
abort();
|
||||
|
||||
case tBindings: {
|
||||
auto obj2 = (Bindings *) obj;
|
||||
for (auto i = obj2->attrs; i < obj2->attrs + obj2->size_; ++i)
|
||||
push(i->value);
|
||||
break;
|
||||
}
|
||||
|
||||
case tValueList: {
|
||||
auto obj2 = (PtrList<Object> *) obj;
|
||||
for (auto i = obj2->elems; i < obj2->elems + obj2->size(); ++i)
|
||||
push(*i);
|
||||
break;
|
||||
}
|
||||
|
||||
case tEnv: {
|
||||
auto obj2 = (Env *) obj;
|
||||
push(obj2->up);
|
||||
for (auto i = obj2->values; i < obj2->values + obj2->getSize(); ++i)
|
||||
push(*i);
|
||||
break;
|
||||
}
|
||||
|
||||
case tWithExprEnv: {
|
||||
auto obj2 = (Env *) obj;
|
||||
push(obj2->up);
|
||||
break;
|
||||
}
|
||||
|
||||
case tWithAttrsEnv: {
|
||||
auto obj2 = (Env *) obj;
|
||||
push(obj2->up);
|
||||
push(obj2->values[0]);
|
||||
break;
|
||||
}
|
||||
|
||||
case tString:
|
||||
case tContext:
|
||||
case tInt:
|
||||
case tBool:
|
||||
case tNull:
|
||||
case tList0:
|
||||
case tFloat:
|
||||
case tShortString:
|
||||
case tStaticString:
|
||||
break;
|
||||
|
||||
case tLongString: {
|
||||
auto obj2 = (Value *) obj;
|
||||
push(obj2->string.s);
|
||||
// See setContext().
|
||||
if (!(((ptrdiff_t) obj2->string.context) & 1))
|
||||
push(obj2->string.context);
|
||||
break;
|
||||
}
|
||||
|
||||
case tPath:
|
||||
push(((Value *) obj)->path);
|
||||
break;
|
||||
|
||||
case tAttrs:
|
||||
push(((Value *) obj)->attrs);
|
||||
break;
|
||||
|
||||
case tList1:
|
||||
push(((Value *) obj)->smallList[0]);
|
||||
break;
|
||||
|
||||
case tList2:
|
||||
push(((Value *) obj)->smallList[0]);
|
||||
push(((Value *) obj)->smallList[1]);
|
||||
break;
|
||||
|
||||
case tListN:
|
||||
push(((Value *) obj)->bigList);
|
||||
break;
|
||||
|
||||
case tThunk:
|
||||
case tBlackhole:
|
||||
push(((Value *) obj)->thunk.env);
|
||||
break;
|
||||
|
||||
case tApp:
|
||||
case tPrimOpApp:
|
||||
push(((Value *) obj)->app.left);
|
||||
push(((Value *) obj)->app.right);
|
||||
break;
|
||||
|
||||
case tLambda:
|
||||
push(((Value *) obj)->lambda.env);
|
||||
break;
|
||||
|
||||
case tPrimOp:
|
||||
// FIXME: GC primops?
|
||||
break;
|
||||
|
||||
default:
|
||||
printError("don't know how to traverse object at %x (tag %d)", obj, obj->type);
|
||||
abort();
|
||||
}
|
||||
};
|
||||
|
||||
auto processStack = [&]() {
|
||||
while (!stack.empty()) {
|
||||
auto obj = stack.top();
|
||||
stack.pop();
|
||||
|
||||
//printError("MARK %x", obj);
|
||||
|
||||
if (!obj->isMarked()) {
|
||||
marked++;
|
||||
obj->mark();
|
||||
pushPointers(obj);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (Root<Object> * p = frontRootSentinel->next; p != backRootSentinel; p = p->next) {
|
||||
pushPointers(&p->value);
|
||||
processStack();
|
||||
}
|
||||
|
||||
for (Ptr<Object> * p = frontPtrSentinel->next; p != backPtrSentinel; p = p->next) {
|
||||
if (!p->value) continue;
|
||||
stack.push(p->value);
|
||||
processStack();
|
||||
}
|
||||
|
||||
auto afterMark = steady_time_point::clock::now();
|
||||
|
||||
// Reset all the freelists.
|
||||
for (auto & freeList : freeLists)
|
||||
freeList.front = nullptr;
|
||||
|
||||
// Go through all the arenas and add free objects to the
|
||||
// appropriate freelists.
|
||||
size_t totalObjectsFreed = 0;
|
||||
size_t totalWordsFreed = 0;
|
||||
size_t totalObjectsKept = 0;
|
||||
size_t totalWordsKept = 0;
|
||||
|
||||
for (auto & arena : arenas) {
|
||||
auto [objectsFreed, wordsFreed, objectsKept, wordsKept] = freeUnmarked(arena);
|
||||
totalObjectsFreed += objectsFreed;
|
||||
totalWordsFreed += wordsFreed;
|
||||
totalObjectsKept += objectsKept;
|
||||
totalWordsKept += wordsKept;
|
||||
}
|
||||
|
||||
auto after = steady_time_point::clock::now();
|
||||
|
||||
auto markDurationMs = std::chrono::duration_cast<std::chrono::milliseconds>(afterMark - before).count();
|
||||
auto sweepDurationMs = std::chrono::duration_cast<std::chrono::milliseconds>(after - afterMark).count();
|
||||
|
||||
debug("freed %d dead objects (%d bytes), keeping %d/%d objects (%d bytes), marked in %d ms, swept in %d ms",
|
||||
totalObjectsFreed, totalWordsFreed * WORD_SIZE,
|
||||
marked, totalObjectsKept, totalWordsKept * WORD_SIZE,
|
||||
markDurationMs, sweepDurationMs);
|
||||
|
||||
allTimeWordsFreed += totalWordsFreed;
|
||||
totalDurationMs += markDurationMs + sweepDurationMs;
|
||||
}
|
||||
|
||||
size_t GC::getObjectSize(Object * obj)
|
||||
{
|
||||
auto tag = obj->type;
|
||||
if (tag >= tInt && tag <= tFloat) {
|
||||
return ((Value *) obj)->words();
|
||||
} else {
|
||||
switch (tag) {
|
||||
case tFree:
|
||||
return ((Free *) obj)->words();
|
||||
break;
|
||||
case tString:
|
||||
return ((String *) obj)->words();
|
||||
break;
|
||||
case tBindings:
|
||||
return ((Bindings *) obj)->words();
|
||||
break;
|
||||
case tValueList:
|
||||
return ((PtrList<Value> *) obj)->words();
|
||||
break;
|
||||
case tEnv:
|
||||
case tWithExprEnv:
|
||||
case tWithAttrsEnv:
|
||||
return ((Env *) obj)->words();
|
||||
break;
|
||||
case tContext:
|
||||
return ((Context *) obj)->getSize() + 1;
|
||||
break;
|
||||
default:
|
||||
printError("GC encountered invalid object with tag %d", tag);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::tuple<size_t, size_t, size_t, size_t> GC::freeUnmarked(Arena & arena)
|
||||
{
|
||||
size_t objectsFreed = 0;
|
||||
size_t wordsFreed = 0;
|
||||
size_t objectsKept = 0;
|
||||
size_t wordsKept = 0;
|
||||
|
||||
auto end = arena.start + arena.size;
|
||||
auto pos = arena.start;
|
||||
|
||||
Free * curFree = nullptr;
|
||||
|
||||
auto linkCurFree = [&]() {
|
||||
if (curFree && curFree->words() > 1)
|
||||
addToFreeList(curFree);
|
||||
curFree = nullptr;
|
||||
};
|
||||
|
||||
while (pos < end) {
|
||||
auto obj = (Object *) pos;
|
||||
|
||||
auto objSize = getObjectSize(obj);
|
||||
|
||||
// Merge current object into the previous free object.
|
||||
auto mergeFree = [&]() {
|
||||
//printError("MERGE %x %x %d", curFree, obj, curFree->size() + objSize);
|
||||
assert(curFree->words() >= 1);
|
||||
curFree->setSize(curFree->words() + objSize);
|
||||
};
|
||||
|
||||
if (obj->type == tFree) {
|
||||
//debug("KEEP FREE %x %d", obj, obj->getMisc());
|
||||
if (curFree) {
|
||||
// Merge this object into the previous free
|
||||
// object.
|
||||
mergeFree();
|
||||
} else {
|
||||
curFree = (Free *) obj;
|
||||
}
|
||||
} else {
|
||||
|
||||
if (obj->isMarked()) {
|
||||
// Unmark to prepare for the next GC run.
|
||||
//debug("KEEP OBJECT %x %d %d", obj, obj->type, objSize);
|
||||
linkCurFree();
|
||||
obj->unmark();
|
||||
objectsKept += 1;
|
||||
wordsKept += objSize;
|
||||
} else {
|
||||
//debug("FREE OBJECT %x %d %d", obj, obj->type, objSize);
|
||||
#if GC_DEBUG
|
||||
for (size_t i = 0; i < objSize; ++i)
|
||||
((Word *) obj)[i] = 0xdeadc0dedeadbeefULL;
|
||||
#endif
|
||||
objectsFreed += 1;
|
||||
wordsFreed += objSize;
|
||||
if (curFree) {
|
||||
mergeFree();
|
||||
} else {
|
||||
// Convert to a free object.
|
||||
curFree = (Free *) obj;
|
||||
curFree->type = tFree;
|
||||
curFree->setSize(objSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pos += objSize;
|
||||
}
|
||||
|
||||
linkCurFree();
|
||||
|
||||
assert(pos == end);
|
||||
|
||||
return {objectsFreed, wordsFreed, objectsKept, wordsKept};
|
||||
}
|
||||
|
||||
bool GC::isObject(void * p)
|
||||
{
|
||||
for (auto & arena : arenas)
|
||||
if (p >= arena.start && p < arena.start + arena.size)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::tuple<size_t, size_t> GC::getObjectClosureSize(Object * p)
|
||||
{
|
||||
std::unordered_set<Object *> seen;
|
||||
|
||||
// FIXME: cut&paste.
|
||||
|
||||
std::stack<Object *> stack;
|
||||
|
||||
// FIXME: ensure this gets inlined.
|
||||
auto push = [&](Object * p) { if (p) { assertObject(p); stack.push(p); } };
|
||||
|
||||
auto pushPointers = [&](Object * obj) {
|
||||
switch (obj->type) {
|
||||
|
||||
case tFree:
|
||||
printError("reached a freed object at %x", obj);
|
||||
abort();
|
||||
|
||||
case tBindings: {
|
||||
auto obj2 = (Bindings *) obj;
|
||||
for (auto i = obj2->attrs; i < obj2->attrs + obj2->size_; ++i)
|
||||
push(i->value);
|
||||
break;
|
||||
}
|
||||
|
||||
case tValueList: {
|
||||
auto obj2 = (PtrList<Object> *) obj;
|
||||
for (auto i = obj2->elems; i < obj2->elems + obj2->size(); ++i)
|
||||
push(*i);
|
||||
break;
|
||||
}
|
||||
|
||||
case tEnv: {
|
||||
auto obj2 = (Env *) obj;
|
||||
push(obj2->up);
|
||||
for (auto i = obj2->values; i < obj2->values + obj2->getSize(); ++i)
|
||||
push(*i);
|
||||
break;
|
||||
}
|
||||
|
||||
case tWithExprEnv: {
|
||||
auto obj2 = (Env *) obj;
|
||||
push(obj2->up);
|
||||
break;
|
||||
}
|
||||
|
||||
case tWithAttrsEnv: {
|
||||
auto obj2 = (Env *) obj;
|
||||
push(obj2->up);
|
||||
push(obj2->values[0]);
|
||||
break;
|
||||
}
|
||||
|
||||
case tString:
|
||||
case tContext:
|
||||
case tInt:
|
||||
case tBool:
|
||||
case tNull:
|
||||
case tList0:
|
||||
case tFloat:
|
||||
case tShortString:
|
||||
case tStaticString:
|
||||
break;
|
||||
|
||||
case tLongString: {
|
||||
auto obj2 = (Value *) obj;
|
||||
push(obj2->string.s);
|
||||
// See setContext().
|
||||
if (!(((ptrdiff_t) obj2->string.context) & 1))
|
||||
push(obj2->string.context);
|
||||
break;
|
||||
}
|
||||
|
||||
case tPath:
|
||||
push(((Value *) obj)->path);
|
||||
break;
|
||||
|
||||
case tAttrs:
|
||||
push(((Value *) obj)->attrs);
|
||||
break;
|
||||
|
||||
case tList1:
|
||||
push(((Value *) obj)->smallList[0]);
|
||||
break;
|
||||
|
||||
case tList2:
|
||||
push(((Value *) obj)->smallList[0]);
|
||||
push(((Value *) obj)->smallList[1]);
|
||||
break;
|
||||
|
||||
case tListN:
|
||||
push(((Value *) obj)->bigList);
|
||||
break;
|
||||
|
||||
case tThunk:
|
||||
case tBlackhole:
|
||||
push(((Value *) obj)->thunk.env);
|
||||
break;
|
||||
|
||||
case tApp:
|
||||
case tPrimOpApp:
|
||||
push(((Value *) obj)->app.left);
|
||||
push(((Value *) obj)->app.right);
|
||||
break;
|
||||
|
||||
case tLambda:
|
||||
push(((Value *) obj)->lambda.env);
|
||||
break;
|
||||
|
||||
case tPrimOp:
|
||||
// FIXME: GC primops?
|
||||
break;
|
||||
|
||||
default:
|
||||
printError("don't know how to traverse object at %x (tag %d)", obj, obj->type);
|
||||
abort();
|
||||
}
|
||||
};
|
||||
|
||||
stack.push(p);
|
||||
|
||||
size_t totalSize = 0;
|
||||
|
||||
while (!stack.empty()) {
|
||||
auto obj = stack.top();
|
||||
stack.pop();
|
||||
if (seen.insert(obj).second) {
|
||||
pushPointers(obj);
|
||||
totalSize += getObjectSize(obj);
|
||||
}
|
||||
}
|
||||
|
||||
return {seen.size(), totalSize};
|
||||
}
|
||||
|
||||
}
|
||||
482
src/libexpr/gc.hh
Normal file
482
src/libexpr/gc.hh
Normal file
@@ -0,0 +1,482 @@
|
||||
#pragma once
|
||||
|
||||
#include "logging.hh"
|
||||
|
||||
#include <stack>
|
||||
#include <limits>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
//#define GC_DEBUG 1
|
||||
|
||||
namespace nix {
|
||||
|
||||
typedef unsigned long Word;
|
||||
|
||||
enum Tag {
|
||||
tFree = 3,
|
||||
|
||||
// Misc types
|
||||
tString,
|
||||
tBindings,
|
||||
tValueList,
|
||||
tEnv,
|
||||
tWithExprEnv,
|
||||
tWithAttrsEnv,
|
||||
tContext,
|
||||
|
||||
// Value tags
|
||||
tInt,
|
||||
tBool,
|
||||
tShortString,
|
||||
tStaticString,
|
||||
tLongString,
|
||||
tPath,
|
||||
tNull,
|
||||
tAttrs,
|
||||
tList0,
|
||||
tList1,
|
||||
tList2,
|
||||
tListN,
|
||||
tThunk,
|
||||
tApp,
|
||||
tLambda,
|
||||
tBlackhole,
|
||||
tPrimOp,
|
||||
tPrimOpApp,
|
||||
tExternal,
|
||||
tFloat
|
||||
};
|
||||
|
||||
constexpr size_t WORD_SIZE = 8;
|
||||
|
||||
struct Object
|
||||
{
|
||||
friend class GC;
|
||||
|
||||
public:
|
||||
constexpr static size_t miscBytes = 7;
|
||||
|
||||
public: // FIXME
|
||||
Tag type:7;
|
||||
|
||||
private:
|
||||
bool marked:1;
|
||||
|
||||
unsigned long misc:56;
|
||||
|
||||
void unmark()
|
||||
{
|
||||
marked = false;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
Object(Tag type, unsigned long misc) : type(type), marked(false), misc(misc) { }
|
||||
|
||||
bool isMarked()
|
||||
{
|
||||
return marked;
|
||||
}
|
||||
|
||||
void mark()
|
||||
{
|
||||
marked = true;
|
||||
}
|
||||
|
||||
void setMisc(unsigned int misc)
|
||||
{
|
||||
this->misc = misc;
|
||||
}
|
||||
|
||||
unsigned int getMisc() const
|
||||
{
|
||||
return misc;
|
||||
}
|
||||
|
||||
char * getMiscData() const
|
||||
{
|
||||
return ((char *) this) + 1;
|
||||
}
|
||||
};
|
||||
|
||||
template<class T>
|
||||
struct PtrList : Object
|
||||
{
|
||||
T * elems[0];
|
||||
|
||||
PtrList(Tag type, size_t size) : Object(type, size)
|
||||
{
|
||||
for (size_t i = 0; i < size; i++)
|
||||
elems[i] = nullptr;
|
||||
}
|
||||
|
||||
size_t size() const { return getMisc(); }
|
||||
size_t words() const { return wordsFor(size()); }
|
||||
static size_t wordsFor(size_t size) { return 1 + size; }
|
||||
};
|
||||
|
||||
struct Free : Object
|
||||
{
|
||||
Free * next;
|
||||
|
||||
Free(size_t size) : Object(tFree, size), next(nullptr) { }
|
||||
|
||||
// return size in words
|
||||
size_t words() const { return getMisc(); }
|
||||
|
||||
void setSize(size_t size) { assert(size >= 1); setMisc(size); }
|
||||
};
|
||||
|
||||
template<class T>
|
||||
struct Ptr;
|
||||
|
||||
template<class T>
|
||||
struct Root;
|
||||
|
||||
struct GC
|
||||
{
|
||||
|
||||
private:
|
||||
|
||||
Ptr<Object> * frontPtrSentinel;
|
||||
Ptr<Object> * backPtrSentinel;
|
||||
|
||||
Root<Object> * frontRootSentinel;
|
||||
Root<Object> * backRootSentinel;
|
||||
|
||||
template<class T>
|
||||
friend class Ptr;
|
||||
|
||||
template<class T>
|
||||
friend class Root;
|
||||
|
||||
struct Arena
|
||||
{
|
||||
size_t size; // in words
|
||||
Word * start;
|
||||
|
||||
Arena(size_t size)
|
||||
: size(size)
|
||||
, start(new Word[size])
|
||||
{
|
||||
assert(size >= 2);
|
||||
}
|
||||
|
||||
Arena(const Arena & arena) = delete;
|
||||
|
||||
Arena(Arena && arena)
|
||||
{
|
||||
size = arena.size;
|
||||
start = arena.start;
|
||||
arena.start = nullptr;
|
||||
}
|
||||
|
||||
~Arena()
|
||||
{
|
||||
delete[] start;
|
||||
}
|
||||
};
|
||||
|
||||
size_t totalSize = 0;
|
||||
size_t nextSize;
|
||||
|
||||
std::vector<Arena> arenas;
|
||||
|
||||
struct FreeList
|
||||
{
|
||||
size_t minSize;
|
||||
Free * front = nullptr;
|
||||
};
|
||||
|
||||
std::array<FreeList, 8> freeLists;
|
||||
|
||||
public:
|
||||
|
||||
size_t getHeapSize() { return totalSize * WORD_SIZE; }
|
||||
|
||||
size_t allTimeWordsAllocated = 0;
|
||||
size_t allTimeWordsFreed = 0;
|
||||
uint64_t totalDurationMs = 0;
|
||||
|
||||
private:
|
||||
|
||||
Object * allocObject(size_t size)
|
||||
{
|
||||
assert(size >= 2);
|
||||
|
||||
for (int attempt = 0; attempt < 3; attempt++) {
|
||||
|
||||
for (size_t i = 0; i < freeLists.size(); ++i) {
|
||||
auto & freeList = freeLists[i];
|
||||
|
||||
if ((size <= freeList.minSize || i == freeLists.size() - 1) && freeList.front) {
|
||||
//printError("TRY %d %d %d", size, i, freeList.minSize);
|
||||
|
||||
Free * * prev = &freeList.front;
|
||||
|
||||
while (Free * freeObj = *prev) {
|
||||
//printError("LOOK %x %d %x", freeObj, freeObj->words(), freeObj->next);
|
||||
assert(freeObj->words() >= freeList.minSize);
|
||||
if (freeObj->words() == size) {
|
||||
// Convert the free object.
|
||||
*prev = freeObj->next;
|
||||
return (Object *) freeObj;
|
||||
} else if (freeObj->words() >= size + 2) {
|
||||
// Split the free object.
|
||||
auto newSize = freeObj->words() - size;
|
||||
freeObj->setSize(newSize);
|
||||
if (newSize < freeList.minSize) {
|
||||
/* The free object is now smaller than
|
||||
the minimum size for this freelist,
|
||||
so move it to another one. */
|
||||
//printError("MOVE %x %d -> %d", freeObj, newSize + size, newSize);
|
||||
*prev = freeObj->next;
|
||||
addToFreeList(freeObj);
|
||||
}
|
||||
return (Object *) (((Word *) freeObj) + newSize);
|
||||
} else if (freeObj->words() == size + 1) {
|
||||
// Return the free object and add a padding word.
|
||||
*prev = freeObj->next;
|
||||
freeObj->setSize(1);
|
||||
return (Object *) (((Word *) freeObj) + 1);
|
||||
} else {
|
||||
assert(freeObj->words() < size);
|
||||
prev = &freeObj->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (attempt == 0) {
|
||||
debug("allocation of %d bytes failed, GCing...", size * WORD_SIZE);
|
||||
gc();
|
||||
} else if (attempt == 1) {
|
||||
addArena(std::max(nextSize, size));
|
||||
}
|
||||
}
|
||||
|
||||
throw Error("allocation of %d bytes failed", size);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
GC();
|
||||
~GC();
|
||||
|
||||
template<typename T, typename... Args>
|
||||
Ptr<T> alloc(size_t size, const Args & ... args)
|
||||
{
|
||||
auto raw = allocObject(size);
|
||||
allTimeWordsAllocated += size;
|
||||
return new (raw) T(args...);
|
||||
}
|
||||
|
||||
void gc();
|
||||
|
||||
bool isObject(void * p);
|
||||
|
||||
void assertObject(void * p)
|
||||
{
|
||||
#if GC_DEBUG
|
||||
if (!isObject(p)) {
|
||||
printError("object %p is not an object", p);
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Return the size in words of object 'obj'. */
|
||||
static size_t getObjectSize(Object * obj);
|
||||
|
||||
/* Return the number and size in words of the objects reachable
|
||||
from object 'obj'. */
|
||||
std::tuple<size_t, size_t> getObjectClosureSize(Object * obj);
|
||||
|
||||
private:
|
||||
|
||||
void addArena(size_t arenaSize);
|
||||
|
||||
void addToFreeList(Free * obj);
|
||||
|
||||
std::tuple<size_t, size_t, size_t, size_t> freeUnmarked(Arena & arena);
|
||||
};
|
||||
|
||||
extern GC gc;
|
||||
|
||||
template<class T>
|
||||
struct Ptr
|
||||
{
|
||||
private:
|
||||
|
||||
friend class GC;
|
||||
|
||||
Ptr * prev = nullptr, * next = nullptr;
|
||||
T * value = nullptr;
|
||||
|
||||
void link()
|
||||
{
|
||||
prev = (Ptr *) gc.frontPtrSentinel;
|
||||
next = prev->next;
|
||||
next->prev = this;
|
||||
prev->next = this;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
Ptr() {
|
||||
link();
|
||||
}
|
||||
|
||||
Ptr(T * value) : value(value)
|
||||
{
|
||||
link();
|
||||
}
|
||||
|
||||
Ptr(const Ptr & p)
|
||||
{
|
||||
auto & p2 = const_cast<Ptr &>(p);
|
||||
value = p2.value;
|
||||
next = &p2;
|
||||
prev = p2.prev;
|
||||
prev->next = this;
|
||||
p2.prev = this;
|
||||
}
|
||||
|
||||
Ptr(Ptr && p)
|
||||
{
|
||||
link();
|
||||
value = p.value;
|
||||
p.value = nullptr;
|
||||
}
|
||||
|
||||
Ptr & operator =(const Ptr & p)
|
||||
{
|
||||
value = p.value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Ptr & operator =(Ptr && p)
|
||||
{
|
||||
value = p.value;
|
||||
p.value = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Ptr & operator =(T * v)
|
||||
{
|
||||
value = v;
|
||||
return *this;
|
||||
}
|
||||
|
||||
~Ptr()
|
||||
{
|
||||
assert(next);
|
||||
assert(prev);
|
||||
assert(next->prev == this);
|
||||
next->prev = prev;
|
||||
assert(prev->next == this);
|
||||
prev->next = next;
|
||||
}
|
||||
|
||||
T * operator ->()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
T * operator ->() const // FIXME
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
operator T * ()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
operator T & ()
|
||||
{
|
||||
assert(value);
|
||||
return *value;
|
||||
}
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return value != nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
template<class T>
|
||||
struct Root
|
||||
{
|
||||
Root * prev = nullptr, * next = nullptr;
|
||||
T value;
|
||||
|
||||
template<typename... Args>
|
||||
Root(const Args & ... args)
|
||||
: value{args... }
|
||||
{
|
||||
prev = (Root *) gc.frontRootSentinel;
|
||||
next = prev->next;
|
||||
next->prev = this;
|
||||
prev->next = this;
|
||||
}
|
||||
|
||||
Root(const Root & p) = delete;
|
||||
Root(Root && p) = delete;
|
||||
|
||||
Root & operator =(const T & v) { value = v; return *this; }
|
||||
|
||||
~Root()
|
||||
{
|
||||
assert(next);
|
||||
assert(prev);
|
||||
assert(next->prev == this);
|
||||
next->prev = prev;
|
||||
assert(prev->next == this);
|
||||
prev->next = next;
|
||||
}
|
||||
|
||||
T * operator ->()
|
||||
{
|
||||
return &value;
|
||||
}
|
||||
|
||||
operator T * ()
|
||||
{
|
||||
return &value;
|
||||
}
|
||||
|
||||
operator T & ()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
struct String : Object
|
||||
{
|
||||
char s[0];
|
||||
|
||||
String(size_t len, const char * src)
|
||||
: Object(tString, len)
|
||||
{
|
||||
std::memcpy(s, src, len + 1);
|
||||
}
|
||||
|
||||
size_t words() const { return wordsFor(getMisc()); }
|
||||
|
||||
/* Return the number of words needed to store a string of 'len'
|
||||
characters (where 'len' excludes the terminator). */
|
||||
static size_t wordsFor(size_t len)
|
||||
{
|
||||
return len / WORD_SIZE + 2;
|
||||
}
|
||||
|
||||
static String * alloc(const char * src)
|
||||
{
|
||||
auto len = strlen(src);
|
||||
return gc.alloc<String>(wordsFor(len), len, src);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -115,15 +115,15 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool onlyOutputsToInstall)
|
||||
return outputs;
|
||||
|
||||
/* Check for `meta.outputsToInstall` and return `outputs` reduced to that. */
|
||||
const Value * outTI = queryMeta("outputsToInstall");
|
||||
const auto outTI = queryMeta("outputsToInstall");
|
||||
if (!outTI) return outputs;
|
||||
const auto errMsg = Error("this derivation has bad 'meta.outputsToInstall'");
|
||||
/* ^ this shows during `nix-env -i` right under the bad derivation */
|
||||
if (!outTI->isList()) throw errMsg;
|
||||
Outputs result;
|
||||
for (auto i = outTI->listElems(); i != outTI->listElems() + outTI->listSize(); ++i) {
|
||||
if ((*i)->type != tString) throw errMsg;
|
||||
auto out = outputs.find((*i)->string.s);
|
||||
if (!(*i)->isString()) throw errMsg;
|
||||
auto out = outputs.find((*i)->getString());
|
||||
if (out == outputs.end()) throw errMsg;
|
||||
result.insert(*out);
|
||||
}
|
||||
@@ -178,8 +178,7 @@ bool DrvInfo::checkMeta(Value & v)
|
||||
if (!checkMeta(*i.value)) return false;
|
||||
return true;
|
||||
}
|
||||
else return v.type == tInt || v.type == tBool || v.type == tString ||
|
||||
v.type == tFloat;
|
||||
else return v.type == tInt || v.type == tBool || v.isString() || v.type == tFloat;
|
||||
}
|
||||
|
||||
|
||||
@@ -194,36 +193,36 @@ Value * DrvInfo::queryMeta(const string & name)
|
||||
|
||||
string DrvInfo::queryMetaString(const string & name)
|
||||
{
|
||||
Value * v = queryMeta(name);
|
||||
if (!v || v->type != tString) return "";
|
||||
return v->string.s;
|
||||
auto v = queryMeta(name);
|
||||
if (!v || !v->isString()) return "";
|
||||
return v->getString();
|
||||
}
|
||||
|
||||
|
||||
NixInt DrvInfo::queryMetaInt(const string & name, NixInt def)
|
||||
{
|
||||
Value * v = queryMeta(name);
|
||||
auto v = queryMeta(name);
|
||||
if (!v) return def;
|
||||
if (v->type == tInt) return v->integer;
|
||||
if (v->type == tString) {
|
||||
if (v->isString()) {
|
||||
/* Backwards compatibility with before we had support for
|
||||
integer meta fields. */
|
||||
NixInt n;
|
||||
if (string2Int(v->string.s, n)) return n;
|
||||
if (string2Int(v->getString(), n)) return n;
|
||||
}
|
||||
return def;
|
||||
}
|
||||
|
||||
NixFloat DrvInfo::queryMetaFloat(const string & name, NixFloat def)
|
||||
{
|
||||
Value * v = queryMeta(name);
|
||||
auto v = queryMeta(name);
|
||||
if (!v) return def;
|
||||
if (v->type == tFloat) return v->fpoint;
|
||||
if (v->type == tString) {
|
||||
if (v->isString()) {
|
||||
/* Backwards compatibility with before we had support for
|
||||
float meta fields. */
|
||||
NixFloat n;
|
||||
if (string2Float(v->string.s, n)) return n;
|
||||
if (string2Float(v->getString(), n)) return n;
|
||||
}
|
||||
return def;
|
||||
}
|
||||
@@ -231,14 +230,15 @@ NixFloat DrvInfo::queryMetaFloat(const string & name, NixFloat def)
|
||||
|
||||
bool DrvInfo::queryMetaBool(const string & name, bool def)
|
||||
{
|
||||
Value * v = queryMeta(name);
|
||||
auto v = queryMeta(name);
|
||||
if (!v) return def;
|
||||
if (v->type == tBool) return v->boolean;
|
||||
if (v->type == tString) {
|
||||
if (v->isString()) {
|
||||
/* Backwards compatibility with before we had support for
|
||||
Boolean meta fields. */
|
||||
if (strcmp(v->string.s, "true") == 0) return true;
|
||||
if (strcmp(v->string.s, "false") == 0) return false;
|
||||
auto s = v->getString();
|
||||
if (strcmp(s, "true") == 0) return true;
|
||||
if (strcmp(s, "false") == 0) return false;
|
||||
}
|
||||
return def;
|
||||
}
|
||||
@@ -247,8 +247,8 @@ bool DrvInfo::queryMetaBool(const string & name, bool def)
|
||||
void DrvInfo::setMeta(const string & name, Value * v)
|
||||
{
|
||||
getMeta();
|
||||
Bindings * old = meta;
|
||||
meta = state->allocBindings(1 + (old ? old->size() : 0));
|
||||
Ptr<Bindings> old = meta;
|
||||
meta = Bindings::allocBindings(1 + (old ? old->size() : 0));
|
||||
Symbol sym = state->symbols.create(name);
|
||||
if (old)
|
||||
for (auto i : *old)
|
||||
@@ -260,6 +260,7 @@ void DrvInfo::setMeta(const string & name, Value * v)
|
||||
|
||||
|
||||
/* Cache for already considered attrsets. */
|
||||
// FIXME: Use Ptr?
|
||||
typedef set<Bindings *> Done;
|
||||
|
||||
|
||||
@@ -319,24 +320,24 @@ static void getDerivations(EvalState & state, Value & vIn,
|
||||
DrvInfos & drvs, Done & done,
|
||||
bool ignoreAssertionFailures)
|
||||
{
|
||||
Value v;
|
||||
Root<Value> v;
|
||||
state.autoCallFunction(autoArgs, vIn, v);
|
||||
|
||||
/* Process the expression. */
|
||||
if (!getDerivation(state, v, pathPrefix, drvs, done, ignoreAssertionFailures)) ;
|
||||
|
||||
else if (v.type == tAttrs) {
|
||||
else if (v->type == tAttrs) {
|
||||
|
||||
/* !!! undocumented hackery to support combining channels in
|
||||
nix-env.cc. */
|
||||
bool combineChannels = v.attrs->find(state.symbols.create("_combineChannels")) != v.attrs->end();
|
||||
bool combineChannels = v->attrs->find(state.symbols.create("_combineChannels")) != v->attrs->end();
|
||||
|
||||
/* Consider the attributes in sorted order to get more
|
||||
deterministic behaviour in nix-env operations (e.g. when
|
||||
there are names clashes between derivations, the derivation
|
||||
bound to the attribute with the "lower" name should take
|
||||
precedence). */
|
||||
for (auto & i : v.attrs->lexicographicOrder()) {
|
||||
for (auto & i : v->attrs->lexicographicOrder()) {
|
||||
debug("evaluating attribute '%1%'", i->name);
|
||||
if (!std::regex_match(std::string(i->name), attrRegex))
|
||||
continue;
|
||||
@@ -356,11 +357,11 @@ static void getDerivations(EvalState & state, Value & vIn,
|
||||
}
|
||||
}
|
||||
|
||||
else if (v.isList()) {
|
||||
for (unsigned int n = 0; n < v.listSize(); ++n) {
|
||||
else if (v->isList()) {
|
||||
for (unsigned int n = 0; n < v->listSize(); ++n) {
|
||||
string pathPrefix2 = addToPath(pathPrefix, (format("%1%") % n).str());
|
||||
if (getDerivation(state, *v.listElems()[n], pathPrefix2, drvs, done, ignoreAssertionFailures))
|
||||
getDerivations(state, *v.listElems()[n], pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
|
||||
if (getDerivation(state, *v->listElems()[n], pathPrefix2, drvs, done, ignoreAssertionFailures))
|
||||
getDerivations(state, *v->listElems()[n], pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,8 @@ private:
|
||||
|
||||
bool failed = false; // set if we get an AssertionError
|
||||
|
||||
Bindings * attrs = nullptr, * meta = nullptr;
|
||||
Ptr<Bindings> attrs;
|
||||
Ptr<Bindings> meta;
|
||||
|
||||
Bindings * getMeta();
|
||||
|
||||
@@ -69,11 +70,7 @@ public:
|
||||
};
|
||||
|
||||
|
||||
#if HAVE_BOEHMGC
|
||||
typedef list<DrvInfo, traceable_allocator<DrvInfo> > DrvInfos;
|
||||
#else
|
||||
typedef list<DrvInfo> DrvInfos;
|
||||
#endif
|
||||
typedef std::list<DrvInfo> DrvInfos;
|
||||
|
||||
|
||||
/* If value `v' denotes a derivation, return a DrvInfo object
|
||||
|
||||
@@ -11,21 +11,22 @@ namespace nix {
|
||||
// for more information, refer to
|
||||
// https://github.com/nlohmann/json/blob/master/include/nlohmann/detail/input/json_sax.hpp
|
||||
class JSONSax : nlohmann::json_sax<json> {
|
||||
|
||||
class JSONState {
|
||||
protected:
|
||||
unique_ptr<JSONState> parent;
|
||||
Value * v;
|
||||
Ptr<Value> v;
|
||||
public:
|
||||
virtual unique_ptr<JSONState> resolve(EvalState &)
|
||||
{
|
||||
throw std::logic_error("tried to close toplevel json parser state");
|
||||
};
|
||||
explicit JSONState(unique_ptr<JSONState>&& p) : parent(std::move(p)), v(nullptr) {};
|
||||
explicit JSONState(Value* v) : v(v) {};
|
||||
JSONState(JSONState& p) = delete;
|
||||
Value& value(EvalState & state)
|
||||
explicit JSONState(unique_ptr<JSONState> && p) : parent(std::move(p)), v(nullptr) {};
|
||||
explicit JSONState(Value * v) : v(v) {};
|
||||
JSONState(JSONState & p) = delete;
|
||||
Value & value(EvalState & state)
|
||||
{
|
||||
if (v == nullptr)
|
||||
if (!v)
|
||||
v = state.allocValue();
|
||||
return *v;
|
||||
};
|
||||
@@ -38,7 +39,7 @@ class JSONSax : nlohmann::json_sax<json> {
|
||||
ValueMap attrs = ValueMap();
|
||||
virtual unique_ptr<JSONState> resolve(EvalState & state) override
|
||||
{
|
||||
Value& v = parent->value(state);
|
||||
Value & v = parent->value(state);
|
||||
state.mkAttrs(v, attrs.size());
|
||||
for (auto & i : attrs)
|
||||
v.attrs->push_back(Attr(i.first, i.second));
|
||||
@@ -46,7 +47,7 @@ class JSONSax : nlohmann::json_sax<json> {
|
||||
}
|
||||
virtual void add() override { v = nullptr; };
|
||||
public:
|
||||
void key(string_t& name, EvalState & state)
|
||||
void key(string_t & name, EvalState & state)
|
||||
{
|
||||
attrs[state.symbols.create(name)] = &value(state);
|
||||
}
|
||||
@@ -56,7 +57,7 @@ class JSONSax : nlohmann::json_sax<json> {
|
||||
ValueVector values = ValueVector();
|
||||
virtual unique_ptr<JSONState> resolve(EvalState & state) override
|
||||
{
|
||||
Value& v = parent->value(state);
|
||||
Value & v = parent->value(state);
|
||||
state.mkList(v, values.size());
|
||||
for (size_t n = 0; n < values.size(); ++n) {
|
||||
v.listElems()[n] = values[n];
|
||||
@@ -68,7 +69,7 @@ class JSONSax : nlohmann::json_sax<json> {
|
||||
v = nullptr;
|
||||
};
|
||||
public:
|
||||
JSONListState(unique_ptr<JSONState>&& p, std::size_t reserve) : JSONState(std::move(p))
|
||||
JSONListState(unique_ptr<JSONState> && p, std::size_t reserve) : JSONState(std::move(p))
|
||||
{
|
||||
values.reserve(reserve);
|
||||
}
|
||||
@@ -114,7 +115,7 @@ public:
|
||||
|
||||
bool string(string_t& val)
|
||||
{
|
||||
return handle_value<void(Value&, const char*)>(mkString, val.c_str());
|
||||
return handle_value<Value & (Value &, std::string_view)>(mkString, (std::string_view) val);
|
||||
}
|
||||
|
||||
bool start_object(std::size_t len)
|
||||
|
||||
@@ -13,11 +13,6 @@ ifneq ($(OS), FreeBSD)
|
||||
libexpr_LDFLAGS += -ldl
|
||||
endif
|
||||
|
||||
# The dependency on libgc must be propagated (i.e. meaning that
|
||||
# programs/libraries that use libexpr must explicitly pass -lgc),
|
||||
# because inline functions in libexpr's header files call libgc.
|
||||
libexpr_LDFLAGS_PROPAGATED = $(BDW_GC_LIBS)
|
||||
|
||||
libexpr_ORDER_AFTER := $(d)/parser-tab.cc $(d)/parser-tab.hh $(d)/lexer-tab.cc $(d)/lexer-tab.hh
|
||||
|
||||
$(d)/parser-tab.cc $(d)/parser-tab.hh: $(d)/parser.y
|
||||
|
||||
@@ -80,7 +80,7 @@ void ExprString::show(std::ostream & str) const
|
||||
|
||||
void ExprPath::show(std::ostream & str) const
|
||||
{
|
||||
str << s;
|
||||
str << v->path->s;
|
||||
}
|
||||
|
||||
void ExprVar::show(std::ostream & str) const
|
||||
|
||||
@@ -78,7 +78,7 @@ struct Expr
|
||||
virtual void show(std::ostream & str) const;
|
||||
virtual void bindVars(const StaticEnv & env);
|
||||
virtual void eval(EvalState & state, Env & env, Value & v);
|
||||
virtual Value * maybeThunk(EvalState & state, Env & env);
|
||||
virtual Ptr<Value> maybeThunk(EvalState & state, Env & env);
|
||||
virtual void setName(Symbol & name);
|
||||
};
|
||||
|
||||
@@ -92,28 +92,37 @@ std::ostream & operator << (std::ostream & str, const Expr & e);
|
||||
struct ExprInt : Expr
|
||||
{
|
||||
NixInt n;
|
||||
Value v;
|
||||
ExprInt(NixInt n) : n(n) { mkInt(v, n); };
|
||||
Ptr<Value> v;
|
||||
ExprInt(NixInt n) : n(n) {
|
||||
v = gc.alloc<Value>(Value::words());
|
||||
mkInt(v, n);
|
||||
};
|
||||
COMMON_METHODS
|
||||
Value * maybeThunk(EvalState & state, Env & env);
|
||||
Ptr<Value> maybeThunk(EvalState & state, Env & env) override;
|
||||
};
|
||||
|
||||
struct ExprFloat : Expr
|
||||
{
|
||||
NixFloat nf;
|
||||
Value v;
|
||||
ExprFloat(NixFloat nf) : nf(nf) { mkFloat(v, nf); };
|
||||
Ptr<Value> v;
|
||||
ExprFloat(NixFloat nf) : nf(nf) {
|
||||
v = gc.alloc<Value>(Value::words());
|
||||
mkFloat(v, nf);
|
||||
};
|
||||
COMMON_METHODS
|
||||
Value * maybeThunk(EvalState & state, Env & env);
|
||||
Ptr<Value> maybeThunk(EvalState & state, Env & env) override;
|
||||
};
|
||||
|
||||
struct ExprString : Expr
|
||||
{
|
||||
Symbol s;
|
||||
Value v;
|
||||
ExprString(const Symbol & s) : s(s) { mkString(v, s); };
|
||||
Ptr<Value> v;
|
||||
ExprString(const Symbol & s) : s(s) {
|
||||
v = gc.alloc<Value>(Value::words());
|
||||
mkString(v, s);
|
||||
};
|
||||
COMMON_METHODS
|
||||
Value * maybeThunk(EvalState & state, Env & env);
|
||||
Ptr<Value> maybeThunk(EvalState & state, Env & env) override;
|
||||
};
|
||||
|
||||
/* Temporary class used during parsing of indented strings. */
|
||||
@@ -125,11 +134,13 @@ struct ExprIndStr : Expr
|
||||
|
||||
struct ExprPath : Expr
|
||||
{
|
||||
string s;
|
||||
Value v;
|
||||
ExprPath(const string & s) : s(s) { mkPathNoCopy(v, this->s.c_str()); };
|
||||
Ptr<Value> v;
|
||||
ExprPath(const string & s) {
|
||||
v = gc.alloc<Value>(Value::words());
|
||||
mkPath(v, s);
|
||||
};
|
||||
COMMON_METHODS
|
||||
Value * maybeThunk(EvalState & state, Env & env);
|
||||
Ptr<Value> maybeThunk(EvalState & state, Env & env) override;
|
||||
};
|
||||
|
||||
struct ExprVar : Expr
|
||||
@@ -153,7 +164,7 @@ struct ExprVar : Expr
|
||||
ExprVar(const Symbol & name) : name(name) { };
|
||||
ExprVar(const Pos & pos, const Symbol & name) : pos(pos), name(name) { };
|
||||
COMMON_METHODS
|
||||
Value * maybeThunk(EvalState & state, Env & env);
|
||||
Ptr<Value> maybeThunk(EvalState & state, Env & env) override;
|
||||
};
|
||||
|
||||
struct ExprSelect : Expr
|
||||
|
||||
@@ -103,14 +103,13 @@ static void prim_scopedImport(EvalState & state, const Pos & pos, Value * * args
|
||||
// FIXME
|
||||
if (state.store->isStorePath(path) && state.store->isValidPath(state.store->parseStorePath(path)) && isDerivation(path)) {
|
||||
Derivation drv = readDerivation(*state.store, realPath);
|
||||
Value & w = *state.allocValue();
|
||||
auto w = state.allocValue();
|
||||
state.mkAttrs(w, 3 + drv.outputs.size());
|
||||
Value * v2 = state.allocAttr(w, state.sDrvPath);
|
||||
auto v2 = state.allocAttr(w, state.sDrvPath);
|
||||
mkString(*v2, path, {"=" + path});
|
||||
v2 = state.allocAttr(w, state.sName);
|
||||
mkString(*v2, drv.env["name"]);
|
||||
Value * outputsVal =
|
||||
state.allocAttr(w, state.symbols.create("outputs"));
|
||||
auto outputsVal = state.allocAttr(w, state.symbols.create("outputs"));
|
||||
state.mkList(*outputsVal, drv.outputs.size());
|
||||
unsigned int outputs_index = 0;
|
||||
|
||||
@@ -120,8 +119,8 @@ static void prim_scopedImport(EvalState & state, const Pos & pos, Value * * args
|
||||
outputsVal->listElems()[outputs_index] = state.allocValue();
|
||||
mkString(*(outputsVal->listElems()[outputs_index++]), o.first);
|
||||
}
|
||||
w.attrs->sort();
|
||||
Value fun;
|
||||
w->attrs->sort();
|
||||
auto fun = state.allocValue();
|
||||
state.evalFile(settings.nixDataDir + "/nix/corepkgs/imported-drv-to-derivation.nix", fun);
|
||||
state.forceFunction(fun, pos);
|
||||
mkApp(v, fun, w);
|
||||
@@ -131,8 +130,8 @@ static void prim_scopedImport(EvalState & state, const Pos & pos, Value * * args
|
||||
if (args[0]->attrs->empty())
|
||||
state.evalFile(realPath, v);
|
||||
else {
|
||||
Env * env = &state.allocEnv(args[0]->attrs->size());
|
||||
env->up = &state.baseEnv;
|
||||
auto env = state.allocEnv(args[0]->attrs->size());
|
||||
env->up = state.baseEnv;
|
||||
|
||||
StaticEnv staticEnv(false, &state.staticBaseEnv);
|
||||
|
||||
@@ -240,19 +239,24 @@ static void prim_typeOf(EvalState & state, const Pos & pos, Value * * args, Valu
|
||||
switch (args[0]->type) {
|
||||
case tInt: t = "int"; break;
|
||||
case tBool: t = "bool"; break;
|
||||
case tString: t = "string"; break;
|
||||
case tShortString:
|
||||
case tStaticString:
|
||||
case tLongString:
|
||||
t = "string"; break;
|
||||
case tPath: t = "path"; break;
|
||||
case tNull: t = "null"; break;
|
||||
case tAttrs: t = "set"; break;
|
||||
case tList1: case tList2: case tListN: t = "list"; break;
|
||||
case tList0: case tList1: case tList2: case tListN: t = "list"; break;
|
||||
case tLambda:
|
||||
case tPrimOp:
|
||||
case tPrimOpApp:
|
||||
t = "lambda";
|
||||
break;
|
||||
#if 0
|
||||
case tExternal:
|
||||
t = args[0]->external->typeOf();
|
||||
break;
|
||||
#endif
|
||||
case tFloat: t = "float"; break;
|
||||
default: abort();
|
||||
}
|
||||
@@ -305,7 +309,7 @@ static void prim_isFloat(EvalState & state, const Pos & pos, Value * * args, Val
|
||||
static void prim_isString(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||
{
|
||||
state.forceValue(*args[0]);
|
||||
mkBool(v, args[0]->type == tString);
|
||||
mkBool(v, args[0]->isString());
|
||||
}
|
||||
|
||||
|
||||
@@ -331,6 +335,8 @@ struct CompareValues
|
||||
return v1->fpoint < v2->integer;
|
||||
if (v1->type == tInt && v2->type == tFloat)
|
||||
return v1->integer < v2->fpoint;
|
||||
if (v1->isString() && v2->isString())
|
||||
return strcmp(v1->getString(), v2->getString()) < 0;
|
||||
if (v1->type != v2->type)
|
||||
throw EvalError(format("cannot compare %1% with %2%") % showType(*v1) % showType(*v2));
|
||||
switch (v1->type) {
|
||||
@@ -338,10 +344,8 @@ struct CompareValues
|
||||
return v1->integer < v2->integer;
|
||||
case tFloat:
|
||||
return v1->fpoint < v2->fpoint;
|
||||
case tString:
|
||||
return strcmp(v1->string.s, v2->string.s) < 0;
|
||||
case tPath:
|
||||
return strcmp(v1->path, v2->path) < 0;
|
||||
return strcmp(v1->path->s, v2->path->s) < 0;
|
||||
default:
|
||||
throw EvalError(format("cannot compare %1% with %2%") % showType(*v1) % showType(*v2));
|
||||
}
|
||||
@@ -349,11 +353,7 @@ struct CompareValues
|
||||
};
|
||||
|
||||
|
||||
#if HAVE_BOEHMGC
|
||||
typedef list<Value *, gc_allocator<Value *> > ValueList;
|
||||
#else
|
||||
typedef list<Value *> ValueList;
|
||||
#endif
|
||||
typedef list<Ptr<Value>> ValueList;
|
||||
|
||||
|
||||
static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||
@@ -383,10 +383,10 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar
|
||||
no new elements are found. */
|
||||
ValueList res;
|
||||
// `doneKeys' doesn't need to be a GC root, because its values are
|
||||
// reachable from res.
|
||||
// reachable from res. FIXME: dubious.
|
||||
set<Value *, CompareValues> doneKeys;
|
||||
while (!workSet.empty()) {
|
||||
Value * e = *(workSet.begin());
|
||||
auto e = *(workSet.begin());
|
||||
workSet.pop_front();
|
||||
|
||||
state.forceAttrs(*e, pos);
|
||||
@@ -401,14 +401,14 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar
|
||||
res.push_back(e);
|
||||
|
||||
/* Call the `operator' function with `e' as argument. */
|
||||
Value call;
|
||||
Root<Value> call;
|
||||
mkApp(call, *op->value, *e);
|
||||
state.forceList(call, pos);
|
||||
|
||||
/* Add the values returned by the operator to the work set. */
|
||||
for (unsigned int n = 0; n < call.listSize(); ++n) {
|
||||
state.forceValue(*call.listElems()[n]);
|
||||
workSet.push_back(call.listElems()[n]);
|
||||
for (unsigned int n = 0; n < call->listSize(); ++n) {
|
||||
state.forceValue(*call->listElems()[n]);
|
||||
workSet.push_back(call->listElems()[n]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -498,10 +498,10 @@ static void prim_deepSeq(EvalState & state, const Pos & pos, Value * * args, Val
|
||||
static void prim_trace(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||
{
|
||||
state.forceValue(*args[0]);
|
||||
if (args[0]->type == tString)
|
||||
printError(format("trace: %1%") % args[0]->string.s);
|
||||
if (args[0]->isString())
|
||||
printError("trace: %s", args[0]->getString());
|
||||
else
|
||||
printError(format("trace: %1%") % *args[0]);
|
||||
printError("trace: %s", *args[0]);
|
||||
state.forceValue(*args[1]);
|
||||
v = *args[1];
|
||||
}
|
||||
@@ -749,6 +749,8 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
|
||||
auto drvPath = writeDerivation(state.store, drv, drvName, state.repair);
|
||||
auto drvPathS = state.store->printStorePath(drvPath);
|
||||
|
||||
if (state.derivationHook) state.derivationHook(drvPath, drv);
|
||||
|
||||
printMsg(lvlChatty, "instantiated '%1%' -> '%2%'", drvName, drvPathS);
|
||||
|
||||
/* Optimisation, but required in read-only mode! because in that
|
||||
@@ -950,15 +952,20 @@ static void prim_readDir(EvalState & state, const Pos & pos, Value * * args, Val
|
||||
DirEntries entries = readDirectory(state.checkSourcePath(path));
|
||||
state.mkAttrs(v, entries.size());
|
||||
|
||||
auto sRegular = state.symbols.create("regular");
|
||||
auto sDirectory = state.symbols.create("directory");
|
||||
auto sSymlink = state.symbols.create("symlink");
|
||||
auto sUnknown = state.symbols.create("unknown");
|
||||
|
||||
for (auto & ent : entries) {
|
||||
Value * ent_val = state.allocAttr(v, state.symbols.create(ent.name));
|
||||
auto ent_val = state.allocAttr(v, state.symbols.create(ent.name));
|
||||
if (ent.type == DT_UNKNOWN)
|
||||
ent.type = getFileType(path + "/" + ent.name);
|
||||
mkStringNoCopy(*ent_val,
|
||||
ent.type == DT_REG ? "regular" :
|
||||
ent.type == DT_DIR ? "directory" :
|
||||
ent.type == DT_LNK ? "symlink" :
|
||||
"unknown");
|
||||
mkString(*ent_val,
|
||||
ent.type == DT_REG ? sRegular :
|
||||
ent.type == DT_DIR ? sDirectory :
|
||||
ent.type == DT_LNK ? sSymlink :
|
||||
sUnknown);
|
||||
}
|
||||
|
||||
v.attrs->sort();
|
||||
@@ -1041,20 +1048,20 @@ static void addPath(EvalState & state, const Pos & pos, const string & name, con
|
||||
|
||||
/* Call the filter function. The first argument is the path,
|
||||
the second is a string indicating the type of the file. */
|
||||
Value arg1;
|
||||
auto arg1 = state.allocValue();
|
||||
mkString(arg1, path);
|
||||
|
||||
Value fun2;
|
||||
Root<Value> fun2;
|
||||
state.callFunction(*filterFun, arg1, fun2, noPos);
|
||||
|
||||
Value arg2;
|
||||
auto arg2 = state.allocValue();
|
||||
mkString(arg2,
|
||||
S_ISREG(st.st_mode) ? "regular" :
|
||||
S_ISDIR(st.st_mode) ? "directory" :
|
||||
S_ISLNK(st.st_mode) ? "symlink" :
|
||||
"unknown" /* not supported, will fail! */);
|
||||
|
||||
Value res;
|
||||
Root<Value> res;
|
||||
state.callFunction(fun2, arg2, res, noPos);
|
||||
|
||||
return state.forceBool(res, pos);
|
||||
@@ -1146,7 +1153,7 @@ static void prim_attrNames(EvalState & state, const Pos & pos, Value * * args, V
|
||||
mkString(*(v.listElems()[n++] = state.allocValue()), i.name);
|
||||
|
||||
std::sort(v.listElems(), v.listElems() + n,
|
||||
[](Value * v1, Value * v2) { return strcmp(v1->string.s, v2->string.s) < 0; });
|
||||
[](Value * v1, Value * v2) { return strcmp(v1->getString(), v2->getString()) < 0; });
|
||||
}
|
||||
|
||||
|
||||
@@ -1223,10 +1230,9 @@ static void prim_removeAttrs(EvalState & state, const Pos & pos, Value * * args,
|
||||
|
||||
/* Get the attribute names to be removed. */
|
||||
std::set<Symbol> names;
|
||||
for (unsigned int i = 0; i < args[1]->listSize(); ++i) {
|
||||
state.forceStringNoCtx(*args[1]->listElems()[i], pos);
|
||||
names.insert(state.symbols.create(args[1]->listElems()[i]->string.s));
|
||||
}
|
||||
for (unsigned int i = 0; i < args[1]->listSize(); ++i)
|
||||
names.insert(state.symbols.create(
|
||||
state.forceStringNoCtx(*args[1]->listElems()[i], pos)));
|
||||
|
||||
/* Copy all attributes not in that set. Note that we don't need
|
||||
to sort v.attrs because it's a subset of an already sorted
|
||||
@@ -1362,8 +1368,8 @@ static void prim_mapAttrs(EvalState & state, const Pos & pos, Value * * args, Va
|
||||
state.mkAttrs(v, args[1]->attrs->size());
|
||||
|
||||
for (auto & i : *args[1]->attrs) {
|
||||
Value * vName = state.allocValue();
|
||||
Value * vFun2 = state.allocValue();
|
||||
auto vName = state.allocValue();
|
||||
auto vFun2 = state.allocValue();
|
||||
mkString(*vName, i.name);
|
||||
mkApp(*vFun2, *args[0], *vName);
|
||||
mkApp(*state.allocAttr(v, i.name), *vFun2, *i.value);
|
||||
@@ -1450,7 +1456,7 @@ static void prim_filter(EvalState & state, const Pos & pos, Value * * args, Valu
|
||||
|
||||
bool same = true;
|
||||
for (unsigned int n = 0; n < args[1]->listSize(); ++n) {
|
||||
Value res;
|
||||
Root<Value> res;
|
||||
state.callFunction(*args[0], *args[1]->listElems()[n], res, noPos);
|
||||
if (state.forceBool(res, pos))
|
||||
vs[k++] = args[1]->listElems()[n];
|
||||
@@ -1505,12 +1511,12 @@ static void prim_foldlStrict(EvalState & state, const Pos & pos, Value * * args,
|
||||
state.forceList(*args[2], pos);
|
||||
|
||||
if (args[2]->listSize()) {
|
||||
Value * vCur = args[1];
|
||||
Ptr<Value> vCur = args[1];
|
||||
|
||||
for (unsigned int n = 0; n < args[2]->listSize(); ++n) {
|
||||
Value vTmp;
|
||||
Root<Value> vTmp; // FIXME: correct?
|
||||
state.callFunction(*args[0], *vCur, vTmp, pos);
|
||||
vCur = n == args[2]->listSize() - 1 ? &v : state.allocValue();
|
||||
vCur = n == args[2]->listSize() - 1 ? Ptr(&v) : state.allocValue();
|
||||
state.callFunction(vTmp, *args[2]->listElems()[n], *vCur, pos);
|
||||
}
|
||||
state.forceValue(v);
|
||||
@@ -1526,7 +1532,7 @@ static void anyOrAll(bool any, EvalState & state, const Pos & pos, Value * * arg
|
||||
state.forceFunction(*args[0], pos);
|
||||
state.forceList(*args[1], pos);
|
||||
|
||||
Value vTmp;
|
||||
Root<Value> vTmp;
|
||||
for (unsigned int n = 0; n < args[1]->listSize(); ++n) {
|
||||
state.callFunction(*args[0], *args[1]->listElems()[n], vTmp, pos);
|
||||
bool res = state.forceBool(vTmp, pos);
|
||||
@@ -1562,7 +1568,7 @@ static void prim_genList(EvalState & state, const Pos & pos, Value * * args, Val
|
||||
state.mkList(v, len);
|
||||
|
||||
for (unsigned int n = 0; n < (unsigned int) len; ++n) {
|
||||
Value * arg = state.allocValue();
|
||||
auto arg = state.allocValue();
|
||||
mkInt(*arg, n);
|
||||
mkApp(*(v.listElems()[n] = state.allocValue()), *args[0], *arg);
|
||||
}
|
||||
@@ -1591,7 +1597,7 @@ static void prim_sort(EvalState & state, const Pos & pos, Value * * args, Value
|
||||
if (args[0]->type == tPrimOp && args[0]->primOp->fun == prim_lessThan)
|
||||
return CompareValues()(a, b);
|
||||
|
||||
Value vTmp1, vTmp2;
|
||||
Root<Value> vTmp1, vTmp2; // FIXME
|
||||
state.callFunction(*args[0], *a, vTmp1, pos);
|
||||
state.callFunction(vTmp1, *b, vTmp2, pos);
|
||||
return state.forceBool(vTmp2, pos);
|
||||
@@ -1611,12 +1617,13 @@ static void prim_partition(EvalState & state, const Pos & pos, Value * * args, V
|
||||
|
||||
auto len = args[1]->listSize();
|
||||
|
||||
ValueVector right, wrong;
|
||||
// Note: these Values are reachable via args[0].
|
||||
std::vector<Value *> right, wrong;
|
||||
|
||||
for (unsigned int n = 0; n < len; ++n) {
|
||||
auto vElem = args[1]->listElems()[n];
|
||||
state.forceValue(*vElem);
|
||||
Value res;
|
||||
Root<Value> res;
|
||||
state.callFunction(*args[0], *vElem, res, pos);
|
||||
if (state.forceBool(res, pos))
|
||||
right.push_back(vElem);
|
||||
@@ -1626,13 +1633,13 @@ static void prim_partition(EvalState & state, const Pos & pos, Value * * args, V
|
||||
|
||||
state.mkAttrs(v, 2);
|
||||
|
||||
Value * vRight = state.allocAttr(v, state.sRight);
|
||||
auto vRight = state.allocAttr(v, state.sRight);
|
||||
auto rsize = right.size();
|
||||
state.mkList(*vRight, rsize);
|
||||
if (rsize)
|
||||
memcpy(vRight->listElems(), right.data(), sizeof(Value *) * rsize);
|
||||
|
||||
Value * vWrong = state.allocAttr(v, state.sWrong);
|
||||
auto vWrong = state.allocAttr(v, state.sWrong);
|
||||
auto wsize = wrong.size();
|
||||
state.mkList(*vWrong, wsize);
|
||||
if (wsize)
|
||||
@@ -1650,22 +1657,23 @@ static void prim_concatMap(EvalState & state, const Pos & pos, Value * * args, V
|
||||
state.forceList(*args[1], pos);
|
||||
auto nrLists = args[1]->listSize();
|
||||
|
||||
Value lists[nrLists];
|
||||
// FIXME: Root<>[] is inefficient
|
||||
Root<Value> lists[nrLists];
|
||||
size_t len = 0;
|
||||
|
||||
for (unsigned int n = 0; n < nrLists; ++n) {
|
||||
Value * vElem = args[1]->listElems()[n];
|
||||
state.callFunction(*args[0], *vElem, lists[n], pos);
|
||||
state.forceList(lists[n], pos);
|
||||
len += lists[n].listSize();
|
||||
len += lists[n]->listSize();
|
||||
}
|
||||
|
||||
state.mkList(v, len);
|
||||
auto out = v.listElems();
|
||||
for (unsigned int n = 0, pos = 0; n < nrLists; ++n) {
|
||||
auto l = lists[n].listSize();
|
||||
auto l = lists[n]->listSize();
|
||||
if (l)
|
||||
memcpy(out + pos, lists[n].listElems(), l * sizeof(Value *));
|
||||
memcpy(out + pos, lists[n]->listElems(), l * sizeof(Value *));
|
||||
pos += l;
|
||||
}
|
||||
}
|
||||
@@ -2115,10 +2123,10 @@ RegisterPrimOp::RegisterPrimOp(std::string name, size_t arity, PrimOpFun fun)
|
||||
|
||||
void EvalState::createBaseEnv()
|
||||
{
|
||||
baseEnv.up = 0;
|
||||
baseEnv->up = 0;
|
||||
|
||||
/* Add global constants such as `true' to the base environment. */
|
||||
Value v;
|
||||
auto v = allocValue();
|
||||
|
||||
/* `builtins' must be first! */
|
||||
mkAttrs(v, 128);
|
||||
@@ -2136,7 +2144,7 @@ void EvalState::createBaseEnv()
|
||||
auto vThrow = addPrimOp("throw", 1, prim_throw);
|
||||
|
||||
auto addPurityError = [&](const std::string & name) {
|
||||
Value * v2 = allocValue();
|
||||
auto v2 = allocValue();
|
||||
mkString(*v2, fmt("'%s' is not allowed in pure evaluation mode", name));
|
||||
mkApp(v, *vThrow, *v2);
|
||||
addConstant(name, v);
|
||||
@@ -2167,7 +2175,7 @@ void EvalState::createBaseEnv()
|
||||
|
||||
// Miscellaneous
|
||||
auto vScopedImport = addPrimOp("scopedImport", 2, prim_scopedImport);
|
||||
Value * v2 = allocValue();
|
||||
auto v2 = allocValue();
|
||||
mkAttrs(*v2, 0);
|
||||
mkApp(v, *vScopedImport, *v2);
|
||||
forceValue(v);
|
||||
@@ -2295,7 +2303,7 @@ void EvalState::createBaseEnv()
|
||||
mkList(v, searchPath.size());
|
||||
int n = 0;
|
||||
for (auto & i : searchPath) {
|
||||
v2 = v.listElems()[n++] = allocValue();
|
||||
v2 = v->listElems()[n++] = allocValue();
|
||||
mkAttrs(*v2, 2);
|
||||
mkString(*allocAttr(*v2, symbols.create("path")), i.second);
|
||||
mkString(*allocAttr(*v2, symbols.create("prefix")), i.first);
|
||||
@@ -2309,7 +2317,7 @@ void EvalState::createBaseEnv()
|
||||
|
||||
/* Now that we've added all primops, sort the `builtins' set,
|
||||
because attribute lookups expect it to be sorted. */
|
||||
baseEnv.values[0]->attrs->sort();
|
||||
baseEnv->values[0]->attrs->sort();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -160,7 +160,7 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg
|
||||
if (iter != i.value->attrs->end()) {
|
||||
if (state.forceBool(*iter->value, *iter->pos)) {
|
||||
if (!isDerivation(i.name)) {
|
||||
throw EvalError("Tried to add all-outputs context of %s, which is not a derivation, to a string, at %s", i.name, i.pos);
|
||||
throw EvalError("tried to add all-outputs context of %s, which is not a derivation, to a string, at %s", i.name, *i.pos);
|
||||
}
|
||||
context.insert("=" + string(i.name));
|
||||
}
|
||||
@@ -170,7 +170,7 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg
|
||||
if (iter != i.value->attrs->end()) {
|
||||
state.forceList(*iter->value, *iter->pos);
|
||||
if (iter->value->listSize() && !isDerivation(i.name)) {
|
||||
throw EvalError("Tried to add derivation output context of %s, which is not a derivation, to a string, at %s", i.name, i.pos);
|
||||
throw EvalError("tried to add derivation output context of %s, which is not a derivation, to a string, at %s", i.name, *i.pos);
|
||||
}
|
||||
for (unsigned int n = 0; n < iter->value->listSize(); ++n) {
|
||||
auto name = state.forceStringNoCtx(*iter->value->listElems()[n], *iter->pos);
|
||||
|
||||
@@ -19,6 +19,7 @@ private:
|
||||
const string * s; // pointer into SymbolTable
|
||||
Symbol(const string * s) : s(s) { };
|
||||
friend class SymbolTable;
|
||||
friend class Value;
|
||||
|
||||
public:
|
||||
Symbol() : s(0) { };
|
||||
@@ -89,4 +90,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
extern SymbolTable symbols;
|
||||
|
||||
}
|
||||
|
||||
@@ -26,13 +26,15 @@ void printValueAsJSON(EvalState & state, bool strict,
|
||||
out.write(v.boolean);
|
||||
break;
|
||||
|
||||
case tString:
|
||||
copyContext(v, context);
|
||||
out.write(v.string.s);
|
||||
case tShortString:
|
||||
case tStaticString:
|
||||
case tLongString:
|
||||
v.getContext(context);
|
||||
out.write(v.getString());
|
||||
break;
|
||||
|
||||
case tPath:
|
||||
out.write(state.copyPathToStore(context, v.path));
|
||||
out.write(state.copyPathToStore(context, v.path->s));
|
||||
break;
|
||||
|
||||
case tNull:
|
||||
@@ -61,7 +63,7 @@ void printValueAsJSON(EvalState & state, bool strict,
|
||||
break;
|
||||
}
|
||||
|
||||
case tList1: case tList2: case tListN: {
|
||||
case tList0: case tList1: case tList2: case tListN: {
|
||||
auto list(out.list());
|
||||
for (unsigned int n = 0; n < v.listSize(); ++n) {
|
||||
auto placeholder(list.placeholder());
|
||||
@@ -70,9 +72,11 @@ void printValueAsJSON(EvalState & state, bool strict,
|
||||
break;
|
||||
}
|
||||
|
||||
#if 0
|
||||
case tExternal:
|
||||
v.external->printValueAsJSON(state, strict, out, context);
|
||||
break;
|
||||
#endif
|
||||
|
||||
case tFloat:
|
||||
out.write(v.fpoint);
|
||||
@@ -90,11 +94,13 @@ void printValueAsJSON(EvalState & state, bool strict,
|
||||
printValueAsJSON(state, strict, v, out, context);
|
||||
}
|
||||
|
||||
#if 0
|
||||
void ExternalValueBase::printValueAsJSON(EvalState & state, bool strict,
|
||||
JSONPlaceholder & out, PathSet & context) const
|
||||
{
|
||||
throw TypeError(format("cannot convert %1% to JSON") % showType());
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -68,14 +68,16 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
|
||||
doc.writeEmptyElement("bool", singletonAttrs("value", v.boolean ? "true" : "false"));
|
||||
break;
|
||||
|
||||
case tString:
|
||||
case tShortString:
|
||||
case tStaticString:
|
||||
case tLongString:
|
||||
/* !!! show the context? */
|
||||
copyContext(v, context);
|
||||
doc.writeEmptyElement("string", singletonAttrs("value", v.string.s));
|
||||
v.getContext(context);
|
||||
doc.writeEmptyElement("string", singletonAttrs("value", v.getString()));
|
||||
break;
|
||||
|
||||
case tPath:
|
||||
doc.writeEmptyElement("path", singletonAttrs("value", v.path));
|
||||
doc.writeEmptyElement("path", singletonAttrs("value", v.path->s));
|
||||
break;
|
||||
|
||||
case tNull:
|
||||
@@ -92,15 +94,15 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
|
||||
a = v.attrs->find(state.sDrvPath);
|
||||
if (a != v.attrs->end()) {
|
||||
if (strict) state.forceValue(*a->value);
|
||||
if (a->value->type == tString)
|
||||
xmlAttrs["drvPath"] = drvPath = a->value->string.s;
|
||||
if (a->value->isString())
|
||||
xmlAttrs["drvPath"] = drvPath = a->value->getString();
|
||||
}
|
||||
|
||||
a = v.attrs->find(state.sOutPath);
|
||||
if (a != v.attrs->end()) {
|
||||
if (strict) state.forceValue(*a->value);
|
||||
if (a->value->type == tString)
|
||||
xmlAttrs["outPath"] = a->value->string.s;
|
||||
if (a->value->isString())
|
||||
xmlAttrs["outPath"] = a->value->getString();
|
||||
}
|
||||
|
||||
XMLOpenElement _(doc, "derivation", xmlAttrs);
|
||||
@@ -118,7 +120,7 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
|
||||
|
||||
break;
|
||||
|
||||
case tList1: case tList2: case tListN: {
|
||||
case tList0: case tList1: case tList2: case tListN: {
|
||||
XMLOpenElement _(doc, "list");
|
||||
for (unsigned int n = 0; n < v.listSize(); ++n)
|
||||
printValueAsXML(state, strict, location, *v.listElems()[n], doc, context, drvsSeen);
|
||||
@@ -143,9 +145,11 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
|
||||
break;
|
||||
}
|
||||
|
||||
#if 0
|
||||
case tExternal:
|
||||
v.external->printValueAsXML(state, strict, location, doc, context, drvsSeen);
|
||||
break;
|
||||
#endif
|
||||
|
||||
case tFloat:
|
||||
doc.writeEmptyElement("float", singletonAttrs("value", (format("%1%") % v.fpoint).str()));
|
||||
@@ -157,11 +161,13 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
|
||||
}
|
||||
|
||||
|
||||
#if 0
|
||||
void ExternalValueBase::printValueAsXML(EvalState & state, bool strict,
|
||||
bool location, XMLWriter & doc, PathSet & context, PathSet & drvsSeen) const
|
||||
{
|
||||
doc.writeEmptyElement("unevaluated");
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
void printValueAsXML(EvalState & state, bool strict, bool location,
|
||||
|
||||
@@ -1,35 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "symbol-table.hh"
|
||||
#include "gc.hh"
|
||||
|
||||
#if HAVE_BOEHMGC
|
||||
#include <gc/gc_allocator.h>
|
||||
#endif
|
||||
#include <cstring>
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
typedef enum {
|
||||
tInt = 1,
|
||||
tBool,
|
||||
tString,
|
||||
tPath,
|
||||
tNull,
|
||||
tAttrs,
|
||||
tList1,
|
||||
tList2,
|
||||
tListN,
|
||||
tThunk,
|
||||
tApp,
|
||||
tLambda,
|
||||
tBlackhole,
|
||||
tPrimOp,
|
||||
tPrimOpApp,
|
||||
tExternal,
|
||||
tFloat
|
||||
} ValueType;
|
||||
|
||||
|
||||
class Bindings;
|
||||
struct Env;
|
||||
struct Expr;
|
||||
@@ -45,13 +22,15 @@ class JSONPlaceholder;
|
||||
typedef int64_t NixInt;
|
||||
typedef double NixFloat;
|
||||
|
||||
#if 0
|
||||
/* External values must descend from ExternalValueBase, so that
|
||||
* type-agnostic nix functions (e.g. showType) can be implemented
|
||||
*/
|
||||
class ExternalValueBase
|
||||
{
|
||||
friend std::ostream & operator << (std::ostream & str, const ExternalValueBase & v);
|
||||
protected:
|
||||
|
||||
protected:
|
||||
/* Print out the value */
|
||||
virtual std::ostream & print(std::ostream & str) const = 0;
|
||||
|
||||
@@ -86,11 +65,29 @@ class ExternalValueBase
|
||||
};
|
||||
|
||||
std::ostream & operator << (std::ostream & str, const ExternalValueBase & v);
|
||||
#endif
|
||||
|
||||
|
||||
struct Value
|
||||
class Context : Object
|
||||
{
|
||||
friend class Value;
|
||||
friend class GC;
|
||||
|
||||
Symbol members[0];
|
||||
|
||||
Context(const PathSet & context) : Object(tContext, context.size())
|
||||
{
|
||||
size_t n = 0;
|
||||
for (auto & i : context)
|
||||
members[n++] = symbols.create(i);
|
||||
}
|
||||
|
||||
size_t getSize() { return getMisc(); }
|
||||
};
|
||||
|
||||
|
||||
struct Value : Object
|
||||
{
|
||||
ValueType type;
|
||||
union
|
||||
{
|
||||
NixInt integer;
|
||||
@@ -113,20 +110,15 @@ struct Value
|
||||
with context C is used as a derivation attribute, then the
|
||||
derivations in C will be added to the inputDrvs of the
|
||||
derivation, and the other store paths in C will be added to
|
||||
the inputSrcs of the derivations.
|
||||
|
||||
For canonicity, the store paths should be in sorted order. */
|
||||
the inputSrcs of the derivations. */
|
||||
struct {
|
||||
const char * s;
|
||||
const char * * context; // must be in sorted order
|
||||
String * s;
|
||||
Context * context;
|
||||
} string;
|
||||
|
||||
const char * path;
|
||||
const char * staticString;
|
||||
String * path;
|
||||
Bindings * attrs;
|
||||
struct {
|
||||
size_t size;
|
||||
Value * * elems;
|
||||
} bigList;
|
||||
PtrList<Value> * bigList;
|
||||
Value * smallList[2];
|
||||
struct {
|
||||
Env * env;
|
||||
@@ -140,46 +132,99 @@ struct Value
|
||||
ExprLambda * fun;
|
||||
} lambda;
|
||||
PrimOp * primOp;
|
||||
struct {
|
||||
Value * left, * right;
|
||||
} primOpApp;
|
||||
ExternalValueBase * external;
|
||||
//ExternalValueBase * external;
|
||||
NixFloat fpoint;
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
Value() : Object(tNull, 0) {}
|
||||
|
||||
friend class GC;
|
||||
template<typename T> friend class Root;
|
||||
|
||||
public:
|
||||
|
||||
bool isList() const
|
||||
{
|
||||
return type == tList1 || type == tList2 || type == tListN;
|
||||
return type >= tList0 && type <= tListN;
|
||||
}
|
||||
|
||||
Value * * listElems()
|
||||
{
|
||||
return type == tList1 || type == tList2 ? smallList : bigList.elems;
|
||||
return type == tList0 || type == tList1 || type == tList2 ? smallList : bigList->elems;
|
||||
}
|
||||
|
||||
const Value * const * listElems() const
|
||||
{
|
||||
return type == tList1 || type == tList2 ? smallList : bigList.elems;
|
||||
return type == tList0 || type == tList1 || type == tList2 ? smallList : bigList->elems;
|
||||
}
|
||||
|
||||
size_t listSize() const
|
||||
{
|
||||
return type == tList1 ? 1 : type == tList2 ? 2 : bigList.size;
|
||||
return type == tList0 ? 0 : type == tList1 ? 1 : type == tList2 ? 2 : bigList->size();
|
||||
}
|
||||
|
||||
constexpr static size_t words() { return 3; } // FIXME
|
||||
|
||||
void setContext(const PathSet & context)
|
||||
{
|
||||
if (context.size() == 0)
|
||||
string.context = nullptr;
|
||||
else if (context.size() == 1) {
|
||||
// If we have a single context, then store it
|
||||
// directly. This saves allocating a Context object (16
|
||||
// bytes).
|
||||
auto symbol = symbols.create(*context.begin());
|
||||
string.context = (Context *) (((ptrdiff_t) symbol.s) | 1);
|
||||
} else {
|
||||
string.context = gc.alloc<Context>(1 + context.size(), context);
|
||||
}
|
||||
}
|
||||
|
||||
void getContext(PathSet & context)
|
||||
{
|
||||
if (type == tLongString && string.context) {
|
||||
if (((ptrdiff_t) string.context) & 1) {
|
||||
auto s = (const std::string *) (((ptrdiff_t) string.context) & ~1UL);
|
||||
context.insert(*s);
|
||||
} else {
|
||||
auto size = string.context->getSize();
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
context.insert(string.context->members[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool isString() const
|
||||
{
|
||||
return type == tShortString || type == tStaticString || type == tLongString;
|
||||
}
|
||||
|
||||
void setShortString(std::string_view s)
|
||||
{
|
||||
// FIXME: can't use strcpy here because gcc flags it as a
|
||||
// buffer overflow on 'misc'.
|
||||
auto p = getMiscData();
|
||||
memcpy(p, s.data(), s.size());
|
||||
p[s.size()] = 0;
|
||||
type = tShortString;
|
||||
}
|
||||
|
||||
const char * getString() const
|
||||
{
|
||||
if (type == tShortString)
|
||||
return getMiscData();
|
||||
else if (type == tStaticString)
|
||||
return staticString;
|
||||
else
|
||||
return string.s->s;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* After overwriting an app node, be sure to clear pointers in the
|
||||
Value to ensure that the target isn't kept alive unnecessarily. */
|
||||
static inline void clearValue(Value & v)
|
||||
{
|
||||
v.app.left = v.app.right = 0;
|
||||
}
|
||||
|
||||
|
||||
static inline void mkInt(Value & v, NixInt n)
|
||||
{
|
||||
clearValue(v);
|
||||
v.type = tInt;
|
||||
v.integer = n;
|
||||
}
|
||||
@@ -187,7 +232,6 @@ static inline void mkInt(Value & v, NixInt n)
|
||||
|
||||
static inline void mkFloat(Value & v, NixFloat n)
|
||||
{
|
||||
clearValue(v);
|
||||
v.type = tFloat;
|
||||
v.fpoint = n;
|
||||
}
|
||||
@@ -195,7 +239,6 @@ static inline void mkFloat(Value & v, NixFloat n)
|
||||
|
||||
static inline void mkBool(Value & v, bool b)
|
||||
{
|
||||
clearValue(v);
|
||||
v.type = tBool;
|
||||
v.boolean = b;
|
||||
}
|
||||
@@ -203,7 +246,6 @@ static inline void mkBool(Value & v, bool b)
|
||||
|
||||
static inline void mkNull(Value & v)
|
||||
{
|
||||
clearValue(v);
|
||||
v.type = tNull;
|
||||
}
|
||||
|
||||
@@ -224,41 +266,35 @@ static inline void mkPrimOpApp(Value & v, Value & left, Value & right)
|
||||
}
|
||||
|
||||
|
||||
static inline void mkStringNoCopy(Value & v, const char * s)
|
||||
static inline Value & mkString(Value & v, std::string_view s)
|
||||
{
|
||||
v.type = tString;
|
||||
v.string.s = s;
|
||||
v.string.context = 0;
|
||||
if (s.size() < WORD_SIZE * 2 + Object::miscBytes)
|
||||
v.setShortString(s);
|
||||
else {
|
||||
v.string.s = gc.alloc<String>(String::wordsFor(s.size()), s.size(), s.data());
|
||||
v.string.context = 0;
|
||||
v.type = tLongString;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
static inline void mkString(Value & v, const Symbol & s)
|
||||
{
|
||||
mkStringNoCopy(v, ((const string &) s).c_str());
|
||||
v.staticString = ((const string &) s).c_str();
|
||||
v.type = tStaticString;
|
||||
}
|
||||
|
||||
|
||||
void mkString(Value & v, const char * s);
|
||||
|
||||
|
||||
static inline void mkPathNoCopy(Value & v, const char * s)
|
||||
static inline void mkPath(Value & v, const std::string & s)
|
||||
{
|
||||
clearValue(v);
|
||||
v.path = String::alloc(s.c_str());
|
||||
v.type = tPath;
|
||||
v.path = s;
|
||||
}
|
||||
|
||||
|
||||
void mkPath(Value & v, const char * s);
|
||||
|
||||
|
||||
#if HAVE_BOEHMGC
|
||||
typedef std::vector<Value *, gc_allocator<Value *> > ValueVector;
|
||||
typedef std::map<Symbol, Value *, std::less<Symbol>, gc_allocator<std::pair<const Symbol, Value *> > > ValueMap;
|
||||
#else
|
||||
typedef std::vector<Value *> ValueVector;
|
||||
typedef std::map<Symbol, Value *> ValueMap;
|
||||
#endif
|
||||
typedef std::vector<Ptr<Value>> ValueVector; // FIXME: make more efficient
|
||||
typedef std::map<Symbol, Ptr<Value>> ValueMap; // FIXME: use Bindings?
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -253,9 +253,6 @@ void printVersion(const string & programName)
|
||||
std::cout << format("%1% (Nix) %2%") % programName % nixVersion << std::endl;
|
||||
if (verbosity > lvlInfo) {
|
||||
Strings cfg;
|
||||
#if HAVE_BOEHMGC
|
||||
cfg.push_back("gc");
|
||||
#endif
|
||||
#if HAVE_SODIUM
|
||||
cfg.push_back("signed-caches");
|
||||
#endif
|
||||
|
||||
@@ -57,23 +57,7 @@ template<class N> N getIntArg(const string & opt,
|
||||
{
|
||||
++i;
|
||||
if (i == end) throw UsageError(format("'%1%' requires an argument") % opt);
|
||||
string s = *i;
|
||||
N multiplier = 1;
|
||||
if (allowUnit && !s.empty()) {
|
||||
char u = std::toupper(*s.rbegin());
|
||||
if (std::isalpha(u)) {
|
||||
if (u == 'K') multiplier = 1ULL << 10;
|
||||
else if (u == 'M') multiplier = 1ULL << 20;
|
||||
else if (u == 'G') multiplier = 1ULL << 30;
|
||||
else if (u == 'T') multiplier = 1ULL << 40;
|
||||
else throw UsageError(format("invalid unit specifier '%1%'") % u);
|
||||
s.resize(s.size() - 1);
|
||||
}
|
||||
}
|
||||
N n;
|
||||
if (!string2Int(s, n))
|
||||
throw UsageError(format("'%1%' requires an integer argument") % opt);
|
||||
return n * multiplier;
|
||||
return parseSize<N>(*i, allowUnit);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -3646,8 +3646,7 @@ void DerivationGoal::registerOutputs()
|
||||
|
||||
if (fixedOutput) {
|
||||
|
||||
bool recursive; Hash h;
|
||||
i.second.parseHashInfo(recursive, h);
|
||||
auto [recursive, h] = i.second.parseHashInfo();
|
||||
|
||||
if (!recursive) {
|
||||
/* The output path should be a regular file without execute permission. */
|
||||
|
||||
@@ -9,21 +9,19 @@
|
||||
namespace nix {
|
||||
|
||||
|
||||
void DerivationOutput::parseHashInfo(bool & recursive, Hash & hash) const
|
||||
std::pair<bool, Hash> DerivationOutput::parseHashInfo() const
|
||||
{
|
||||
recursive = false;
|
||||
bool recursive = false;
|
||||
string algo = hashAlgo;
|
||||
|
||||
if (string(algo, 0, 2) == "r:") {
|
||||
if (string(algo, 0, 2) == "r:")
|
||||
recursive = true;
|
||||
algo = string(algo, 2);
|
||||
}
|
||||
|
||||
HashType hashType = parseHashType(algo);
|
||||
HashType hashType = parseHashType(recursive ? string(algo, 2) : algo);
|
||||
if (hashType == htUnknown)
|
||||
throw Error("unknown hash algorithm '%s'", algo);
|
||||
|
||||
hash = Hash(this->hash, hashType);
|
||||
return {recursive, Hash(this->hash, hashType)};
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ struct DerivationOutput
|
||||
, hashAlgo(std::move(hashAlgo))
|
||||
, hash(std::move(hash))
|
||||
{ }
|
||||
void parseHashInfo(bool & recursive, Hash & hash) const;
|
||||
std::pair<bool, Hash> parseHashInfo() const;
|
||||
};
|
||||
|
||||
typedef std::map<string, DerivationOutput> DerivationOutputs;
|
||||
|
||||
@@ -552,8 +552,7 @@ void LocalStore::checkDerivationOutputs(const StorePath & drvPath, const Derivat
|
||||
if (out == drv.outputs.end())
|
||||
throw Error("derivation '%s' does not have an output named 'out'", printStorePath(drvPath));
|
||||
|
||||
bool recursive; Hash h;
|
||||
out->second.parseHashInfo(recursive, h);
|
||||
auto [recursive, h] = out->second.parseHashInfo();
|
||||
auto outPath = makeFixedOutputPath(recursive, h, drvName);
|
||||
|
||||
StringPairs::const_iterator j = drv.env.find("out");
|
||||
|
||||
@@ -384,7 +384,7 @@ bool statusOk(int status);
|
||||
|
||||
|
||||
/* Parse a string into an integer. */
|
||||
template<class N> bool string2Int(const string & s, N & n)
|
||||
template<typename N> bool string2Int(const string & s, N & n)
|
||||
{
|
||||
if (string(s, 0, 1) == "-" && !std::numeric_limits<N>::is_signed)
|
||||
return false;
|
||||
@@ -393,6 +393,27 @@ template<class N> bool string2Int(const string & s, N & n)
|
||||
return str && str.get() == EOF;
|
||||
}
|
||||
|
||||
template<typename N>
|
||||
N parseSize(std::string s, bool allowUnit = true)
|
||||
{
|
||||
N multiplier = 1;
|
||||
if (allowUnit && !s.empty()) {
|
||||
char u = std::toupper(*s.rbegin());
|
||||
if (std::isalpha(u)) {
|
||||
if (u == 'K') multiplier = 1ULL << 10;
|
||||
else if (u == 'M') multiplier = 1ULL << 20;
|
||||
else if (u == 'G') multiplier = 1ULL << 30;
|
||||
else if (u == 'T') multiplier = 1ULL << 40;
|
||||
else throw Error("invalid unit specifier '%1%'", u);
|
||||
s.resize(s.size() - 1);
|
||||
}
|
||||
}
|
||||
N n;
|
||||
if (!string2Int(s, n))
|
||||
throw Error("'%s' is not an integer", s);
|
||||
return n * multiplier;
|
||||
}
|
||||
|
||||
/* Parse a string into a float. */
|
||||
template<class N> bool string2Float(const string & s, N & n)
|
||||
{
|
||||
|
||||
@@ -248,7 +248,7 @@ static void _main(int argc, char * * argv)
|
||||
auto autoArgs = myArgs.getAutoArgs(*state);
|
||||
|
||||
if (runEnv) {
|
||||
auto newArgs = state->allocBindings(autoArgs->size() + 1);
|
||||
auto newArgs = Bindings::allocBindings(autoArgs->size() + 1);
|
||||
auto tru = state->allocValue();
|
||||
mkBool(*tru, true);
|
||||
newArgs->push_back(Attr(state->symbols.create("inNixShell"), tru));
|
||||
@@ -305,11 +305,11 @@ static void _main(int argc, char * * argv)
|
||||
if (attrPaths.empty()) attrPaths = {""};
|
||||
|
||||
for (auto e : exprs) {
|
||||
Value vRoot;
|
||||
Root<Value> vRoot;
|
||||
state->eval(e, vRoot);
|
||||
|
||||
for (auto & i : attrPaths) {
|
||||
Value & v(*findAlongAttrPath(*state, i, *autoArgs, vRoot));
|
||||
auto v = findAlongAttrPath(*state, i, *autoArgs, vRoot);
|
||||
state->forceValue(v);
|
||||
getDerivations(*state, v, "", *autoArgs, drvs, false);
|
||||
}
|
||||
@@ -351,7 +351,7 @@ static void _main(int argc, char * * argv)
|
||||
try {
|
||||
auto expr = state->parseExprFromString("(import <nixpkgs> {}).bashInteractive", absPath("."));
|
||||
|
||||
Value v;
|
||||
Root<Value> v;
|
||||
state->eval(expr, v);
|
||||
|
||||
auto drv = getDerivation(*state, v, false);
|
||||
|
||||
@@ -46,7 +46,7 @@ struct InstallSourceInfo
|
||||
Path nixExprPath; /* for srcNixExprDrvs, srcNixExprs */
|
||||
Path profile; /* for srcProfile */
|
||||
string systemFilter; /* for srcNixExprDrvs */
|
||||
Bindings * autoArgs;
|
||||
Ptr<Bindings> autoArgs;
|
||||
};
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ static void getAllExprs(EvalState & state,
|
||||
}
|
||||
/* Load the expression on demand. */
|
||||
Value & vFun = state.getBuiltin("import");
|
||||
Value & vArg(*state.allocValue());
|
||||
auto vArg = state.allocValue();
|
||||
mkString(vArg, path2);
|
||||
if (v.attrs->size() == v.attrs->capacity())
|
||||
throw Error(format("too many Nix expressions in directory '%1%'") % path);
|
||||
@@ -175,10 +175,10 @@ static void loadDerivations(EvalState & state, Path nixExprPath,
|
||||
string systemFilter, Bindings & autoArgs,
|
||||
const string & pathPrefix, DrvInfos & elems)
|
||||
{
|
||||
Value vRoot;
|
||||
auto vRoot = state.allocValue();
|
||||
loadSourceExpr(state, nixExprPath, vRoot);
|
||||
|
||||
Value & v(*findAlongAttrPath(state, pathPrefix, autoArgs, vRoot));
|
||||
auto v = findAlongAttrPath(state, pathPrefix, autoArgs, vRoot);
|
||||
|
||||
getDerivations(state, v, pathPrefix, autoArgs, elems, true);
|
||||
|
||||
@@ -352,13 +352,14 @@ static void queryInstSources(EvalState & state,
|
||||
(import ./foo.nix)' = `(import ./foo.nix).bar'. */
|
||||
case srcNixExprs: {
|
||||
|
||||
Value vArg;
|
||||
auto vArg = state.allocValue();
|
||||
loadSourceExpr(state, instSource.nixExprPath, vArg);
|
||||
|
||||
for (auto & i : args) {
|
||||
Expr * eFun = state.parseExprFromString(i, absPath("."));
|
||||
Value vFun, vTmp;
|
||||
auto vFun = state.allocValue();
|
||||
state.eval(eFun, vFun);
|
||||
auto vTmp = state.allocValue();
|
||||
mkApp(vTmp, vFun, vArg);
|
||||
getDerivations(state, vTmp, "", *instSource.autoArgs, elems, true);
|
||||
}
|
||||
@@ -405,10 +406,10 @@ static void queryInstSources(EvalState & state,
|
||||
}
|
||||
|
||||
case srcAttrPath: {
|
||||
Value vRoot;
|
||||
Root<Value> vRoot;
|
||||
loadSourceExpr(state, instSource.nixExprPath, vRoot);
|
||||
for (auto & i : args) {
|
||||
Value & v(*findAlongAttrPath(state, i, *instSource.autoArgs, vRoot));
|
||||
auto v = findAlongAttrPath(state, i, *instSource.autoArgs, vRoot);
|
||||
getDerivations(state, v, "", *instSource.autoArgs, elems, true);
|
||||
}
|
||||
break;
|
||||
@@ -628,7 +629,7 @@ static void opUpgrade(Globals & globals, Strings opFlags, Strings opArgs)
|
||||
static void setMetaFlag(EvalState & state, DrvInfo & drv,
|
||||
const string & name, const string & value)
|
||||
{
|
||||
Value * v = state.allocValue();
|
||||
auto v = state.allocValue();
|
||||
mkString(*v, value.c_str());
|
||||
drv.setMeta(name, v);
|
||||
}
|
||||
@@ -1112,9 +1113,9 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
|
||||
if (!v)
|
||||
printError("derivation '%s' has invalid meta attribute '%s'", i.queryName(), j);
|
||||
else {
|
||||
if (v->type == tString) {
|
||||
if (v->isString()) {
|
||||
attrs2["type"] = "string";
|
||||
attrs2["value"] = v->string.s;
|
||||
attrs2["value"] = v->getString();
|
||||
xml.writeEmptyElement("meta", attrs2);
|
||||
} else if (v->type == tInt) {
|
||||
attrs2["type"] = "int";
|
||||
@@ -1132,9 +1133,9 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
|
||||
attrs2["type"] = "strings";
|
||||
XMLOpenElement m(xml, "meta", attrs2);
|
||||
for (unsigned int j = 0; j < v->listSize(); ++j) {
|
||||
if (v->listElems()[j]->type != tString) continue;
|
||||
if (!v->listElems()[j]->isString()) continue;
|
||||
XMLAttrs attrs3;
|
||||
attrs3["value"] = v->listElems()[j]->string.s;
|
||||
attrs3["value"] = v->listElems()[j]->getString();
|
||||
xml.writeEmptyElement("string", attrs3);
|
||||
}
|
||||
} else if (v->type == tAttrs) {
|
||||
@@ -1143,10 +1144,10 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
|
||||
Bindings & attrs = *v->attrs;
|
||||
for (auto &i : attrs) {
|
||||
Attr & a(*attrs.find(i.name));
|
||||
if(a.value->type != tString) continue;
|
||||
if (!a.value->isString()) continue;
|
||||
XMLAttrs attrs3;
|
||||
attrs3["type"] = i.name;
|
||||
attrs3["value"] = a.value->string.s;
|
||||
attrs3["value"] = a.value->getString();
|
||||
xml.writeEmptyElement("string", attrs3);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,9 @@ DrvInfos queryInstalled(EvalState & state, const Path & userEnv)
|
||||
DrvInfos elems;
|
||||
Path manifestFile = userEnv + "/manifest.nix";
|
||||
if (pathExists(manifestFile)) {
|
||||
Value v;
|
||||
Root<Value> v;
|
||||
state.evalFile(manifestFile, v);
|
||||
Bindings & bindings(*state.allocBindings(0));
|
||||
auto bindings = Bindings::allocBindings(0);
|
||||
getDerivations(state, v, "", bindings, elems, false);
|
||||
}
|
||||
return elems;
|
||||
@@ -42,7 +42,7 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
|
||||
|
||||
/* Construct the whole top level derivation. */
|
||||
StorePathSet references;
|
||||
Value manifest;
|
||||
auto manifest = state.allocValue();
|
||||
state.mkList(manifest, elems.size());
|
||||
unsigned int n = 0;
|
||||
for (auto & i : elems) {
|
||||
@@ -51,8 +51,8 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
|
||||
as the meta attributes. */
|
||||
Path drvPath = keepDerivations ? i.queryDrvPath() : "";
|
||||
|
||||
Value & v(*state.allocValue());
|
||||
manifest.listElems()[n++] = &v;
|
||||
auto v = state.allocValue();
|
||||
manifest->listElems()[n++] = (Value *) v;
|
||||
state.mkAttrs(v, 16);
|
||||
|
||||
mkString(*state.allocAttr(v, state.sType), "derivation");
|
||||
@@ -70,7 +70,7 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
|
||||
state.mkList(vOutputs, outputs.size());
|
||||
unsigned int m = 0;
|
||||
for (auto & j : outputs) {
|
||||
mkString(*(vOutputs.listElems()[m++] = state.allocValue()), j.first);
|
||||
mkString(*(vOutputs.listElems()[m++] = (Value *) state.allocValue()), j.first);
|
||||
Value & vOutputs = *state.allocAttr(v, state.symbols.create(j.first));
|
||||
state.mkAttrs(vOutputs, 2);
|
||||
mkString(*state.allocAttr(vOutputs, state.sOutPath), j.second);
|
||||
@@ -93,7 +93,7 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
|
||||
vMeta.attrs->push_back(Attr(state.symbols.create(j), v));
|
||||
}
|
||||
vMeta.attrs->sort();
|
||||
v.attrs->sort();
|
||||
v->attrs->sort();
|
||||
|
||||
if (drvPath != "") references.insert(state.store->parseStorePath(drvPath));
|
||||
}
|
||||
@@ -102,29 +102,30 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
|
||||
the store; we need it for future modifications of the
|
||||
environment. */
|
||||
auto manifestFile = state.store->addTextToStore("env-manifest.nix",
|
||||
fmt("%s", manifest), references);
|
||||
fmt("%1%", (Value &) manifest), references);
|
||||
|
||||
/* Get the environment builder expression. */
|
||||
Value envBuilder;
|
||||
auto envBuilder = state.allocValue();
|
||||
state.evalFile(state.findFile("nix/buildenv.nix"), envBuilder);
|
||||
|
||||
/* Construct a Nix expression that calls the user environment
|
||||
builder with the manifest as argument. */
|
||||
Value args, topLevel;
|
||||
auto args = state.allocValue();
|
||||
Root<Value> topLevel;
|
||||
state.mkAttrs(args, 3);
|
||||
mkString(*state.allocAttr(args, state.symbols.create("manifest")),
|
||||
state.store->printStorePath(manifestFile), {state.store->printStorePath(manifestFile)});
|
||||
args.attrs->push_back(Attr(state.symbols.create("derivations"), &manifest));
|
||||
args.attrs->sort();
|
||||
args->attrs->push_back(Attr(state.symbols.create("derivations"), (Value *) manifest));
|
||||
args->attrs->sort();
|
||||
mkApp(topLevel, envBuilder, args);
|
||||
|
||||
/* Evaluate it. */
|
||||
debug("evaluating user environment builder");
|
||||
state.forceValue(topLevel);
|
||||
PathSet context;
|
||||
Attr & aDrvPath(*topLevel.attrs->find(state.sDrvPath));
|
||||
Attr & aDrvPath(*topLevel->attrs->find(state.sDrvPath));
|
||||
auto topLevelDrv = state.store->parseStorePath(state.coerceToPath(aDrvPath.pos ? *(aDrvPath.pos) : noPos, *(aDrvPath.value), context));
|
||||
Attr & aOutPath(*topLevel.attrs->find(state.sOutPath));
|
||||
Attr & aOutPath(*topLevel->attrs->find(state.sOutPath));
|
||||
Path topLevelOut = state.coerceToPath(aOutPath.pos ? *(aOutPath.pos) : noPos, *(aOutPath.value), context);
|
||||
|
||||
/* Realise the resulting store expression. */
|
||||
|
||||
@@ -35,18 +35,18 @@ void processExpr(EvalState & state, const Strings & attrPaths,
|
||||
return;
|
||||
}
|
||||
|
||||
Value vRoot;
|
||||
Root<Value> vRoot;
|
||||
state.eval(e, vRoot);
|
||||
|
||||
for (auto & i : attrPaths) {
|
||||
Value & v(*findAlongAttrPath(state, i, autoArgs, vRoot));
|
||||
auto v = findAlongAttrPath(state, i, autoArgs, vRoot);
|
||||
state.forceValue(v);
|
||||
|
||||
PathSet context;
|
||||
if (evalOnly) {
|
||||
Value vRes;
|
||||
Root<Value> vRes;
|
||||
if (autoArgs.empty())
|
||||
vRes = v;
|
||||
vRes = *v;
|
||||
else
|
||||
state.autoCallFunction(autoArgs, v, vRes);
|
||||
if (output == okXML)
|
||||
@@ -55,7 +55,7 @@ void processExpr(EvalState & state, const Strings & attrPaths,
|
||||
printValueAsJSON(state, strict, vRes, std::cout, context);
|
||||
else {
|
||||
if (strict) state.forceValueDeep(vRes);
|
||||
std::cout << vRes << std::endl;
|
||||
std::cout << *vRes << std::endl;
|
||||
}
|
||||
} else {
|
||||
DrvInfos drvs;
|
||||
@@ -159,7 +159,7 @@ static int _main(int argc, char * * argv)
|
||||
auto state = std::make_unique<EvalState>(myArgs.searchPath, store);
|
||||
state->repair = repair;
|
||||
|
||||
Bindings & autoArgs = *myArgs.getAutoArgs(*state);
|
||||
auto autoArgs = myArgs.getAutoArgs(*state);
|
||||
|
||||
if (attrPaths.empty()) attrPaths = {""};
|
||||
|
||||
@@ -174,7 +174,7 @@ static int _main(int argc, char * * argv)
|
||||
|
||||
if (readStdin) {
|
||||
Expr * e = state->parseStdin();
|
||||
processExpr(*state, attrPaths, parseOnly, strict, autoArgs,
|
||||
processExpr(*state, attrPaths, parseOnly, strict, *autoArgs,
|
||||
evalOnly, outputKind, xmlOutputSourceLocation, e);
|
||||
} else if (files.empty() && !fromArgs)
|
||||
files.push_back("./default.nix");
|
||||
@@ -183,7 +183,7 @@ static int _main(int argc, char * * argv)
|
||||
Expr * e = fromArgs
|
||||
? state->parseExprFromString(i, absPath("."))
|
||||
: state->parseExprFromFile(resolveExprPath(state->checkSourcePath(lookupFileArg(*state, i))));
|
||||
processExpr(*state, attrPaths, parseOnly, strict, autoArgs,
|
||||
processExpr(*state, attrPaths, parseOnly, strict, *autoArgs,
|
||||
evalOnly, outputKind, xmlOutputSourceLocation, e);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,12 +31,12 @@ string resolveMirrorUri(EvalState & state, string uri)
|
||||
if (p == string::npos) throw Error("invalid mirror URI");
|
||||
string mirrorName(s, 0, p);
|
||||
|
||||
Value vMirrors;
|
||||
Root<Value> vMirrors;
|
||||
state.eval(state.parseExprFromString("import <nixpkgs/pkgs/build-support/fetchurl/mirrors.nix>", "."), vMirrors);
|
||||
state.forceAttrs(vMirrors);
|
||||
|
||||
auto mirrorList = vMirrors.attrs->find(state.symbols.create(mirrorName));
|
||||
if (mirrorList == vMirrors.attrs->end())
|
||||
auto mirrorList = vMirrors->attrs->find(state.symbols.create(mirrorName));
|
||||
if (mirrorList == vMirrors->attrs->end())
|
||||
throw Error(format("unknown mirror name '%1%'") % mirrorName);
|
||||
state.forceList(*mirrorList->value);
|
||||
|
||||
@@ -107,7 +107,7 @@ static int _main(int argc, char * * argv)
|
||||
auto store = openStore();
|
||||
auto state = std::make_unique<EvalState>(myArgs.searchPath, store);
|
||||
|
||||
Bindings & autoArgs = *myArgs.getAutoArgs(*state);
|
||||
auto autoArgs = myArgs.getAutoArgs(*state);
|
||||
|
||||
/* If -A is given, get the URI from the specified Nix
|
||||
expression. */
|
||||
@@ -118,14 +118,14 @@ static int _main(int argc, char * * argv)
|
||||
uri = args[0];
|
||||
} else {
|
||||
Path path = resolveExprPath(lookupFileArg(*state, args.empty() ? "." : args[0]));
|
||||
Value vRoot;
|
||||
Root<Value> vRoot;
|
||||
state->evalFile(path, vRoot);
|
||||
Value & v(*findAlongAttrPath(*state, attrPath, autoArgs, vRoot));
|
||||
auto v = findAlongAttrPath(*state, attrPath, *autoArgs, vRoot);
|
||||
state->forceAttrs(v);
|
||||
|
||||
/* Extract the URI. */
|
||||
auto attr = v.attrs->find(state->symbols.create("urls"));
|
||||
if (attr == v.attrs->end())
|
||||
auto attr = v->attrs->find(state->symbols.create("urls"));
|
||||
if (attr == v->attrs->end())
|
||||
throw Error("attribute set does not contain a 'urls' attribute");
|
||||
state->forceList(*attr->value);
|
||||
if (attr->value->listSize() < 1)
|
||||
@@ -133,16 +133,16 @@ static int _main(int argc, char * * argv)
|
||||
uri = state->forceString(*attr->value->listElems()[0]);
|
||||
|
||||
/* Extract the hash mode. */
|
||||
attr = v.attrs->find(state->symbols.create("outputHashMode"));
|
||||
if (attr == v.attrs->end())
|
||||
attr = v->attrs->find(state->symbols.create("outputHashMode"));
|
||||
if (attr == v->attrs->end())
|
||||
printInfo("warning: this does not look like a fetchurl call");
|
||||
else
|
||||
unpack = state->forceString(*attr->value) == "recursive";
|
||||
|
||||
/* Extract the name. */
|
||||
if (name.empty()) {
|
||||
attr = v.attrs->find(state->symbols.create("name"));
|
||||
if (attr != v.attrs->end())
|
||||
attr = v->attrs->find(state->symbols.create("name"));
|
||||
if (attr != v->attrs->end())
|
||||
name = state->forceString(*attr->value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "args.hh"
|
||||
#include "gc.hh"
|
||||
#include "common-eval-args.hh"
|
||||
#include "path.hh"
|
||||
|
||||
@@ -48,7 +49,7 @@ struct Installable
|
||||
|
||||
Buildable toBuildable();
|
||||
|
||||
virtual Value * toValue(EvalState & state)
|
||||
virtual Ptr<Value> toValue(EvalState & state)
|
||||
{
|
||||
throw Error("argument '%s' cannot be evaluated", what());
|
||||
}
|
||||
@@ -56,6 +57,8 @@ struct Installable
|
||||
|
||||
struct SourceExprCommand : virtual Args, StoreCommand, MixEvalArgs
|
||||
{
|
||||
friend void mainWrapped(int argc, char * * argv);
|
||||
|
||||
Path file;
|
||||
|
||||
SourceExprCommand();
|
||||
@@ -64,7 +67,7 @@ struct SourceExprCommand : virtual Args, StoreCommand, MixEvalArgs
|
||||
are installing. This is either the file specified by ‘--file’,
|
||||
or an attribute set constructed from $NIX_PATH, e.g. ‘{ nixpkgs
|
||||
= import ...; bla = import ...; }’. */
|
||||
Value * getSourceExpr(EvalState & state);
|
||||
Ptr<Value> getSourceExpr(EvalState & state);
|
||||
|
||||
ref<EvalState> getEvalState();
|
||||
|
||||
@@ -72,7 +75,7 @@ private:
|
||||
|
||||
std::shared_ptr<EvalState> evalState;
|
||||
|
||||
Value * vSourceExpr = 0;
|
||||
Ptr<Value> vSourceExpr;
|
||||
};
|
||||
|
||||
enum RealiseMode { Build, NoBuild, DryRun };
|
||||
|
||||
332
src/nix/eval-hydra-jobs.cc
Normal file
332
src/nix/eval-hydra-jobs.cc
Normal file
@@ -0,0 +1,332 @@
|
||||
#include "command.hh"
|
||||
#include "eval.hh"
|
||||
#include "eval-inline.hh"
|
||||
#include "derivations.hh"
|
||||
#include "common-args.hh"
|
||||
#include "json.hh"
|
||||
#include "get-drvs.hh"
|
||||
#include "attr-path.hh"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <sys/resource.h>
|
||||
|
||||
using namespace nix;
|
||||
|
||||
static std::string queryMetaStrings(EvalState & state, DrvInfo & drv, const string & name, const string & subAttribute)
|
||||
{
|
||||
Strings res;
|
||||
std::function<void(Value & v)> rec;
|
||||
|
||||
rec = [&](Value & v) {
|
||||
state.forceValue(v);
|
||||
if (v.type == tString)
|
||||
res.push_back(v.getString());
|
||||
else if (v.isList())
|
||||
for (unsigned int n = 0; n < v.listSize(); ++n)
|
||||
rec(*v.listElems()[n]);
|
||||
else if (v.type == tAttrs) {
|
||||
auto a = v.attrs->find(state.symbols.create(subAttribute));
|
||||
if (a != v.attrs->end())
|
||||
res.push_back(state.forceString(*a->value));
|
||||
}
|
||||
};
|
||||
|
||||
Value * v = drv.queryMeta(name);
|
||||
if (v) rec(*v);
|
||||
|
||||
return concatStringsSep(", ", res);
|
||||
}
|
||||
|
||||
struct CmdEvalHydraJobs : MixJSON, MixDryRun, InstallableCommand
|
||||
{
|
||||
std::optional<Path> gcRootsDir;
|
||||
size_t nrWorkers = 1;
|
||||
size_t maxMemorySize = 4ULL * 1024;
|
||||
|
||||
CmdEvalHydraJobs()
|
||||
{
|
||||
mkFlag()
|
||||
.longName("gc-roots-dir")
|
||||
.description("garbage collector roots directory")
|
||||
.labels({"path"})
|
||||
.dest(&gcRootsDir);
|
||||
|
||||
mkIntFlag(0, "workers", "number of concurrent worker processes", &nrWorkers);
|
||||
|
||||
mkIntFlag(0, "max-memory-size", "maximum memory usage per worker process (in MiB)", &maxMemorySize);
|
||||
}
|
||||
|
||||
std::string description() override
|
||||
{
|
||||
return "evaluate a Hydra jobset";
|
||||
}
|
||||
|
||||
Examples examples() override
|
||||
{
|
||||
return {
|
||||
Example{
|
||||
"Evaluate Nixpkgs' release-combined jobset:",
|
||||
"nix eval-hydra-jobs -f '<nixpkgs/nixos/release-combined.nix>' '' --json"
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
void worker(AutoCloseFD & to, AutoCloseFD & from)
|
||||
{
|
||||
auto state = getEvalState();
|
||||
|
||||
// FIXME: should re-open state->store.
|
||||
|
||||
if (dryRun) settings.readOnlyMode = true;
|
||||
|
||||
/* Prevent access to paths outside of the Nix search path and
|
||||
to the environment. */
|
||||
evalSettings.restrictEval = true;
|
||||
|
||||
auto vTop = installable->toValue(*state);
|
||||
|
||||
auto vRoot = state->allocValue();
|
||||
state->autoCallFunction(getAutoArgs(*state), vTop, vRoot);
|
||||
|
||||
while (true) {
|
||||
/* Wait for the master to send us a job name. */
|
||||
writeLine(to.get(), "next");
|
||||
|
||||
auto s = readLine(from.get());
|
||||
if (s == "exit") break;
|
||||
if (!hasPrefix(s, "do ")) abort();
|
||||
std::string attrPath(s, 3);
|
||||
|
||||
debug("worker process %d at '%s'", getpid(), attrPath);
|
||||
|
||||
/* Evaluate it and send info back to the master. */
|
||||
nlohmann::json reply;
|
||||
|
||||
try {
|
||||
auto v = findAlongAttrPath(*state, attrPath, getAutoArgs(*state), vRoot);
|
||||
|
||||
state->forceValue(v);
|
||||
|
||||
if (auto drv = getDerivation(*state, v, false)) {
|
||||
|
||||
DrvInfo::Outputs outputs = drv->queryOutputs();
|
||||
|
||||
if (drv->querySystem() == "unknown")
|
||||
throw EvalError("derivation must have a 'system' attribute");
|
||||
|
||||
auto drvPath = drv->queryDrvPath();
|
||||
|
||||
nlohmann::json job;
|
||||
|
||||
job["nixName"] = drv->queryName();
|
||||
job["system"] =drv->querySystem();
|
||||
job["drvPath"] = drvPath;
|
||||
job["description"] = drv->queryMetaString("description");
|
||||
job["license"] = queryMetaStrings(*state, *drv, "license", "shortName");
|
||||
job["homepage"] = drv->queryMetaString("homepage");
|
||||
job["maintainers"] = queryMetaStrings(*state, *drv, "maintainers", "email");
|
||||
job["schedulingPriority"] = drv->queryMetaInt("schedulingPriority", 100);
|
||||
job["timeout"] = drv->queryMetaInt("timeout", 36000);
|
||||
job["maxSilent"] = drv->queryMetaInt("maxSilent", 7200);
|
||||
job["isChannel"] = drv->queryMetaBool("isHydraChannel", false);
|
||||
|
||||
/* If this is an aggregate, then get its constituents. */
|
||||
auto a = v->attrs->get(state->symbols.create("_hydraAggregate"));
|
||||
if (a && state->forceBool(*a->value, *a->pos)) {
|
||||
auto a = v->attrs->get(state->symbols.create("constituents"));
|
||||
if (!a)
|
||||
throw EvalError("derivation must have a ‘constituents’ attribute");
|
||||
PathSet context;
|
||||
state->coerceToString(*a->pos, *a->value, context, true, false);
|
||||
PathSet drvs;
|
||||
for (auto & i : context)
|
||||
if (i.at(0) == '!') {
|
||||
size_t index = i.find("!", 1);
|
||||
drvs.insert(string(i, index + 1));
|
||||
}
|
||||
job["constituents"] = concatStringsSep(" ", drvs);
|
||||
}
|
||||
|
||||
/* Register the derivation as a GC root. !!! This
|
||||
registers roots for jobs that we may have already
|
||||
done. */
|
||||
auto localStore = state->store.dynamic_pointer_cast<LocalFSStore>();
|
||||
if (gcRootsDir && localStore) {
|
||||
Path root = *gcRootsDir + "/" + std::string(baseNameOf(drvPath));
|
||||
if (!pathExists(root))
|
||||
localStore->addPermRoot(localStore->parseStorePath(drvPath), root, false);
|
||||
}
|
||||
|
||||
nlohmann::json out;
|
||||
for (auto & j : outputs)
|
||||
out[j.first] = j.second;
|
||||
job["outputs"] = std::move(out);
|
||||
|
||||
reply["job"] = std::move(job);
|
||||
}
|
||||
|
||||
else if (v->type == tAttrs) {
|
||||
auto attrs = nlohmann::json::array();
|
||||
StringSet ss;
|
||||
for (auto & i : v->attrs->lexicographicOrder()) {
|
||||
std::string name(i->name);
|
||||
if (name.find('.') != std::string::npos || name.find(' ') != std::string::npos) {
|
||||
printError("skipping job with illegal name '%s'", name);
|
||||
continue;
|
||||
}
|
||||
attrs.push_back(name);
|
||||
}
|
||||
reply["attrs"] = std::move(attrs);
|
||||
}
|
||||
|
||||
} catch (EvalError & e) {
|
||||
reply["error"] = filterANSIEscapes(e.msg(), true);
|
||||
}
|
||||
|
||||
writeLine(to.get(), reply.dump());
|
||||
|
||||
/* If our RSS exceeds the maximum, exit. The master will
|
||||
start a new process. */
|
||||
struct rusage r;
|
||||
getrusage(RUSAGE_SELF, &r);
|
||||
if ((size_t) r.ru_maxrss > maxMemorySize * 1024) break;
|
||||
}
|
||||
|
||||
writeLine(to.get(), "restart");
|
||||
}
|
||||
|
||||
void run(ref<Store> store) override
|
||||
{
|
||||
if (!gcRootsDir) warn("'--gc-roots-dir' not specified");
|
||||
|
||||
struct State
|
||||
{
|
||||
std::set<std::string> todo{""};
|
||||
std::set<std::string> active;
|
||||
nlohmann::json result;
|
||||
};
|
||||
|
||||
std::condition_variable wakeup;
|
||||
|
||||
Sync<State> state_;
|
||||
|
||||
/* Start a handler thread per worker process. */
|
||||
auto handler = [this, &state_, &wakeup]()
|
||||
{
|
||||
try {
|
||||
pid_t pid = -1;
|
||||
AutoCloseFD from, to;
|
||||
|
||||
while (true) {
|
||||
|
||||
/* Start a new worker process if necessary. */
|
||||
if (pid == -1) {
|
||||
Pipe toPipe, fromPipe;
|
||||
toPipe.create();
|
||||
fromPipe.create();
|
||||
pid = startProcess(
|
||||
[this,
|
||||
to{std::make_shared<AutoCloseFD>(std::move(fromPipe.writeSide))},
|
||||
from{std::make_shared<AutoCloseFD>(std::move(toPipe.readSide))}
|
||||
]()
|
||||
{
|
||||
try {
|
||||
worker(*to, *from);
|
||||
} catch (Error & e) {
|
||||
printError("unexpected worker error: %s", e.msg());
|
||||
_exit(1);
|
||||
}
|
||||
},
|
||||
ProcessOptions { .allowVfork = false });
|
||||
from = std::move(fromPipe.readSide);
|
||||
to = std::move(toPipe.writeSide);
|
||||
debug("created worker process %d", pid);
|
||||
}
|
||||
|
||||
/* Check whether the existing worker process is still there. */
|
||||
auto s = readLine(from.get());
|
||||
if (s == "restart") {
|
||||
pid = -1;
|
||||
continue;
|
||||
} else if (s != "next")
|
||||
throw Error("unexpected worker request: %s", s);
|
||||
|
||||
/* Wait for a job name to become available. */
|
||||
std::string attrPath;
|
||||
|
||||
while (true) {
|
||||
checkInterrupt();
|
||||
auto state(state_.lock());
|
||||
if (state->todo.empty() && state->active.empty()) {
|
||||
writeLine(to.get(), "exit");
|
||||
return;
|
||||
}
|
||||
if (!state->todo.empty()) {
|
||||
attrPath = *state->todo.begin();
|
||||
state->todo.erase(state->todo.begin());
|
||||
state->active.insert(attrPath);
|
||||
break;
|
||||
} else
|
||||
state.wait(wakeup);
|
||||
}
|
||||
|
||||
/* Tell the worker to evaluate it. */
|
||||
writeLine(to.get(), "do " + attrPath);
|
||||
|
||||
/* Wait for the response. */
|
||||
auto response = nlohmann::json::parse(readLine(from.get()));
|
||||
|
||||
/* Handle the response. */
|
||||
StringSet newAttrs;
|
||||
|
||||
if (response.find("job") != response.end()) {
|
||||
auto state(state_.lock());
|
||||
if (json)
|
||||
state->result[attrPath] = response["job"];
|
||||
else
|
||||
std::cout << fmt("%d: %d\n", attrPath, (std::string) response["job"]["drvPath"]);
|
||||
}
|
||||
|
||||
if (response.find("attrs") != response.end()) {
|
||||
for (auto & i : response["attrs"]) {
|
||||
auto s = (attrPath.empty() ? "" : attrPath + ".") + (std::string) i;
|
||||
newAttrs.insert(s);
|
||||
}
|
||||
}
|
||||
|
||||
if (response.find("error") != response.end()) {
|
||||
auto state(state_.lock());
|
||||
if (json)
|
||||
state->result[attrPath]["error"] = response["error"];
|
||||
else
|
||||
printError("error in job '%s': %s",
|
||||
attrPath, (std::string) response["error"]);
|
||||
}
|
||||
|
||||
/* Add newly discovered job names to the queue. */
|
||||
{
|
||||
auto state(state_.lock());
|
||||
state->active.erase(attrPath);
|
||||
for (auto & s : newAttrs)
|
||||
state->todo.insert(s);
|
||||
wakeup.notify_all();
|
||||
}
|
||||
}
|
||||
} catch (Error & e) {
|
||||
printError("unexpected handler thread error: %s", e.msg());
|
||||
abort();
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<std::thread> threads;
|
||||
for (size_t i = 0; i < nrWorkers; i++)
|
||||
threads.emplace_back(std::thread(handler));
|
||||
|
||||
for (auto & thread : threads)
|
||||
thread.join();
|
||||
|
||||
if (json) std::cout << state_.lock()->result.dump(2) << "\n";
|
||||
}
|
||||
};
|
||||
|
||||
static auto r1 = registerCommand<CmdEvalHydraJobs>("eval-hydra-jobs");
|
||||
@@ -22,7 +22,7 @@ SourceExprCommand::SourceExprCommand()
|
||||
.dest(&file);
|
||||
}
|
||||
|
||||
Value * SourceExprCommand::getSourceExpr(EvalState & state)
|
||||
Ptr<Value> SourceExprCommand::getSourceExpr(EvalState & state)
|
||||
{
|
||||
if (vSourceExpr) return vSourceExpr;
|
||||
|
||||
@@ -48,10 +48,10 @@ Value * SourceExprCommand::getSourceExpr(EvalState & state)
|
||||
auto addEntry = [&](const std::string & name) {
|
||||
if (name == "") return;
|
||||
if (!seen.insert(name).second) return;
|
||||
Value * v1 = state.allocValue();
|
||||
auto v1 = state.allocValue();
|
||||
mkPrimOpApp(*v1, state.getBuiltin("findFile"), state.getBuiltin("nixPath"));
|
||||
Value * v2 = state.allocValue();
|
||||
mkApp(*v2, *v1, mkString(*state.allocValue(), name));
|
||||
auto v2 = state.allocValue();
|
||||
mkApp(*v2, *v1, mkString(state.allocValue(), name));
|
||||
mkApp(*state.allocAttr(*vSourceExpr, state.symbols.create(name)),
|
||||
state.getBuiltin("import"), *v2);
|
||||
};
|
||||
@@ -169,7 +169,7 @@ struct InstallableExpr : InstallableValue
|
||||
|
||||
std::string what() override { return text; }
|
||||
|
||||
Value * toValue(EvalState & state) override
|
||||
Ptr<Value> toValue(EvalState & state) override
|
||||
{
|
||||
auto v = state.allocValue();
|
||||
state.eval(state.parseExprFromString(text, absPath(".")), *v);
|
||||
@@ -187,13 +187,13 @@ struct InstallableAttrPath : InstallableValue
|
||||
|
||||
std::string what() override { return attrPath; }
|
||||
|
||||
Value * toValue(EvalState & state) override
|
||||
Ptr<Value> toValue(EvalState & state) override
|
||||
{
|
||||
auto source = cmd.getSourceExpr(state);
|
||||
|
||||
Bindings & autoArgs = *cmd.getAutoArgs(state);
|
||||
auto autoArgs = cmd.getAutoArgs(state);
|
||||
|
||||
Value * v = findAlongAttrPath(state, attrPath, autoArgs, *source);
|
||||
auto v = findAlongAttrPath(state, attrPath, *autoArgs, *source);
|
||||
state.forceValue(*v);
|
||||
|
||||
return v;
|
||||
|
||||
136
src/nix/list-tarballs.cc
Normal file
136
src/nix/list-tarballs.cc
Normal file
@@ -0,0 +1,136 @@
|
||||
#include "command.hh"
|
||||
#include "eval.hh"
|
||||
#include "eval-inline.hh"
|
||||
#include "derivations.hh"
|
||||
#include "common-args.hh"
|
||||
#include "json.hh"
|
||||
|
||||
using namespace nix;
|
||||
|
||||
struct CmdListTarballs : MixJSON, InstallablesCommand
|
||||
{
|
||||
std::string description() override
|
||||
{
|
||||
return "list the 'fetchurl' calls made by the dependency graph of a package";
|
||||
}
|
||||
|
||||
Examples examples() override
|
||||
{
|
||||
return {
|
||||
Example{
|
||||
"To get the tarballs required to build GNU Hello and its dependencies:",
|
||||
"nix list-tarballs nixpkgs.hello"
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
struct File
|
||||
{
|
||||
std::string type;
|
||||
bool recursive;
|
||||
Hash hash;
|
||||
std::string url;
|
||||
StorePath storePath;
|
||||
};
|
||||
|
||||
void doIt(ref<Store> store, std::function<void(const File &)> callback)
|
||||
{
|
||||
settings.readOnlyMode = true;
|
||||
|
||||
auto state = getEvalState();
|
||||
auto autoArgs = getAutoArgs(*state);
|
||||
|
||||
StorePathSet done;
|
||||
|
||||
state->derivationHook =
|
||||
[&](const StorePath & drvPath, const Derivation & drv) {
|
||||
if (drv.outputs.size() != 1) return;
|
||||
|
||||
auto & output = *drv.outputs.begin();
|
||||
|
||||
if (output.second.hashAlgo.empty() || output.second.hash.empty()) return;
|
||||
|
||||
if (!done.insert(output.second.path.clone()).second) return;
|
||||
|
||||
auto [recursive, hash] = output.second.parseHashInfo();
|
||||
|
||||
if (recursive) return; // FIXME
|
||||
|
||||
std::optional<std::string> url;
|
||||
|
||||
auto i = drv.env.find("url");
|
||||
if (i != drv.env.end())
|
||||
url = i->second;
|
||||
else {
|
||||
i = drv.env.find("urls");
|
||||
if (i == drv.env.end()) return;
|
||||
auto urls = tokenizeString<std::vector<std::string>>(i->second, " ");
|
||||
if (urls.empty()) return;
|
||||
url = urls[0];
|
||||
}
|
||||
|
||||
File file {
|
||||
.type = drv.builder == "builtin:fetchurl" ? "fetchurl" : "unknown",
|
||||
.recursive = recursive,
|
||||
.hash = hash,
|
||||
.url = *url,
|
||||
.storePath = output.second.path.clone()
|
||||
};
|
||||
|
||||
callback(file);
|
||||
};
|
||||
|
||||
std::function<void(Value * v)> findDerivations;
|
||||
|
||||
findDerivations =
|
||||
[&](Value * v) {
|
||||
state->forceValue(*v);
|
||||
if (v->type == tAttrs) {
|
||||
if (state->isDerivation(*v)) {
|
||||
auto aDrvPath = v->attrs->get(state->sDrvPath);
|
||||
if (!aDrvPath) return;
|
||||
try {
|
||||
state->forceValue(*aDrvPath->value, *aDrvPath->pos);
|
||||
} catch (EvalError & e) {
|
||||
}
|
||||
} else {
|
||||
std::unordered_set<Value *> vs;
|
||||
for (auto & attr : *v->attrs) {
|
||||
if (!vs.insert(attr.value).second) continue;
|
||||
findDerivations(attr.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (auto & installable : installables) {
|
||||
auto v = state->allocValue();
|
||||
state->autoCallFunction(autoArgs, installable->toValue(*state), v);
|
||||
findDerivations(v);
|
||||
}
|
||||
}
|
||||
|
||||
void run(ref<Store> store) override
|
||||
{
|
||||
if (json) {
|
||||
JSONList json(std::cout);
|
||||
doIt(store,
|
||||
[&](const File & file) {
|
||||
auto obj = json.object();
|
||||
obj.attr("type", file.type);
|
||||
if (file.recursive)
|
||||
obj.attr("recursive", true);
|
||||
obj.attr("hash", file.hash.to_string(SRI));
|
||||
obj.attr("url", file.url);
|
||||
obj.attr("storePath", store->printStorePath(file.storePath));
|
||||
});
|
||||
} else {
|
||||
doIt(store,
|
||||
[&](const File & file) {
|
||||
std::cout << file.url << "\n";
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static auto r1 = registerCommand<CmdListTarballs>("list-tarballs");
|
||||
@@ -132,7 +132,6 @@ void mainWrapped(int argc, char * * argv)
|
||||
}
|
||||
|
||||
initNix();
|
||||
initGC();
|
||||
|
||||
programPath = argv[0];
|
||||
auto programName = std::string(baseNameOf(programPath));
|
||||
@@ -178,6 +177,11 @@ void mainWrapped(int argc, char * * argv)
|
||||
|
||||
args.command->prepare();
|
||||
args.command->run();
|
||||
|
||||
auto c = dynamic_cast<SourceExprCommand *>(&*args.command);
|
||||
if (c && c->evalState) {
|
||||
c->evalState->printStats();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -46,13 +46,13 @@ struct NixRepl
|
||||
{
|
||||
string curDir;
|
||||
EvalState state;
|
||||
Bindings * autoArgs;
|
||||
Ptr<Bindings> autoArgs;
|
||||
|
||||
Strings loadedFiles;
|
||||
|
||||
const static int envSize = 32768;
|
||||
StaticEnv staticEnv;
|
||||
Env * env;
|
||||
Ptr<Env> env;
|
||||
int displ;
|
||||
StringSet varNames;
|
||||
|
||||
@@ -352,11 +352,11 @@ StringSet NixRepl::completePrefix(string prefix)
|
||||
string cur2 = string(cur, dot + 1);
|
||||
|
||||
Expr * e = parseString(expr);
|
||||
Value v;
|
||||
Root<Value> v;
|
||||
e->eval(state, *env, v);
|
||||
state.forceAttrs(v);
|
||||
|
||||
for (auto & i : *v.attrs) {
|
||||
for (auto & i : *v->attrs) {
|
||||
string name = i.name;
|
||||
if (string(name, 0, cur2.size()) != cur2) continue;
|
||||
completions.insert(prev + expr + "." + name);
|
||||
@@ -453,7 +453,7 @@ bool NixRepl::processLine(string line)
|
||||
}
|
||||
|
||||
else if (command == ":a" || command == ":add") {
|
||||
Value v;
|
||||
Root<Value> v;
|
||||
evalString(arg, v);
|
||||
addAttrsToScope(v);
|
||||
}
|
||||
@@ -469,17 +469,17 @@ bool NixRepl::processLine(string line)
|
||||
}
|
||||
|
||||
else if (command == ":e" || command == ":edit") {
|
||||
Value v;
|
||||
Root<Value> v;
|
||||
evalString(arg, v);
|
||||
|
||||
Pos pos;
|
||||
|
||||
if (v.type == tPath || v.type == tString) {
|
||||
if (v->type == tPath || v->isString()) {
|
||||
PathSet context;
|
||||
auto filename = state.coerceToString(noPos, v, context);
|
||||
pos.file = state.symbols.create(filename);
|
||||
} else if (v.type == tLambda) {
|
||||
pos = v.lambda.fun->pos;
|
||||
} else if (v->type == tLambda) {
|
||||
pos = v->lambda.fun->pos;
|
||||
} else {
|
||||
// assume it's a derivation
|
||||
pos = findDerivationFilename(state, v, arg);
|
||||
@@ -497,12 +497,12 @@ bool NixRepl::processLine(string line)
|
||||
}
|
||||
|
||||
else if (command == ":t") {
|
||||
Value v;
|
||||
Root<Value> v;
|
||||
evalString(arg, v);
|
||||
std::cout << showType(v) << std::endl;
|
||||
|
||||
} else if (command == ":u") {
|
||||
Value v, f, result;
|
||||
Root<Value> v, f, result;
|
||||
evalString(arg, v);
|
||||
evalString("drv: (import <nixpkgs> {}).runCommand \"shell\" { buildInputs = [ drv ]; } \"\"", f);
|
||||
state.callFunction(f, v, result, Pos());
|
||||
@@ -512,7 +512,7 @@ bool NixRepl::processLine(string line)
|
||||
}
|
||||
|
||||
else if (command == ":b" || command == ":i" || command == ":s") {
|
||||
Value v;
|
||||
Root<Value> v;
|
||||
evalString(arg, v);
|
||||
Path drvPath = getDerivationPath(v);
|
||||
|
||||
@@ -534,7 +534,7 @@ bool NixRepl::processLine(string line)
|
||||
}
|
||||
|
||||
else if (command == ":p" || command == ":print") {
|
||||
Value v;
|
||||
Root<Value> v;
|
||||
evalString(arg, v);
|
||||
printValue(std::cout, v, 1000000000) << std::endl;
|
||||
}
|
||||
@@ -560,7 +560,7 @@ bool NixRepl::processLine(string line)
|
||||
v.thunk.expr = e;
|
||||
addVarToScope(state.symbols.create(name), v);
|
||||
} else {
|
||||
Value v;
|
||||
Root<Value> v;
|
||||
evalString(line, v);
|
||||
printValue(std::cout, v, 1) << std::endl;
|
||||
}
|
||||
@@ -574,17 +574,17 @@ void NixRepl::loadFile(const Path & path)
|
||||
{
|
||||
loadedFiles.remove(path);
|
||||
loadedFiles.push_back(path);
|
||||
Value v, v2;
|
||||
Root<Value> v, v2;
|
||||
state.evalFile(lookupFileArg(state, path), v);
|
||||
state.autoCallFunction(*autoArgs, v, v2);
|
||||
state.autoCallFunction(autoArgs, v, v2);
|
||||
addAttrsToScope(v2);
|
||||
}
|
||||
|
||||
|
||||
void NixRepl::initEnv()
|
||||
{
|
||||
env = &state.allocEnv(envSize);
|
||||
env->up = &state.baseEnv;
|
||||
env = state.allocEnv(envSize);
|
||||
env->up = state.baseEnv;
|
||||
displ = 0;
|
||||
staticEnv.vars.clear();
|
||||
|
||||
@@ -683,9 +683,11 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m
|
||||
str << ESC_CYA << (v.boolean ? "true" : "false") << ESC_END;
|
||||
break;
|
||||
|
||||
case tString:
|
||||
case tShortString:
|
||||
case tStaticString:
|
||||
case tLongString:
|
||||
str << ESC_YEL;
|
||||
printStringValue(str, v.string.s);
|
||||
printStringValue(str, v.getString());
|
||||
str << ESC_END;
|
||||
break;
|
||||
|
||||
@@ -742,6 +744,7 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m
|
||||
break;
|
||||
}
|
||||
|
||||
case tList0:
|
||||
case tList1:
|
||||
case tList2:
|
||||
case tListN:
|
||||
|
||||
@@ -120,7 +120,7 @@ struct CmdSearch : SourceExprCommand, MixJSON
|
||||
|
||||
if (v->type == tLambda && toplevel) {
|
||||
Value * v2 = state->allocValue();
|
||||
state->autoCallFunction(*state->allocBindings(1), *v, *v2);
|
||||
state->autoCallFunction(*Bindings::allocBindings(1), *v, *v2);
|
||||
v = v2;
|
||||
state->forceValue(*v);
|
||||
}
|
||||
@@ -226,12 +226,12 @@ struct CmdSearch : SourceExprCommand, MixJSON
|
||||
|
||||
warn("using cached results; pass '-u' to update the cache");
|
||||
|
||||
Value vRoot;
|
||||
Root<Value> vRoot;
|
||||
parseJSON(*state, readFile(jsonCacheFileName), vRoot);
|
||||
|
||||
fromCache = true;
|
||||
|
||||
doExpr(&vRoot, "", true, nullptr);
|
||||
doExpr(&*vRoot, "", true, nullptr);
|
||||
}
|
||||
|
||||
else {
|
||||
|
||||
@@ -144,7 +144,7 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand
|
||||
auto state = std::make_unique<EvalState>(Strings(), store);
|
||||
auto v = state->allocValue();
|
||||
state->eval(state->parseExprFromString(*res.data, "/no-such-path"), *v);
|
||||
Bindings & bindings(*state->allocBindings(0));
|
||||
Bindings & bindings(*Bindings::allocBindings(0));
|
||||
auto v2 = findAlongAttrPath(*state, settings.thisSystem, bindings, *v);
|
||||
|
||||
return store->parseStorePath(state->forceString(*v2));
|
||||
|
||||
@@ -36,6 +36,9 @@ export HAVE_SODIUM="@HAVE_SODIUM@"
|
||||
export version=@PACKAGE_VERSION@
|
||||
export system=@system@
|
||||
|
||||
# Make it more likely to trigger GC bugs.
|
||||
export GC_INITIAL_HEAP_SIZE=10
|
||||
|
||||
cacheDir=$TEST_ROOT/binary-cache
|
||||
|
||||
readLink() {
|
||||
|
||||
Reference in New Issue
Block a user