Compare commits
27 Commits
libgit2-25
...
nix-develo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82aa3ca2d7 | ||
|
|
d1f750a714 | ||
|
|
af1db7774f | ||
|
|
750306234d | ||
|
|
68a5110fb9 | ||
|
|
182ae393d1 | ||
|
|
060a354f22 | ||
|
|
496e43ec72 | ||
|
|
7a60f1429f | ||
|
|
65fbb4d975 | ||
|
|
070e8ee590 | ||
|
|
46b5d2e739 | ||
|
|
709a73e7ae | ||
|
|
accb564889 | ||
|
|
a786c9eedb | ||
|
|
3bf8c76072 | ||
|
|
8c113f80f3 | ||
|
|
0c53c88367 | ||
|
|
d6fc64ac38 | ||
|
|
c5f348db95 | ||
|
|
4f1c8f62c3 | ||
|
|
80b1d7b87a | ||
|
|
9c04c629e5 | ||
|
|
40f600644d | ||
|
|
bc6b9cef51 | ||
|
|
d4fd5c222d | ||
|
|
a38c7eb64e |
@@ -22,7 +22,15 @@ The store path info JSON format has been updated from version 1 to version 2:
|
||||
- New: `"ca": {"method": "nar", "hash": {"algorithm": "sha256", "format": "base64", "hash": "EMIJ+giQ..."}}`
|
||||
- Still `null` values for input-addressed store objects
|
||||
|
||||
Version 1 format is still accepted when reading for backward compatibility.
|
||||
- **Structured hash fields**:
|
||||
|
||||
Hash values (`narHash` and `downloadHash`) are now structured JSON objects instead of strings:
|
||||
|
||||
- Old: `"narHash": "sha256:FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc="`
|
||||
- New: `"narHash": {"algorithm": "sha256", "format": "base64", "hash": "FePFYIlM..."}`
|
||||
- Same structure applies to `downloadHash` in NAR info contexts
|
||||
|
||||
Nix currently only produces, and doesn't consume this format.
|
||||
|
||||
**Affected command**: `nix path-info --json`
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ $defs:
|
||||
Note: This field may not be present in all contexts, such as when the path is used as the key and the the store object info the value in map.
|
||||
|
||||
narHash:
|
||||
type: string
|
||||
"$ref": "./hash-v1.yaml"
|
||||
title: NAR Hash
|
||||
description: |
|
||||
Hash of the [file system object](@docroot@/store/file-system-object.md) part of the store object when serialized as a [Nix Archive](@docroot@/store/file-system-object/content-address.md#serial-nix-archive).
|
||||
@@ -229,7 +229,7 @@ $defs:
|
||||
> This is an impure "`.narinfo`" field that may not be included in certain contexts.
|
||||
|
||||
downloadHash:
|
||||
type: string
|
||||
"$ref": "./hash-v1.yaml"
|
||||
title: Download Hash
|
||||
description: |
|
||||
A digest for the compressed archive itself, as opposed to the data contained within.
|
||||
|
||||
@@ -74,14 +74,4 @@ scope: {
|
||||
buildPhase = lib.replaceStrings [ "--without-python" ] [ "" ] old.buildPhase;
|
||||
installPhase = lib.replaceStrings [ "--without-python" ] [ "" ] old.installPhase;
|
||||
});
|
||||
|
||||
libgit2 = pkgs.libgit2.overrideAttrs (attrs: {
|
||||
cmakeFlags = (attrs.cmakeFlags or [ ]) ++ [
|
||||
(lib.mesonBool "EXPERIMENTAL_SHA256" true)
|
||||
];
|
||||
|
||||
postInstall = (attrs.postInstall or "") + ''
|
||||
substituteInPlace $(find $dev/include -type f) --replace-quiet '#include "git2/' '#include "git2-experimental/'
|
||||
'';
|
||||
});
|
||||
}
|
||||
|
||||
@@ -104,6 +104,7 @@ MATCHER(IsAttrs, "")
|
||||
MATCHER_P(IsStringEq, s, fmt("The string is equal to \"%1%\"", s))
|
||||
{
|
||||
if (arg.type() != nString) {
|
||||
*result_listener << "Expected a string got " << arg.type();
|
||||
return false;
|
||||
}
|
||||
return arg.string_view() == s;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "nix/expr/tests/libexpr.hh"
|
||||
#include "nix/expr/value-to-json.hh"
|
||||
#include "nix/expr/static-string-data.hh"
|
||||
|
||||
namespace nix {
|
||||
// Testing the conversion to JSON
|
||||
@@ -54,7 +55,7 @@ TEST_F(JSONValueTest, IntNegative)
|
||||
TEST_F(JSONValueTest, String)
|
||||
{
|
||||
Value v;
|
||||
v.mkStringNoCopy("test");
|
||||
v.mkStringNoCopy("test"_sds);
|
||||
ASSERT_EQ(getJSONValue(v), "\"test\"");
|
||||
}
|
||||
|
||||
@@ -62,7 +63,7 @@ TEST_F(JSONValueTest, StringQuotes)
|
||||
{
|
||||
Value v;
|
||||
|
||||
v.mkStringNoCopy("test\"");
|
||||
v.mkStringNoCopy("test\""_sds);
|
||||
ASSERT_EQ(getJSONValue(v), "\"test\\\"\"");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "nix/expr/tests/libexpr.hh"
|
||||
#include "nix/expr/static-string-data.hh"
|
||||
|
||||
#include "nix/expr/value.hh"
|
||||
#include "nix/expr/print.hh"
|
||||
@@ -35,14 +36,14 @@ TEST_F(ValuePrintingTests, tBool)
|
||||
TEST_F(ValuePrintingTests, tString)
|
||||
{
|
||||
Value vString;
|
||||
vString.mkStringNoCopy("some-string");
|
||||
vString.mkStringNoCopy("some-string"_sds);
|
||||
test(vString, "\"some-string\"");
|
||||
}
|
||||
|
||||
TEST_F(ValuePrintingTests, tPath)
|
||||
{
|
||||
Value vPath;
|
||||
vPath.mkStringNoCopy("/foo");
|
||||
vPath.mkStringNoCopy("/foo"_sds);
|
||||
test(vPath, "\"/foo\"");
|
||||
}
|
||||
|
||||
@@ -289,10 +290,10 @@ TEST_F(StringPrintingTests, maxLengthTruncation)
|
||||
TEST_F(ValuePrintingTests, attrsTypeFirst)
|
||||
{
|
||||
Value vType;
|
||||
vType.mkStringNoCopy("puppy");
|
||||
vType.mkStringNoCopy("puppy"_sds);
|
||||
|
||||
Value vApple;
|
||||
vApple.mkStringNoCopy("apple");
|
||||
vApple.mkStringNoCopy("apple"_sds);
|
||||
|
||||
BindingsBuilder builder = state.buildBindings(10);
|
||||
builder.insert(state.symbols.create("type"), &vType);
|
||||
@@ -333,7 +334,7 @@ TEST_F(ValuePrintingTests, ansiColorsBool)
|
||||
TEST_F(ValuePrintingTests, ansiColorsString)
|
||||
{
|
||||
Value v;
|
||||
v.mkStringNoCopy("puppy");
|
||||
v.mkStringNoCopy("puppy"_sds);
|
||||
|
||||
test(v, ANSI_MAGENTA "\"puppy\"" ANSI_NORMAL, PrintOptions{.ansiColors = true});
|
||||
}
|
||||
@@ -341,7 +342,7 @@ TEST_F(ValuePrintingTests, ansiColorsString)
|
||||
TEST_F(ValuePrintingTests, ansiColorsStringElided)
|
||||
{
|
||||
Value v;
|
||||
v.mkStringNoCopy("puppy");
|
||||
v.mkStringNoCopy("puppy"_sds);
|
||||
|
||||
test(
|
||||
v,
|
||||
@@ -389,7 +390,7 @@ TEST_F(ValuePrintingTests, ansiColorsAttrs)
|
||||
TEST_F(ValuePrintingTests, ansiColorsDerivation)
|
||||
{
|
||||
Value vDerivation;
|
||||
vDerivation.mkStringNoCopy("derivation");
|
||||
vDerivation.mkStringNoCopy("derivation"_sds);
|
||||
|
||||
BindingsBuilder builder = state.buildBindings(10);
|
||||
builder.insert(state.s.type, &vDerivation);
|
||||
@@ -412,7 +413,7 @@ TEST_F(ValuePrintingTests, ansiColorsError)
|
||||
{
|
||||
Value throw_ = state.getBuiltin("throw");
|
||||
Value message;
|
||||
message.mkStringNoCopy("uh oh!");
|
||||
message.mkStringNoCopy("uh oh!"_sds);
|
||||
Value vError;
|
||||
vError.mkApp(&throw_, &message);
|
||||
|
||||
@@ -429,12 +430,12 @@ TEST_F(ValuePrintingTests, ansiColorsDerivationError)
|
||||
{
|
||||
Value throw_ = state.getBuiltin("throw");
|
||||
Value message;
|
||||
message.mkStringNoCopy("uh oh!");
|
||||
message.mkStringNoCopy("uh oh!"_sds);
|
||||
Value vError;
|
||||
vError.mkApp(&throw_, &message);
|
||||
|
||||
Value vDerivation;
|
||||
vDerivation.mkStringNoCopy("derivation");
|
||||
vDerivation.mkStringNoCopy("derivation"_sds);
|
||||
|
||||
BindingsBuilder builder = state.buildBindings(10);
|
||||
builder.insert(state.s.type, &vDerivation);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "nix/expr/value.hh"
|
||||
#include "nix/expr/static-string-data.hh"
|
||||
|
||||
#include "nix/store/tests/libstore.hh"
|
||||
#include <gtest/gtest.h>
|
||||
@@ -27,17 +28,17 @@ TEST_F(ValueTest, staticString)
|
||||
{
|
||||
Value vStr1;
|
||||
Value vStr2;
|
||||
vStr1.mkStringNoCopy("foo");
|
||||
vStr2.mkStringNoCopy("foo");
|
||||
vStr1.mkStringNoCopy("foo"_sds);
|
||||
vStr2.mkStringNoCopy("foo"_sds);
|
||||
|
||||
auto sd1 = vStr1.string_view();
|
||||
auto sd2 = vStr2.string_view();
|
||||
auto & sd1 = vStr1.string_data();
|
||||
auto & sd2 = vStr2.string_data();
|
||||
|
||||
// The strings should be the same
|
||||
ASSERT_EQ(sd1, sd2);
|
||||
ASSERT_EQ(sd1.view(), sd2.view());
|
||||
|
||||
// The strings should also be backed by the same (static) allocation
|
||||
ASSERT_EQ(sd1.data(), sd2.data());
|
||||
ASSERT_EQ(&sd1, &sd2);
|
||||
}
|
||||
|
||||
} // namespace nix
|
||||
|
||||
@@ -147,7 +147,7 @@ struct AttrDb
|
||||
for (auto * elem : *context) {
|
||||
if (!first)
|
||||
ctx.push_back(' ');
|
||||
ctx.append(elem);
|
||||
ctx.append(elem->view());
|
||||
first = false;
|
||||
}
|
||||
state->insertAttributeWithContext.use()(key.first)(symbols[key.second])(AttrType::String) (s) (ctx)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "nix/expr/primops.hh"
|
||||
#include "nix/expr/print-options.hh"
|
||||
#include "nix/expr/symbol-table.hh"
|
||||
#include "nix/expr/value.hh"
|
||||
#include "nix/util/exit.hh"
|
||||
#include "nix/util/types.hh"
|
||||
#include "nix/util/util.hh"
|
||||
@@ -28,6 +29,8 @@
|
||||
#include "parser-tab.hh"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <cstring>
|
||||
@@ -48,6 +51,9 @@ using json = nlohmann::json;
|
||||
|
||||
namespace nix {
|
||||
|
||||
/**
|
||||
* Just for doc strings. Not for regular string values.
|
||||
*/
|
||||
static char * allocString(size_t size)
|
||||
{
|
||||
char * t;
|
||||
@@ -61,6 +67,9 @@ static char * allocString(size_t size)
|
||||
// string allocations.
|
||||
// This function handles makeImmutableString(std::string_view()) by returning
|
||||
// the empty string.
|
||||
/**
|
||||
* Just for doc strings. Not for regular string values.
|
||||
*/
|
||||
static const char * makeImmutableString(std::string_view s)
|
||||
{
|
||||
const size_t size = s.size();
|
||||
@@ -72,6 +81,25 @@ static const char * makeImmutableString(std::string_view s)
|
||||
return t;
|
||||
}
|
||||
|
||||
StringData & StringData::alloc(size_t size)
|
||||
{
|
||||
void * t = GC_MALLOC_ATOMIC(sizeof(StringData) + size + 1);
|
||||
if (!t)
|
||||
throw std::bad_alloc();
|
||||
auto res = new (t) StringData(size);
|
||||
return *res;
|
||||
}
|
||||
|
||||
const StringData & StringData::make(std::string_view s)
|
||||
{
|
||||
if (s.empty())
|
||||
return ""_sds;
|
||||
auto & res = alloc(s.size());
|
||||
std::memcpy(&res.data_, s.data(), s.size());
|
||||
res.data_[s.size()] = '\0';
|
||||
return res;
|
||||
}
|
||||
|
||||
RootValue allocRootValue(Value * v)
|
||||
{
|
||||
return std::allocate_shared<Value *>(traceable_allocator<Value *>(), v);
|
||||
@@ -585,7 +613,9 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
|
||||
.name = name,
|
||||
.arity = 0, // FIXME: figure out how deep by syntax only? It's not semantically useful though...
|
||||
.args = {},
|
||||
.doc = makeImmutableString(s.view()), // NOTE: memory leak when compiled without GC
|
||||
/* N.B. Can't use StringData here, because that would lead to an interior pointer.
|
||||
NOTE: memory leak when compiled without GC. */
|
||||
.doc = makeImmutableString(s.view()),
|
||||
};
|
||||
}
|
||||
if (isFunctor(v)) {
|
||||
@@ -819,7 +849,7 @@ DebugTraceStacker::DebugTraceStacker(EvalState & evalState, DebugTrace t)
|
||||
|
||||
void Value::mkString(std::string_view s)
|
||||
{
|
||||
mkStringNoCopy(makeImmutableString(s));
|
||||
mkStringNoCopy(StringData::make(s));
|
||||
}
|
||||
|
||||
Value::StringWithContext::Context * Value::StringWithContext::Context::fromBuilder(const NixStringContext & context)
|
||||
@@ -829,23 +859,23 @@ Value::StringWithContext::Context * Value::StringWithContext::Context::fromBuild
|
||||
|
||||
auto ctx = new (allocBytes(sizeof(Context) + context.size() * sizeof(value_type))) Context(context.size());
|
||||
std::ranges::transform(
|
||||
context, ctx->elems, [](const NixStringContextElem & elt) { return makeImmutableString(elt.to_string()); });
|
||||
context, ctx->elems, [](const NixStringContextElem & elt) { return &StringData::make(elt.to_string()); });
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void Value::mkString(std::string_view s, const NixStringContext & context)
|
||||
{
|
||||
mkStringNoCopy(makeImmutableString(s), Value::StringWithContext::Context::fromBuilder(context));
|
||||
mkStringNoCopy(StringData::make(s), Value::StringWithContext::Context::fromBuilder(context));
|
||||
}
|
||||
|
||||
void Value::mkStringMove(const char * s, const NixStringContext & context)
|
||||
void Value::mkStringMove(const StringData & s, const NixStringContext & context)
|
||||
{
|
||||
mkStringNoCopy(s, Value::StringWithContext::Context::fromBuilder(context));
|
||||
}
|
||||
|
||||
void Value::mkPath(const SourcePath & path)
|
||||
{
|
||||
mkPath(&*path.accessor, makeImmutableString(path.path.abs()));
|
||||
mkPath(&*path.accessor, StringData::make(path.path.abs()));
|
||||
}
|
||||
|
||||
inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
|
||||
@@ -2099,21 +2129,21 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
|
||||
.atPos(pos)
|
||||
.withFrame(env, *this)
|
||||
.debugThrow();
|
||||
std::string result_str;
|
||||
result_str.reserve(sSize);
|
||||
std::string resultStr;
|
||||
resultStr.reserve(sSize);
|
||||
for (const auto & part : strings) {
|
||||
result_str += *part;
|
||||
resultStr += *part;
|
||||
}
|
||||
v.mkPath(state.rootPath(CanonPath(result_str)));
|
||||
v.mkPath(state.rootPath(CanonPath(resultStr)));
|
||||
} else {
|
||||
char * result_str = allocString(sSize + 1);
|
||||
char * tmp = result_str;
|
||||
auto & resultStr = StringData::alloc(sSize);
|
||||
auto * tmp = resultStr.data();
|
||||
for (const auto & part : strings) {
|
||||
memcpy(tmp, part->data(), part->size());
|
||||
std::memcpy(tmp, part->data(), part->size());
|
||||
tmp += part->size();
|
||||
}
|
||||
*tmp = 0;
|
||||
v.mkStringMove(result_str, context);
|
||||
*tmp = '\0';
|
||||
v.mkStringMove(resultStr, context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2288,7 +2318,7 @@ void copyContext(const Value & v, NixStringContext & context, const Experimental
|
||||
{
|
||||
if (auto * ctx = v.context())
|
||||
for (auto * elem : *ctx)
|
||||
context.insert(NixStringContextElem::parse(elem, xpSettings));
|
||||
context.insert(NixStringContextElem::parse(elem->view(), xpSettings));
|
||||
}
|
||||
|
||||
std::string_view EvalState::forceString(
|
||||
|
||||
@@ -31,6 +31,7 @@ headers = [ config_pub_h ] + files(
|
||||
'print.hh',
|
||||
'repl-exit-status.hh',
|
||||
'search-path.hh',
|
||||
'static-string-data.hh',
|
||||
'symbol-table.hh',
|
||||
'value-to-json.hh',
|
||||
'value-to-xml.hh',
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <map>
|
||||
#include <span>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <memory_resource>
|
||||
#include <algorithm>
|
||||
@@ -11,6 +12,7 @@
|
||||
#include "nix/expr/value.hh"
|
||||
#include "nix/expr/symbol-table.hh"
|
||||
#include "nix/expr/eval-error.hh"
|
||||
#include "nix/expr/static-string-data.hh"
|
||||
#include "nix/util/pos-idx.hh"
|
||||
#include "nix/expr/counter.hh"
|
||||
#include "nix/util/pos-table.hh"
|
||||
@@ -186,22 +188,18 @@ struct ExprString : Expr
|
||||
* This is only for strings already allocated in our polymorphic allocator,
|
||||
* or that live at least that long (e.g. c++ string literals)
|
||||
*/
|
||||
ExprString(const char * s)
|
||||
ExprString(const StringData & s)
|
||||
{
|
||||
v.mkStringNoCopy(s);
|
||||
};
|
||||
|
||||
ExprString(std::pmr::polymorphic_allocator<char> & alloc, std::string_view sv)
|
||||
{
|
||||
auto len = sv.length();
|
||||
if (len == 0) {
|
||||
v.mkStringNoCopy("");
|
||||
if (sv.size() == 0) {
|
||||
v.mkStringNoCopy(""_sds);
|
||||
return;
|
||||
}
|
||||
char * s = alloc.allocate(len + 1);
|
||||
sv.copy(s, len);
|
||||
s[len] = '\0';
|
||||
v.mkStringNoCopy(s);
|
||||
v.mkStringNoCopy(StringData::make(*alloc.resource(), sv));
|
||||
};
|
||||
|
||||
Value * maybeThunk(EvalState & state, Env & env) override;
|
||||
@@ -216,11 +214,7 @@ struct ExprPath : Expr
|
||||
ExprPath(std::pmr::polymorphic_allocator<char> & alloc, ref<SourceAccessor> accessor, std::string_view sv)
|
||||
: accessor(accessor)
|
||||
{
|
||||
auto len = sv.length();
|
||||
char * s = alloc.allocate(len + 1);
|
||||
sv.copy(s, len);
|
||||
s[len] = '\0';
|
||||
v.mkPath(&*accessor, s);
|
||||
v.mkPath(&*accessor, StringData::make(*alloc.resource(), sv));
|
||||
}
|
||||
|
||||
Value * maybeThunk(EvalState & state, Env & env) override;
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include <limits>
|
||||
|
||||
#include "nix/expr/eval.hh"
|
||||
#include "nix/expr/value.hh"
|
||||
#include "nix/expr/static-string-data.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
@@ -240,7 +242,7 @@ inline Expr *
|
||||
ParserState::stripIndentation(const PosIdx pos, std::vector<std::pair<PosIdx, std::variant<Expr *, StringToken>>> && es)
|
||||
{
|
||||
if (es.empty())
|
||||
return exprs.add<ExprString>("");
|
||||
return exprs.add<ExprString>(""_sds);
|
||||
|
||||
/* Figure out the minimum indentation. Note that by design
|
||||
whitespace-only final lines are not taken into account. (So
|
||||
@@ -332,7 +334,7 @@ ParserState::stripIndentation(const PosIdx pos, std::vector<std::pair<PosIdx, st
|
||||
// If there is nothing at all, return the empty string directly.
|
||||
// This also ensures that equivalent empty strings result in the same ast, which is helpful when testing formatters.
|
||||
if (es2.size() == 0) {
|
||||
auto * const result = exprs.add<ExprString>("");
|
||||
auto * const result = exprs.add<ExprString>(""_sds);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
44
src/libexpr/include/nix/expr/static-string-data.hh
Normal file
44
src/libexpr/include/nix/expr/static-string-data.hh
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
///@file
|
||||
|
||||
#include "nix/expr/value.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
template<size_t N>
|
||||
struct StringData::Static
|
||||
{
|
||||
/**
|
||||
* @note Must be first to make layout compatible with StringData.
|
||||
*/
|
||||
const size_t size = N - 1;
|
||||
char data[N];
|
||||
|
||||
consteval Static(const char (&str)[N])
|
||||
{
|
||||
static_assert(N > 0);
|
||||
if (str[size] != '\0')
|
||||
throw;
|
||||
std::copy_n(str, N, data);
|
||||
}
|
||||
|
||||
operator const StringData &() const &
|
||||
{
|
||||
static_assert(sizeof(decltype(*this)) >= sizeof(StringData));
|
||||
static_assert(alignof(decltype(*this)) == alignof(StringData));
|
||||
/* NOTE: This cast is somewhat on the fence of what's legal in C++.
|
||||
The question boils down to whether flexible array members are
|
||||
layout compatible with fixed-size arrays. This is a gray area, since
|
||||
FAMs are not standard anyway.
|
||||
*/
|
||||
return *reinterpret_cast<const StringData *>(this);
|
||||
}
|
||||
};
|
||||
|
||||
template<StringData::Static S>
|
||||
const StringData & operator""_sds()
|
||||
{
|
||||
return S;
|
||||
}
|
||||
|
||||
} // namespace nix
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <memory_resource>
|
||||
#include "nix/expr/value.hh"
|
||||
#include "nix/expr/static-string-data.hh"
|
||||
#include "nix/util/chunked-vector.hh"
|
||||
#include "nix/util/error.hh"
|
||||
|
||||
@@ -16,7 +17,6 @@ class SymbolValue : protected Value
|
||||
friend class SymbolStr;
|
||||
friend class SymbolTable;
|
||||
|
||||
uint32_t size_;
|
||||
uint32_t idx;
|
||||
|
||||
SymbolValue() = default;
|
||||
@@ -24,7 +24,7 @@ class SymbolValue : protected Value
|
||||
public:
|
||||
operator std::string_view() const noexcept
|
||||
{
|
||||
return {c_str(), size_};
|
||||
return string_view();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -96,13 +96,13 @@ class SymbolStr
|
||||
SymbolValueStore & store;
|
||||
std::string_view s;
|
||||
std::size_t hash;
|
||||
std::pmr::polymorphic_allocator<char> & alloc;
|
||||
std::pmr::memory_resource & resource;
|
||||
|
||||
Key(SymbolValueStore & store, std::string_view s, std::pmr::polymorphic_allocator<char> & stringAlloc)
|
||||
Key(SymbolValueStore & store, std::string_view s, std::pmr::memory_resource & stringMemory)
|
||||
: store(store)
|
||||
, s(s)
|
||||
, hash(HashType{}(s))
|
||||
, alloc(stringAlloc)
|
||||
, resource(stringMemory)
|
||||
{
|
||||
}
|
||||
};
|
||||
@@ -122,14 +122,10 @@ public:
|
||||
// for multi-threaded implementations: lock store and allocator here
|
||||
const auto & [v, idx] = key.store.add(SymbolValue{});
|
||||
if (size == 0) {
|
||||
v.mkStringNoCopy("", nullptr);
|
||||
v.mkStringNoCopy(""_sds, nullptr);
|
||||
} else {
|
||||
auto s = key.alloc.allocate(size + 1);
|
||||
memcpy(s, key.s.data(), size);
|
||||
s[size] = '\0';
|
||||
v.mkStringNoCopy(s, nullptr);
|
||||
v.mkStringNoCopy(StringData::make(key.resource, key.s));
|
||||
}
|
||||
v.size_ = size;
|
||||
v.idx = idx;
|
||||
this->s = &v;
|
||||
}
|
||||
@@ -139,6 +135,12 @@ public:
|
||||
return *s == s2;
|
||||
}
|
||||
|
||||
[[gnu::always_inline]]
|
||||
const StringData & string_data() const noexcept
|
||||
{
|
||||
return s->string_data();
|
||||
}
|
||||
|
||||
[[gnu::always_inline]]
|
||||
const char * c_str() const noexcept
|
||||
{
|
||||
@@ -155,13 +157,17 @@ public:
|
||||
[[gnu::always_inline]]
|
||||
bool empty() const noexcept
|
||||
{
|
||||
return s->size_ == 0;
|
||||
auto * p = &s->string_data();
|
||||
// Save a dereference in the sentinel value case
|
||||
if (p == &""_sds)
|
||||
return true;
|
||||
return p->size() == 0;
|
||||
}
|
||||
|
||||
[[gnu::always_inline]]
|
||||
size_t size() const noexcept
|
||||
{
|
||||
return s->size_;
|
||||
return s->string_data().size();
|
||||
}
|
||||
|
||||
[[gnu::always_inline]]
|
||||
@@ -259,7 +265,6 @@ private:
|
||||
* During its lifetime the monotonic buffer holds all strings and nodes, if the symbol set is node based.
|
||||
*/
|
||||
std::pmr::monotonic_buffer_resource buffer;
|
||||
std::pmr::polymorphic_allocator<char> stringAlloc{&buffer};
|
||||
SymbolStr::SymbolValueStore store{16};
|
||||
|
||||
/**
|
||||
@@ -282,7 +287,7 @@ public:
|
||||
// Most symbols are looked up more than once, so we trade off insertion performance
|
||||
// for lookup performance.
|
||||
// FIXME: make this thread-safe.
|
||||
return Symbol(*symbols.insert(SymbolStr::Key{store, s, stringAlloc}).first);
|
||||
return Symbol(*symbols.insert(SymbolStr::Key{store, s, buffer}).first);
|
||||
}
|
||||
|
||||
std::vector<SymbolStr> resolve(const std::vector<Symbol> & symbols) const
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
#pragma once
|
||||
///@file
|
||||
|
||||
#include <bit>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <memory_resource>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <concepts>
|
||||
|
||||
@@ -186,6 +192,91 @@ public:
|
||||
friend struct Value;
|
||||
};
|
||||
|
||||
class StringData
|
||||
{
|
||||
public:
|
||||
using size_type = std::size_t;
|
||||
|
||||
size_type size_;
|
||||
char data_[];
|
||||
|
||||
/*
|
||||
* This in particular ensures that we cannot have a `StringData`
|
||||
* that we use by value, which is just what we want!
|
||||
*
|
||||
* Dynamically sized types aren't a thing in C++ and even flexible array
|
||||
* members are a language extension and beyond the realm of standard C++.
|
||||
* Technically, sizeof data_ member is 0 and the intended way to use flexible
|
||||
* array members is to allocate sizeof(StrindData) + count * sizeof(char) bytes
|
||||
* and the compiler will consider alignment restrictions for the FAM.
|
||||
*
|
||||
*/
|
||||
|
||||
StringData(StringData &&) = delete;
|
||||
StringData & operator=(StringData &&) = delete;
|
||||
StringData(const StringData &) = delete;
|
||||
StringData & operator=(const StringData &) = delete;
|
||||
~StringData() = default;
|
||||
|
||||
private:
|
||||
StringData() = delete;
|
||||
|
||||
explicit StringData(size_type size)
|
||||
: size_(size)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* Allocate StringData on the (possibly) GC-managed heap and copy
|
||||
* the contents of s to it.
|
||||
*/
|
||||
static const StringData & make(std::string_view s);
|
||||
|
||||
/**
|
||||
* Allocate StringData on the (possibly) GC-managed heap.
|
||||
* @param size Length of the string (without the NUL terminator).
|
||||
*/
|
||||
static StringData & alloc(size_t size);
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return size_;
|
||||
}
|
||||
|
||||
char * data() noexcept
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
|
||||
const char * data() const noexcept
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
|
||||
const char * c_str() const noexcept
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
|
||||
constexpr std::string_view view() const noexcept
|
||||
{
|
||||
return std::string_view(data_, size_);
|
||||
}
|
||||
|
||||
template<size_t N>
|
||||
struct Static;
|
||||
|
||||
static StringData & make(std::pmr::memory_resource & resource, std::string_view s)
|
||||
{
|
||||
auto & res =
|
||||
*new (resource.allocate(sizeof(StringData) + s.size() + 1, alignof(StringData))) StringData(s.size());
|
||||
std::memcpy(res.data_, s.data(), s.size());
|
||||
res.data_[s.size()] = '\0';
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
/**
|
||||
@@ -219,7 +310,7 @@ struct ValueBase
|
||||
*/
|
||||
struct StringWithContext
|
||||
{
|
||||
const char * c_str;
|
||||
const StringData * str;
|
||||
|
||||
/**
|
||||
* The type of the context itself.
|
||||
@@ -234,7 +325,7 @@ struct ValueBase
|
||||
*/
|
||||
struct Context
|
||||
{
|
||||
using value_type = const char *;
|
||||
using value_type = const StringData *;
|
||||
using size_type = std::size_t;
|
||||
using iterator = const value_type *;
|
||||
|
||||
@@ -285,7 +376,7 @@ struct ValueBase
|
||||
struct Path
|
||||
{
|
||||
SourceAccessor * accessor;
|
||||
const char * path;
|
||||
const StringData * path;
|
||||
};
|
||||
|
||||
struct Null
|
||||
@@ -646,13 +737,13 @@ protected:
|
||||
void getStorage(StringWithContext & string) const noexcept
|
||||
{
|
||||
string.context = untagPointer<decltype(string.context)>(payload[0]);
|
||||
string.c_str = std::bit_cast<const char *>(payload[1]);
|
||||
string.str = std::bit_cast<const StringData *>(payload[1]);
|
||||
}
|
||||
|
||||
void getStorage(Path & path) const noexcept
|
||||
{
|
||||
path.accessor = untagPointer<decltype(path.accessor)>(payload[0]);
|
||||
path.path = std::bit_cast<const char *>(payload[1]);
|
||||
path.path = std::bit_cast<const StringData *>(payload[1]);
|
||||
}
|
||||
|
||||
void setStorage(NixInt integer) noexcept
|
||||
@@ -697,7 +788,7 @@ protected:
|
||||
|
||||
void setStorage(StringWithContext string) noexcept
|
||||
{
|
||||
setUntaggablePayload<pdString>(string.context, string.c_str);
|
||||
setUntaggablePayload<pdString>(string.context, string.str);
|
||||
}
|
||||
|
||||
void setStorage(Path path) noexcept
|
||||
@@ -1050,22 +1141,22 @@ public:
|
||||
setStorage(b);
|
||||
}
|
||||
|
||||
void mkStringNoCopy(const char * s, const Value::StringWithContext::Context * context = nullptr) noexcept
|
||||
void mkStringNoCopy(const StringData & s, const Value::StringWithContext::Context * context = nullptr) noexcept
|
||||
{
|
||||
setStorage(StringWithContext{.c_str = s, .context = context});
|
||||
setStorage(StringWithContext{.str = &s, .context = context});
|
||||
}
|
||||
|
||||
void mkString(std::string_view s);
|
||||
|
||||
void mkString(std::string_view s, const NixStringContext & context);
|
||||
|
||||
void mkStringMove(const char * s, const NixStringContext & context);
|
||||
void mkStringMove(const StringData & s, const NixStringContext & context);
|
||||
|
||||
void mkPath(const SourcePath & path);
|
||||
|
||||
inline void mkPath(SourceAccessor * accessor, const char * path) noexcept
|
||||
inline void mkPath(SourceAccessor * accessor, const StringData & path) noexcept
|
||||
{
|
||||
setStorage(Path{.accessor = accessor, .path = path});
|
||||
setStorage(Path{.accessor = accessor, .path = &path});
|
||||
}
|
||||
|
||||
inline void mkNull() noexcept
|
||||
@@ -1163,17 +1254,23 @@ public:
|
||||
|
||||
SourcePath path() const
|
||||
{
|
||||
return SourcePath(ref(pathAccessor()->shared_from_this()), CanonPath(CanonPath::unchecked_t(), pathStr()));
|
||||
return SourcePath(
|
||||
ref(pathAccessor()->shared_from_this()), CanonPath(CanonPath::unchecked_t(), std::string(pathStrView())));
|
||||
}
|
||||
|
||||
std::string_view string_view() const noexcept
|
||||
const StringData & string_data() const noexcept
|
||||
{
|
||||
return std::string_view{getStorage<StringWithContext>().c_str};
|
||||
return *getStorage<StringWithContext>().str;
|
||||
}
|
||||
|
||||
const char * c_str() const noexcept
|
||||
{
|
||||
return getStorage<StringWithContext>().c_str;
|
||||
return getStorage<StringWithContext>().str->data();
|
||||
}
|
||||
|
||||
std::string_view string_view() const noexcept
|
||||
{
|
||||
return string_data().view();
|
||||
}
|
||||
|
||||
const Value::StringWithContext::Context * context() const noexcept
|
||||
@@ -1233,12 +1330,12 @@ public:
|
||||
|
||||
const char * pathStr() const noexcept
|
||||
{
|
||||
return getStorage<Path>().path;
|
||||
return getStorage<Path>().path->c_str();
|
||||
}
|
||||
|
||||
std::string_view pathStrView() const noexcept
|
||||
{
|
||||
return std::string_view{getStorage<Path>().path};
|
||||
return getStorage<Path>().path->view();
|
||||
}
|
||||
|
||||
SourceAccessor * pathAccessor() const noexcept
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "nix/expr/eval-settings.hh"
|
||||
#include "nix/expr/gc-small-vector.hh"
|
||||
#include "nix/expr/json-to-value.hh"
|
||||
#include "nix/expr/static-string-data.hh"
|
||||
#include "nix/store/globals.hh"
|
||||
#include "nix/store/names.hh"
|
||||
#include "nix/store/path-references.hh"
|
||||
@@ -487,34 +488,34 @@ static void prim_typeOf(EvalState & state, const PosIdx pos, Value ** args, Valu
|
||||
state.forceValue(*args[0], pos);
|
||||
switch (args[0]->type()) {
|
||||
case nInt:
|
||||
v.mkStringNoCopy("int");
|
||||
v.mkStringNoCopy("int"_sds);
|
||||
break;
|
||||
case nBool:
|
||||
v.mkStringNoCopy("bool");
|
||||
v.mkStringNoCopy("bool"_sds);
|
||||
break;
|
||||
case nString:
|
||||
v.mkStringNoCopy("string");
|
||||
v.mkStringNoCopy("string"_sds);
|
||||
break;
|
||||
case nPath:
|
||||
v.mkStringNoCopy("path");
|
||||
v.mkStringNoCopy("path"_sds);
|
||||
break;
|
||||
case nNull:
|
||||
v.mkStringNoCopy("null");
|
||||
v.mkStringNoCopy("null"_sds);
|
||||
break;
|
||||
case nAttrs:
|
||||
v.mkStringNoCopy("set");
|
||||
v.mkStringNoCopy("set"_sds);
|
||||
break;
|
||||
case nList:
|
||||
v.mkStringNoCopy("list");
|
||||
v.mkStringNoCopy("list"_sds);
|
||||
break;
|
||||
case nFunction:
|
||||
v.mkStringNoCopy("lambda");
|
||||
v.mkStringNoCopy("lambda"_sds);
|
||||
break;
|
||||
case nExternal:
|
||||
v.mkString(args[0]->external()->typeOf());
|
||||
break;
|
||||
case nFloat:
|
||||
v.mkStringNoCopy("float");
|
||||
v.mkStringNoCopy("float"_sds);
|
||||
break;
|
||||
case nThunk:
|
||||
unreachable();
|
||||
@@ -2024,9 +2025,9 @@ static void prim_dirOf(EvalState & state, const PosIdx pos, Value ** args, Value
|
||||
pos, *args[0], context, "while evaluating the first argument passed to 'builtins.dirOf'", false, false);
|
||||
auto pos = path->rfind('/');
|
||||
if (pos == path->npos)
|
||||
v.mkStringMove(".", context);
|
||||
v.mkStringMove("."_sds, context);
|
||||
else if (pos == 0)
|
||||
v.mkStringMove("/", context);
|
||||
v.mkStringMove("/"_sds, context);
|
||||
else
|
||||
v.mkString(path->substr(0, pos), context);
|
||||
}
|
||||
@@ -2309,10 +2310,10 @@ static const Value & fileTypeToString(EvalState & state, SourceAccessor::Type ty
|
||||
|
||||
static const Constants stringValues = []() {
|
||||
Constants res;
|
||||
res.regular.mkStringNoCopy("regular");
|
||||
res.directory.mkStringNoCopy("directory");
|
||||
res.symlink.mkStringNoCopy("symlink");
|
||||
res.unknown.mkStringNoCopy("unknown");
|
||||
res.regular.mkStringNoCopy("regular"_sds);
|
||||
res.directory.mkStringNoCopy("directory"_sds);
|
||||
res.symlink.mkStringNoCopy("symlink"_sds);
|
||||
res.unknown.mkStringNoCopy("unknown"_sds);
|
||||
return res;
|
||||
}();
|
||||
|
||||
@@ -4463,7 +4464,7 @@ static void prim_substring(EvalState & state, const PosIdx pos, Value ** args, V
|
||||
if (len == 0) {
|
||||
state.forceValue(*args[2], pos);
|
||||
if (args[2]->type() == nString) {
|
||||
v.mkStringNoCopy("", args[2]->context());
|
||||
v.mkStringNoCopy(""_sds, args[2]->context());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "nix/expr/primops.hh"
|
||||
#include "nix/expr/eval-inline.hh"
|
||||
#include "nix/expr/static-string-data.hh"
|
||||
|
||||
#include "expr-config-private.hh"
|
||||
|
||||
@@ -136,7 +137,7 @@ static void prim_fromTOML(EvalState & state, const PosIdx pos, Value ** args, Va
|
||||
normalizeDatetimeFormat(t);
|
||||
#endif
|
||||
auto attrs = state.buildBindings(2);
|
||||
attrs.alloc("_type").mkStringNoCopy("timestamp");
|
||||
attrs.alloc("_type").mkStringNoCopy("timestamp"_sds);
|
||||
std::ostringstream s;
|
||||
s << t;
|
||||
auto str = s.view();
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
#include "nix/fetchers/git-utils.hh"
|
||||
#include "nix/util/file-system.hh"
|
||||
#include <gmock/gmock.h>
|
||||
#include <git2-experimental/global.h>
|
||||
#include <git2-experimental/repository.h>
|
||||
#include <git2-experimental/signature.h>
|
||||
#include <git2-experimental/types.h>
|
||||
#include <git2-experimental/object.h>
|
||||
#include <git2-experimental/tag.h>
|
||||
#include <git2/global.h>
|
||||
#include <git2/repository.h>
|
||||
#include <git2/signature.h>
|
||||
#include <git2/types.h>
|
||||
#include <git2/object.h>
|
||||
#include <git2/tag.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include "nix/util/fs-sink.hh"
|
||||
#include "nix/util/serialise.hh"
|
||||
#include "nix/fetchers/git-lfs-fetch.hh"
|
||||
|
||||
#include <git2-experimental/blob.h>
|
||||
#include <git2-experimental/tree.h>
|
||||
#include <git2/blob.h>
|
||||
#include <git2/tree.h>
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "nix/fetchers/fetchers.hh"
|
||||
#include "nix/fetchers/git-utils.hh"
|
||||
|
||||
#include <git2-experimental.h>
|
||||
#include <git2.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
61
src/libfetchers-tests/input.cc
Normal file
61
src/libfetchers-tests/input.cc
Normal file
@@ -0,0 +1,61 @@
|
||||
#include "nix/fetchers/fetch-settings.hh"
|
||||
#include "nix/fetchers/attrs.hh"
|
||||
#include "nix/fetchers/fetchers.hh"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace nix {
|
||||
|
||||
using fetchers::Attr;
|
||||
|
||||
struct InputFromAttrsTestCase
|
||||
{
|
||||
fetchers::Attrs attrs;
|
||||
std::string expectedUrl;
|
||||
std::string description;
|
||||
fetchers::Attrs expectedAttrs = attrs;
|
||||
};
|
||||
|
||||
class InputFromAttrsTest : public ::testing::WithParamInterface<InputFromAttrsTestCase>, public ::testing::Test
|
||||
{};
|
||||
|
||||
TEST_P(InputFromAttrsTest, attrsAreCorrectAndRoundTrips)
|
||||
{
|
||||
fetchers::Settings fetchSettings;
|
||||
|
||||
const auto & testCase = GetParam();
|
||||
|
||||
auto input = fetchers::Input::fromAttrs(fetchSettings, fetchers::Attrs(testCase.attrs));
|
||||
|
||||
EXPECT_EQ(input.toAttrs(), testCase.expectedAttrs);
|
||||
EXPECT_EQ(input.toURLString(), testCase.expectedUrl);
|
||||
|
||||
auto input2 = fetchers::Input::fromAttrs(fetchSettings, input.toAttrs());
|
||||
EXPECT_EQ(input, input2);
|
||||
EXPECT_EQ(input.toAttrs(), input2.toAttrs());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
InputFromAttrs,
|
||||
InputFromAttrsTest,
|
||||
::testing::Values(
|
||||
// Test for issue #14429.
|
||||
InputFromAttrsTestCase{
|
||||
.attrs =
|
||||
{
|
||||
{"url", Attr("git+ssh://git@github.com/NixOS/nixpkgs")},
|
||||
{"type", Attr("git")},
|
||||
},
|
||||
.expectedUrl = "git+ssh://git@github.com/NixOS/nixpkgs",
|
||||
.description = "strips_git_plus_prefix",
|
||||
.expectedAttrs =
|
||||
{
|
||||
{"url", Attr("ssh://git@github.com/NixOS/nixpkgs")},
|
||||
{"type", Attr("git")},
|
||||
},
|
||||
}),
|
||||
[](const ::testing::TestParamInfo<InputFromAttrsTestCase> & info) { return info.param.description; });
|
||||
|
||||
} // namespace nix
|
||||
@@ -33,7 +33,7 @@ deps_private += rapidcheck
|
||||
gtest = dependency('gtest', main : true)
|
||||
deps_private += gtest
|
||||
|
||||
libgit2 = dependency('libgit2-experimental')
|
||||
libgit2 = dependency('libgit2')
|
||||
deps_private += libgit2
|
||||
|
||||
subdir('nix-meson-build-support/common')
|
||||
@@ -42,6 +42,7 @@ sources = files(
|
||||
'access-tokens.cc',
|
||||
'git-utils.cc',
|
||||
'git.cc',
|
||||
'input.cc',
|
||||
'nix_api_fetchers.cc',
|
||||
'public-key.cc',
|
||||
)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "nix/fetchers/attrs.hh"
|
||||
#include "nix/fetchers/fetchers.hh"
|
||||
#include "nix/fetchers/git-utils.hh"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
@@ -112,7 +111,7 @@ StringMap attrsToQuery(const Attrs & attrs)
|
||||
|
||||
Hash getRevAttr(const Attrs & attrs, const std::string & name)
|
||||
{
|
||||
return parseGitHash(getStrAttr(attrs, name));
|
||||
return Hash::parseAny(getStrAttr(attrs, name), HashAlgorithm::SHA1);
|
||||
}
|
||||
|
||||
} // namespace nix::fetchers
|
||||
|
||||
@@ -447,9 +447,6 @@ std::optional<Hash> Input::getRev() const
|
||||
} catch (BadHash & e) {
|
||||
// Default to sha1 for backwards compatibility with existing
|
||||
// usages (e.g. `builtins.fetchTree` calls or flake inputs).
|
||||
//
|
||||
// Note that means that for SHA-256 git repos, prefixing
|
||||
// must be used.
|
||||
hash = Hash::parseAny(*s, HashAlgorithm::SHA1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
#include "nix/util/hash.hh"
|
||||
#include "nix/store/ssh.hh"
|
||||
|
||||
#include <git2-experimental/attr.h>
|
||||
#include <git2-experimental/config.h>
|
||||
#include <git2-experimental/errors.h>
|
||||
#include <git2-experimental/remote.h>
|
||||
#include <git2/attr.h>
|
||||
#include <git2/config.h>
|
||||
#include <git2/errors.h>
|
||||
#include <git2/remote.h>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
|
||||
@@ -13,27 +13,27 @@
|
||||
#include "nix/util/thread-pool.hh"
|
||||
#include "nix/util/pool.hh"
|
||||
|
||||
#include <git2-experimental/attr.h>
|
||||
#include <git2-experimental/blob.h>
|
||||
#include <git2-experimental/branch.h>
|
||||
#include <git2-experimental/commit.h>
|
||||
#include <git2-experimental/config.h>
|
||||
#include <git2-experimental/describe.h>
|
||||
#include <git2-experimental/errors.h>
|
||||
#include <git2-experimental/global.h>
|
||||
#include <git2-experimental/indexer.h>
|
||||
#include <git2-experimental/object.h>
|
||||
#include <git2-experimental/odb.h>
|
||||
#include <git2-experimental/refs.h>
|
||||
#include <git2-experimental/remote.h>
|
||||
#include <git2-experimental/repository.h>
|
||||
#include <git2-experimental/revparse.h>
|
||||
#include <git2-experimental/status.h>
|
||||
#include <git2-experimental/submodule.h>
|
||||
#include <git2-experimental/sys/odb_backend.h>
|
||||
#include <git2-experimental/sys/mempack.h>
|
||||
#include <git2-experimental/tag.h>
|
||||
#include <git2-experimental/tree.h>
|
||||
#include <git2/attr.h>
|
||||
#include <git2/blob.h>
|
||||
#include <git2/branch.h>
|
||||
#include <git2/commit.h>
|
||||
#include <git2/config.h>
|
||||
#include <git2/describe.h>
|
||||
#include <git2/errors.h>
|
||||
#include <git2/global.h>
|
||||
#include <git2/indexer.h>
|
||||
#include <git2/object.h>
|
||||
#include <git2/odb.h>
|
||||
#include <git2/refs.h>
|
||||
#include <git2/remote.h>
|
||||
#include <git2/repository.h>
|
||||
#include <git2/revparse.h>
|
||||
#include <git2/status.h>
|
||||
#include <git2/submodule.h>
|
||||
#include <git2/sys/odb_backend.h>
|
||||
#include <git2/sys/mempack.h>
|
||||
#include <git2/tag.h>
|
||||
#include <git2/tree.h>
|
||||
|
||||
#include <boost/unordered/concurrent_flat_set.hpp>
|
||||
#include <boost/unordered/unordered_flat_map.hpp>
|
||||
@@ -91,21 +91,10 @@ typedef std::unique_ptr<git_indexer, Deleter<git_indexer_free>> Indexer;
|
||||
|
||||
Hash toHash(const git_oid & oid)
|
||||
{
|
||||
HashAlgorithm algo;
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wswitch-enum"
|
||||
switch (oid.type) {
|
||||
case GIT_OID_SHA1:
|
||||
algo = HashAlgorithm::SHA1;
|
||||
break;
|
||||
case GIT_OID_SHA256:
|
||||
algo = HashAlgorithm::SHA256;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
#pragma GCC diagnostic pop
|
||||
Hash hash(algo);
|
||||
#ifdef GIT_EXPERIMENTAL_SHA256
|
||||
assert(oid.type == GIT_OID_SHA1);
|
||||
#endif
|
||||
Hash hash(HashAlgorithm::SHA1);
|
||||
memcpy(hash.hash, oid.id, hash.hashSize);
|
||||
return hash;
|
||||
}
|
||||
@@ -122,21 +111,7 @@ static void initLibGit2()
|
||||
git_oid hashToOID(const Hash & hash)
|
||||
{
|
||||
git_oid oid;
|
||||
git_oid_t t;
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wswitch-enum"
|
||||
switch (hash.algo) {
|
||||
case HashAlgorithm::SHA1:
|
||||
t = GIT_OID_SHA1;
|
||||
break;
|
||||
case HashAlgorithm::SHA256:
|
||||
t = GIT_OID_SHA256;
|
||||
break;
|
||||
default:
|
||||
throw Error("unsupported hash algorithm for Git: %s", printHashAlgo(hash.algo));
|
||||
}
|
||||
#pragma GCC diagnostic pop
|
||||
if (git_oid_fromstr(&oid, hash.gitRev().c_str(), t))
|
||||
if (git_oid_fromstr(&oid, hash.gitRev().c_str()))
|
||||
throw Error("cannot convert '%s' to a Git OID", hash.gitRev());
|
||||
return oid;
|
||||
}
|
||||
@@ -329,8 +304,7 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
|
||||
// (synchronously on the git_packbuilder_write_buf thread)
|
||||
Indexer indexer;
|
||||
git_indexer_progress stats;
|
||||
git_indexer_options indexer_opts = GIT_INDEXER_OPTIONS_INIT;
|
||||
if (git_indexer_new(Setter(indexer), pack_dir_path.c_str(), &indexer_opts))
|
||||
if (git_indexer_new(Setter(indexer), pack_dir_path.c_str(), 0, nullptr, nullptr))
|
||||
throw Error("creating git packfile indexer: %s", git_error_last()->message);
|
||||
|
||||
// TODO: provide index callback for checkInterrupt() termination
|
||||
@@ -1354,13 +1328,18 @@ std::vector<std::tuple<GitRepoImpl::Submodule, Hash>> GitRepoImpl::getSubmodules
|
||||
return result;
|
||||
}
|
||||
|
||||
ref<GitRepo> getTarballCache()
|
||||
{
|
||||
static auto repoDir = std::filesystem::path(getCacheDir()) / "tarball-cache";
|
||||
namespace fetchers {
|
||||
|
||||
return GitRepo::openRepo(repoDir, true, true);
|
||||
ref<GitRepo> Settings::getTarballCache() const
|
||||
{
|
||||
auto tarballCache(_tarballCache.lock());
|
||||
if (!*tarballCache)
|
||||
*tarballCache = GitRepo::openRepo(std::filesystem::path(getCacheDir()) / "tarball-cache", true, true);
|
||||
return ref<GitRepo>(*tarballCache);
|
||||
}
|
||||
|
||||
} // namespace fetchers
|
||||
|
||||
GitRepo::WorkdirInfo GitRepo::getCachedWorkdirInfo(const std::filesystem::path & path)
|
||||
{
|
||||
static Sync<std::map<std::filesystem::path, WorkdirInfo>> _cache;
|
||||
@@ -1404,21 +1383,4 @@ bool isLegalRefName(const std::string & refName)
|
||||
return false;
|
||||
}
|
||||
|
||||
Hash parseGitHash(std::string_view hashStr)
|
||||
{
|
||||
HashAlgorithm algo;
|
||||
switch (hashStr.size()) {
|
||||
case 40:
|
||||
algo = HashAlgorithm::SHA1;
|
||||
break;
|
||||
case 64:
|
||||
algo = HashAlgorithm::SHA256;
|
||||
break;
|
||||
default:
|
||||
throw Error(
|
||||
"invalid git hash '%s': expected 40 (SHA1) or 64 (SHA256) hex characters, got %d", hashStr, hashStr.size());
|
||||
}
|
||||
return Hash::parseNonSRIUnprefixed(hashStr, algo);
|
||||
}
|
||||
|
||||
} // namespace nix
|
||||
|
||||
@@ -168,8 +168,6 @@ struct GitInputScheme : InputScheme
|
||||
return {};
|
||||
|
||||
auto url2(url);
|
||||
if (hasPrefix(url2.scheme, "git+"))
|
||||
url2.scheme = std::string(url2.scheme, 4);
|
||||
url2.query.clear();
|
||||
|
||||
Attrs attrs;
|
||||
|
||||
@@ -48,7 +48,7 @@ struct GitArchiveInputScheme : InputScheme
|
||||
auto size = path.size();
|
||||
if (size == 3) {
|
||||
if (std::regex_match(path[2], revRegex))
|
||||
rev = parseGitHash(path[2]);
|
||||
rev = Hash::parseAny(path[2], HashAlgorithm::SHA1);
|
||||
else if (isLegalRefName(path[2]))
|
||||
ref = path[2];
|
||||
else
|
||||
@@ -74,7 +74,7 @@ struct GitArchiveInputScheme : InputScheme
|
||||
if (name == "rev") {
|
||||
if (rev)
|
||||
throw BadURL("URL '%s' contains multiple commit hashes", url);
|
||||
rev = parseGitHash(value);
|
||||
rev = Hash::parseAny(value, HashAlgorithm::SHA1);
|
||||
} else if (name == "ref") {
|
||||
if (!isLegalRefName(value))
|
||||
throw BadURL("URL '%s' contains an invalid branch/tag name", url);
|
||||
@@ -270,7 +270,7 @@ struct GitArchiveInputScheme : InputScheme
|
||||
if (auto lastModifiedAttrs = cache->lookup(lastModifiedKey)) {
|
||||
auto treeHash = getRevAttr(*treeHashAttrs, "treeHash");
|
||||
auto lastModified = getIntAttr(*lastModifiedAttrs, "lastModified");
|
||||
if (getTarballCache()->hasObject(treeHash))
|
||||
if (input.settings->getTarballCache()->hasObject(treeHash))
|
||||
return {std::move(input), TarballInfo{.treeHash = treeHash, .lastModified = (time_t) lastModified}};
|
||||
else
|
||||
debug("Git tree with hash '%s' has disappeared from the cache, refetching...", treeHash.gitRev());
|
||||
@@ -290,7 +290,7 @@ struct GitArchiveInputScheme : InputScheme
|
||||
*logger, lvlInfo, actUnknown, fmt("unpacking '%s' into the Git cache", input.to_string()));
|
||||
|
||||
TarArchive archive{*source};
|
||||
auto tarballCache = getTarballCache();
|
||||
auto tarballCache = input.settings->getTarballCache();
|
||||
auto parseSink = tarballCache->getFileSystemObjectSink();
|
||||
auto lastModified = unpackTarfileToSink(archive, *parseSink);
|
||||
auto tree = parseSink->flush();
|
||||
@@ -324,7 +324,8 @@ struct GitArchiveInputScheme : InputScheme
|
||||
#endif
|
||||
input.attrs.insert_or_assign("lastModified", uint64_t(tarballInfo.lastModified));
|
||||
|
||||
auto accessor = getTarballCache()->getAccessor(tarballInfo.treeHash, false, "«" + input.to_string() + "»");
|
||||
auto accessor =
|
||||
input.settings->getTarballCache()->getAccessor(tarballInfo.treeHash, false, "«" + input.to_string() + "»");
|
||||
|
||||
return {accessor, input};
|
||||
}
|
||||
@@ -403,8 +404,8 @@ struct GitHubInputScheme : GitArchiveInputScheme
|
||||
store->requireStoreObjectAccessor(downloadResult.storePath)->readFile(CanonPath::root));
|
||||
|
||||
return RefInfo{
|
||||
.rev = parseGitHash(std::string{json["sha"]}),
|
||||
.treeHash = parseGitHash(std::string{json["commit"]["tree"]["sha"]})};
|
||||
.rev = Hash::parseAny(std::string{json["sha"]}, HashAlgorithm::SHA1),
|
||||
.treeHash = Hash::parseAny(std::string{json["commit"]["tree"]["sha"]}, HashAlgorithm::SHA1)};
|
||||
}
|
||||
|
||||
DownloadUrl getDownloadUrl(const Input & input) const override
|
||||
@@ -478,7 +479,7 @@ struct GitLabInputScheme : GitArchiveInputScheme
|
||||
store->requireStoreObjectAccessor(downloadResult.storePath)->readFile(CanonPath::root));
|
||||
|
||||
if (json.is_array() && json.size() >= 1 && json[0]["id"] != nullptr) {
|
||||
return RefInfo{.rev = parseGitHash(std::string(json[0]["id"]))};
|
||||
return RefInfo{.rev = Hash::parseAny(std::string(json[0]["id"]), HashAlgorithm::SHA1)};
|
||||
}
|
||||
if (json.is_array() && json.size() == 0) {
|
||||
throw Error("No commits returned by GitLab API -- does the git ref really exist?");
|
||||
@@ -579,7 +580,7 @@ struct SourceHutInputScheme : GitArchiveInputScheme
|
||||
if (!id)
|
||||
throw BadURL("in '%d', couldn't find ref '%d'", input.to_string(), ref);
|
||||
|
||||
return RefInfo{.rev = parseGitHash(*id)};
|
||||
return RefInfo{.rev = Hash::parseAny(*id, HashAlgorithm::SHA1)};
|
||||
}
|
||||
|
||||
DownloadUrl getDownloadUrl(const Input & input) const override
|
||||
|
||||
@@ -11,6 +11,12 @@
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace nix {
|
||||
|
||||
struct GitRepo;
|
||||
|
||||
}
|
||||
|
||||
namespace nix::fetchers {
|
||||
|
||||
struct Cache;
|
||||
@@ -125,8 +131,12 @@ struct Settings : public Config
|
||||
|
||||
ref<Cache> getCache() const;
|
||||
|
||||
ref<GitRepo> getTarballCache() const;
|
||||
|
||||
private:
|
||||
mutable Sync<std::shared_ptr<Cache>> _cache;
|
||||
|
||||
mutable Sync<std::shared_ptr<GitRepo>> _tarballCache;
|
||||
};
|
||||
|
||||
} // namespace nix::fetchers
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "nix/util/serialise.hh"
|
||||
#include "nix/util/url.hh"
|
||||
|
||||
#include <git2-experimental/repository.h>
|
||||
#include <git2/repository.h>
|
||||
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
|
||||
|
||||
@@ -120,8 +120,6 @@ struct GitRepo
|
||||
virtual Hash dereferenceSingletonDirectory(const Hash & oid) = 0;
|
||||
};
|
||||
|
||||
ref<GitRepo> getTarballCache();
|
||||
|
||||
// A helper to ensure that the `git_*_free` functions get called.
|
||||
template<auto del>
|
||||
struct Deleter
|
||||
@@ -167,14 +165,4 @@ struct Setter
|
||||
*/
|
||||
bool isLegalRefName(const std::string & refName);
|
||||
|
||||
/**
|
||||
* Parse a base16-encoded git hash string and determine the hash
|
||||
* algorithm based on the length (40 chars = SHA1, 64 chars = SHA256).
|
||||
*
|
||||
* @note For Nix-native information we should *not* do length tricks,
|
||||
* but instead always rely on an explicit algorithm. This hack should be
|
||||
* only for foreign hash literals.
|
||||
*/
|
||||
Hash parseGitHash(std::string_view hashStr);
|
||||
|
||||
} // namespace nix
|
||||
|
||||
@@ -23,7 +23,7 @@ struct IndirectInputScheme : InputScheme
|
||||
if (path.size() == 1) {
|
||||
} else if (path.size() == 2) {
|
||||
if (std::regex_match(path[1], revRegex))
|
||||
rev = parseGitHash(path[1]);
|
||||
rev = Hash::parseAny(path[1], HashAlgorithm::SHA1);
|
||||
else if (isLegalRefName(path[1]))
|
||||
ref = path[1];
|
||||
else
|
||||
@@ -34,7 +34,7 @@ struct IndirectInputScheme : InputScheme
|
||||
ref = path[1];
|
||||
if (!std::regex_match(path[2], revRegex))
|
||||
throw BadURL("in flake URL '%s', '%s' is not a commit hash", url, path[2]);
|
||||
rev = parseGitHash(path[2]);
|
||||
rev = Hash::parseAny(path[2], HashAlgorithm::SHA1);
|
||||
} else
|
||||
throw BadURL("GitHub URL '%s' is invalid", url);
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ subdir('nix-meson-build-support/subprojects')
|
||||
nlohmann_json = dependency('nlohmann_json', version : '>= 3.9')
|
||||
deps_public += nlohmann_json
|
||||
|
||||
libgit2 = dependency('libgit2-experimental', version : '>= 1.9')
|
||||
libgit2 = dependency('libgit2', version : '>= 1.9')
|
||||
deps_private += libgit2
|
||||
|
||||
subdir('nix-meson-build-support/common')
|
||||
|
||||
@@ -136,11 +136,11 @@ static DownloadTarballResult downloadTarball_(
|
||||
.treeHash = treeHash,
|
||||
.lastModified = (time_t) getIntAttr(infoAttrs, "lastModified"),
|
||||
.immutableUrl = maybeGetStrAttr(infoAttrs, "immutableUrl"),
|
||||
.accessor = getTarballCache()->getAccessor(treeHash, false, displayPrefix),
|
||||
.accessor = settings.getTarballCache()->getAccessor(treeHash, false, displayPrefix),
|
||||
};
|
||||
};
|
||||
|
||||
if (cached && !getTarballCache()->hasObject(getRevAttr(cached->value, "treeHash")))
|
||||
if (cached && !settings.getTarballCache()->hasObject(getRevAttr(cached->value, "treeHash")))
|
||||
cached.reset();
|
||||
|
||||
if (cached && !cached->expired)
|
||||
@@ -179,7 +179,7 @@ static DownloadTarballResult downloadTarball_(
|
||||
TarArchive{path};
|
||||
})
|
||||
: TarArchive{*source};
|
||||
auto tarballCache = getTarballCache();
|
||||
auto tarballCache = settings.getTarballCache();
|
||||
auto parseSink = tarballCache->getFileSystemObjectSink();
|
||||
auto lastModified = unpackTarfileToSink(archive, *parseSink);
|
||||
auto tree = parseSink->flush();
|
||||
@@ -398,7 +398,9 @@ struct TarballInputScheme : CurlInputScheme
|
||||
|
||||
input.attrs.insert_or_assign(
|
||||
"narHash",
|
||||
getTarballCache()->treeHashToNarHash(*input.settings, result.treeHash).to_string(HashFormat::SRI, true));
|
||||
input.settings->getTarballCache()
|
||||
->treeHashToNarHash(*input.settings, result.treeHash)
|
||||
.to_string(HashFormat::SRI, true));
|
||||
|
||||
return {result.accessor, input};
|
||||
}
|
||||
|
||||
@@ -467,8 +467,6 @@ public:
|
||||
|
||||
std::string getStatus(State & state)
|
||||
{
|
||||
auto MiB = 1024.0 * 1024.0;
|
||||
|
||||
std::string res;
|
||||
|
||||
auto renderActivity =
|
||||
@@ -516,6 +514,65 @@ public:
|
||||
return s;
|
||||
};
|
||||
|
||||
auto renderSizeActivity = [&](ActivityType type, const std::string & itemFmt = "%s") {
|
||||
auto & act = state.activitiesByType[type];
|
||||
uint64_t done = act.done, expected = act.done, running = 0, failed = act.failed;
|
||||
for (auto & j : act.its) {
|
||||
done += j.second->done;
|
||||
expected += j.second->expected;
|
||||
running += j.second->running;
|
||||
failed += j.second->failed;
|
||||
}
|
||||
|
||||
expected = std::max(expected, act.expected);
|
||||
|
||||
std::optional<SizeUnit> commonUnit;
|
||||
std::string s;
|
||||
|
||||
if (running || done || expected || failed) {
|
||||
if (running)
|
||||
if (expected != 0) {
|
||||
commonUnit = getCommonSizeUnit({(int64_t) running, (int64_t) done, (int64_t) expected});
|
||||
s =
|
||||
fmt(ANSI_BLUE "%s" ANSI_NORMAL "/" ANSI_GREEN "%s" ANSI_NORMAL "/%s",
|
||||
commonUnit ? renderSizeWithoutUnit(running, *commonUnit) : renderSize(running),
|
||||
commonUnit ? renderSizeWithoutUnit(done, *commonUnit) : renderSize(done),
|
||||
commonUnit ? renderSizeWithoutUnit(expected, *commonUnit) : renderSize(expected));
|
||||
} else {
|
||||
commonUnit = getCommonSizeUnit({(int64_t) running, (int64_t) done});
|
||||
s =
|
||||
fmt(ANSI_BLUE "%s" ANSI_NORMAL "/" ANSI_GREEN "%s" ANSI_NORMAL,
|
||||
commonUnit ? renderSizeWithoutUnit(running, *commonUnit) : renderSize(running),
|
||||
commonUnit ? renderSizeWithoutUnit(done, *commonUnit) : renderSize(done));
|
||||
}
|
||||
else if (expected != done)
|
||||
if (expected != 0) {
|
||||
commonUnit = getCommonSizeUnit({(int64_t) done, (int64_t) expected});
|
||||
s =
|
||||
fmt(ANSI_GREEN "%s" ANSI_NORMAL "/%s",
|
||||
commonUnit ? renderSizeWithoutUnit(done, *commonUnit) : renderSize(done),
|
||||
commonUnit ? renderSizeWithoutUnit(expected, *commonUnit) : renderSize(expected));
|
||||
} else {
|
||||
commonUnit = getSizeUnit(done);
|
||||
s = fmt(ANSI_GREEN "%s" ANSI_NORMAL, renderSizeWithoutUnit(done, *commonUnit));
|
||||
}
|
||||
else {
|
||||
commonUnit = getSizeUnit(done);
|
||||
s = fmt(done ? ANSI_GREEN "%s" ANSI_NORMAL : "%s", renderSizeWithoutUnit(done, *commonUnit));
|
||||
}
|
||||
|
||||
if (commonUnit)
|
||||
s = fmt("%s %siB", s, getSizeUnitSuffix(*commonUnit));
|
||||
|
||||
s = fmt(itemFmt, s);
|
||||
|
||||
if (failed)
|
||||
s += fmt(" (" ANSI_RED "%s failed" ANSI_NORMAL ")", renderSize(failed));
|
||||
}
|
||||
|
||||
return s;
|
||||
};
|
||||
|
||||
auto showActivity =
|
||||
[&](ActivityType type, const std::string & itemFmt, const std::string & numberFmt = "%d", double unit = 1) {
|
||||
auto s = renderActivity(type, itemFmt, numberFmt, unit);
|
||||
@@ -529,7 +586,7 @@ public:
|
||||
showActivity(actBuilds, "%s built");
|
||||
|
||||
auto s1 = renderActivity(actCopyPaths, "%s copied");
|
||||
auto s2 = renderActivity(actCopyPath, "%s MiB", "%.1f", MiB);
|
||||
auto s2 = renderSizeActivity(actCopyPath);
|
||||
|
||||
if (!s1.empty() || !s2.empty()) {
|
||||
if (!res.empty())
|
||||
@@ -545,12 +602,12 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
showActivity(actFileTransfer, "%s MiB DL", "%.1f", MiB);
|
||||
renderSizeActivity(actFileTransfer, "%s DL");
|
||||
|
||||
{
|
||||
auto s = renderActivity(actOptimiseStore, "%s paths optimised");
|
||||
if (s != "") {
|
||||
s += fmt(", %.1f MiB / %d inodes freed", state.bytesLinked / MiB, state.filesLinked);
|
||||
s += fmt(", %s / %d inodes freed", renderSize(state.bytesLinked), state.filesLinked);
|
||||
if (!res.empty())
|
||||
res += ", ";
|
||||
res += s;
|
||||
|
||||
@@ -69,7 +69,8 @@
|
||||
"outputChecks": {
|
||||
"bin": {
|
||||
"disallowedReferences": [
|
||||
"/0nyw57wm2iicnm9rglvjmbci3ikmcp823czdqdzdcgsnnwqps71g"
|
||||
"/0nyw57wm2iicnm9rglvjmbci3ikmcp823czdqdzdcgsnnwqps71g",
|
||||
"dev"
|
||||
],
|
||||
"disallowedRequisites": [
|
||||
"/07f301yqyz8c6wf6bbbavb2q39j4n8kmcly1s09xadyhgy6x2wr8"
|
||||
@@ -84,7 +85,8 @@
|
||||
"/164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9"
|
||||
],
|
||||
"allowedRequisites": [
|
||||
"/0nr45p69vn6izw9446wsh9bng9nndhvn19kpsm4n96a5mycw0s4z"
|
||||
"/0nr45p69vn6izw9446wsh9bng9nndhvn19kpsm4n96a5mycw0s4z",
|
||||
"bin"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
"__sandboxProfile": "sandcastle",
|
||||
"allowSubstitutes": "",
|
||||
"allowedReferences": "/164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9",
|
||||
"allowedRequisites": "/0nr45p69vn6izw9446wsh9bng9nndhvn19kpsm4n96a5mycw0s4z",
|
||||
"allowedRequisites": "/0nr45p69vn6izw9446wsh9bng9nndhvn19kpsm4n96a5mycw0s4z bin",
|
||||
"builder": "/bin/bash",
|
||||
"disallowedReferences": "/0nyw57wm2iicnm9rglvjmbci3ikmcp823czdqdzdcgsnnwqps71g",
|
||||
"disallowedReferences": "/0nyw57wm2iicnm9rglvjmbci3ikmcp823czdqdzdcgsnnwqps71g dev",
|
||||
"disallowedRequisites": "/07f301yqyz8c6wf6bbbavb2q39j4n8kmcly1s09xadyhgy6x2wr8",
|
||||
"exportReferencesGraph": "refs1 /164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9 refs2 /nix/store/qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv",
|
||||
"impureEnvVars": "UNICORN",
|
||||
|
||||
@@ -23,10 +23,12 @@
|
||||
"/164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9"
|
||||
],
|
||||
"allowedRequisites": [
|
||||
"/0nr45p69vn6izw9446wsh9bng9nndhvn19kpsm4n96a5mycw0s4z"
|
||||
"/0nr45p69vn6izw9446wsh9bng9nndhvn19kpsm4n96a5mycw0s4z",
|
||||
"bin"
|
||||
],
|
||||
"disallowedReferences": [
|
||||
"/0nyw57wm2iicnm9rglvjmbci3ikmcp823czdqdzdcgsnnwqps71g"
|
||||
"/0nyw57wm2iicnm9rglvjmbci3ikmcp823czdqdzdcgsnnwqps71g",
|
||||
"dev"
|
||||
],
|
||||
"disallowedRequisites": [
|
||||
"/07f301yqyz8c6wf6bbbavb2q39j4n8kmcly1s09xadyhgy6x2wr8"
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
"allowedReferences": null,
|
||||
"allowedRequisites": null,
|
||||
"disallowedReferences": [
|
||||
"/0nyw57wm2iicnm9rglvjmbci3ikmcp823czdqdzdcgsnnwqps71g"
|
||||
"/0nyw57wm2iicnm9rglvjmbci3ikmcp823czdqdzdcgsnnwqps71g",
|
||||
"dev"
|
||||
],
|
||||
"disallowedRequisites": [
|
||||
"/07f301yqyz8c6wf6bbbavb2q39j4n8kmcly1s09xadyhgy6x2wr8"
|
||||
@@ -46,7 +47,8 @@
|
||||
"/164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9"
|
||||
],
|
||||
"allowedRequisites": [
|
||||
"/0nr45p69vn6izw9446wsh9bng9nndhvn19kpsm4n96a5mycw0s4z"
|
||||
"/0nr45p69vn6izw9446wsh9bng9nndhvn19kpsm4n96a5mycw0s4z",
|
||||
"bin"
|
||||
],
|
||||
"disallowedReferences": [],
|
||||
"disallowedRequisites": [],
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
],
|
||||
"builder": "/bin/bash",
|
||||
"env": {
|
||||
"bin": "/nix/store/33qms3h55wlaspzba3brlzlrm8m2239g-advanced-attributes-structured-attrs-bin",
|
||||
"dev": "/nix/store/wyfgwsdi8rs851wmy1xfzdxy7y5vrg5l-advanced-attributes-structured-attrs-dev",
|
||||
"out": "/nix/store/7cxy4zx1vqc885r4jl2l64pymqbdmhii-advanced-attributes-structured-attrs"
|
||||
"bin": "/nix/store/cnpasdljgkhnwaf78cf3qygcp4qbki1c-advanced-attributes-structured-attrs-bin",
|
||||
"dev": "/nix/store/ijq6mwpa9jbnpnl33qldfqihrr38kprx-advanced-attributes-structured-attrs-dev",
|
||||
"out": "/nix/store/h1vh648d3p088kdimy0r8ngpfx7c3nzw-advanced-attributes-structured-attrs"
|
||||
},
|
||||
"inputs": {
|
||||
"drvs": {
|
||||
@@ -33,13 +33,13 @@
|
||||
"name": "advanced-attributes-structured-attrs",
|
||||
"outputs": {
|
||||
"bin": {
|
||||
"path": "33qms3h55wlaspzba3brlzlrm8m2239g-advanced-attributes-structured-attrs-bin"
|
||||
"path": "cnpasdljgkhnwaf78cf3qygcp4qbki1c-advanced-attributes-structured-attrs-bin"
|
||||
},
|
||||
"dev": {
|
||||
"path": "wyfgwsdi8rs851wmy1xfzdxy7y5vrg5l-advanced-attributes-structured-attrs-dev"
|
||||
"path": "ijq6mwpa9jbnpnl33qldfqihrr38kprx-advanced-attributes-structured-attrs-dev"
|
||||
},
|
||||
"out": {
|
||||
"path": "7cxy4zx1vqc885r4jl2l64pymqbdmhii-advanced-attributes-structured-attrs"
|
||||
"path": "h1vh648d3p088kdimy0r8ngpfx7c3nzw-advanced-attributes-structured-attrs"
|
||||
}
|
||||
},
|
||||
"structuredAttrs": {
|
||||
@@ -66,7 +66,8 @@
|
||||
"outputChecks": {
|
||||
"bin": {
|
||||
"disallowedReferences": [
|
||||
"/nix/store/r5cff30838majxk5mp3ip2diffi8vpaj-bar"
|
||||
"/nix/store/r5cff30838majxk5mp3ip2diffi8vpaj-bar",
|
||||
"dev"
|
||||
],
|
||||
"disallowedRequisites": [
|
||||
"/nix/store/9b61w26b4avv870dw0ymb6rw4r1hzpws-bar-dev"
|
||||
@@ -81,7 +82,8 @@
|
||||
"/nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo"
|
||||
],
|
||||
"allowedRequisites": [
|
||||
"/nix/store/z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev"
|
||||
"/nix/store/z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev",
|
||||
"bin"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -11,14 +11,14 @@
|
||||
"__sandboxProfile": "sandcastle",
|
||||
"allowSubstitutes": "",
|
||||
"allowedReferences": "/nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo",
|
||||
"allowedRequisites": "/nix/store/z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev",
|
||||
"allowedRequisites": "/nix/store/z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev bin",
|
||||
"builder": "/bin/bash",
|
||||
"disallowedReferences": "/nix/store/r5cff30838majxk5mp3ip2diffi8vpaj-bar",
|
||||
"disallowedReferences": "/nix/store/r5cff30838majxk5mp3ip2diffi8vpaj-bar dev",
|
||||
"disallowedRequisites": "/nix/store/9b61w26b4avv870dw0ymb6rw4r1hzpws-bar-dev",
|
||||
"exportReferencesGraph": "refs1 /nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo refs2 /nix/store/vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv",
|
||||
"impureEnvVars": "UNICORN",
|
||||
"name": "advanced-attributes",
|
||||
"out": "/nix/store/wyhpwd748pns4k7svh48wdrc8kvjk0ra-advanced-attributes",
|
||||
"out": "/nix/store/ymqmybkq5j4nd1xplw6ccdpbjnfi017v-advanced-attributes",
|
||||
"preferLocalBuild": "1",
|
||||
"requiredSystemFeatures": "rainbow uid-range",
|
||||
"system": "my-system"
|
||||
@@ -47,7 +47,7 @@
|
||||
"name": "advanced-attributes",
|
||||
"outputs": {
|
||||
"out": {
|
||||
"path": "wyhpwd748pns4k7svh48wdrc8kvjk0ra-advanced-attributes"
|
||||
"path": "ymqmybkq5j4nd1xplw6ccdpbjnfi017v-advanced-attributes"
|
||||
}
|
||||
},
|
||||
"system": "my-system",
|
||||
|
||||
@@ -23,10 +23,12 @@
|
||||
"/nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo"
|
||||
],
|
||||
"allowedRequisites": [
|
||||
"/nix/store/z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev"
|
||||
"/nix/store/z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev",
|
||||
"bin"
|
||||
],
|
||||
"disallowedReferences": [
|
||||
"/nix/store/r5cff30838majxk5mp3ip2diffi8vpaj-bar"
|
||||
"/nix/store/r5cff30838majxk5mp3ip2diffi8vpaj-bar",
|
||||
"dev"
|
||||
],
|
||||
"disallowedRequisites": [
|
||||
"/nix/store/9b61w26b4avv870dw0ymb6rw4r1hzpws-bar-dev"
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
"allowedReferences": null,
|
||||
"allowedRequisites": null,
|
||||
"disallowedReferences": [
|
||||
"/nix/store/r5cff30838majxk5mp3ip2diffi8vpaj-bar"
|
||||
"/nix/store/r5cff30838majxk5mp3ip2diffi8vpaj-bar",
|
||||
"dev"
|
||||
],
|
||||
"disallowedRequisites": [
|
||||
"/nix/store/9b61w26b4avv870dw0ymb6rw4r1hzpws-bar-dev"
|
||||
@@ -46,7 +47,8 @@
|
||||
"/nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo"
|
||||
],
|
||||
"allowedRequisites": [
|
||||
"/nix/store/z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev"
|
||||
"/nix/store/z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev",
|
||||
"bin"
|
||||
],
|
||||
"disallowedReferences": [],
|
||||
"disallowedRequisites": [],
|
||||
|
||||
@@ -9,9 +9,17 @@
|
||||
},
|
||||
"compression": "xz",
|
||||
"deriver": "/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar.drv",
|
||||
"downloadHash": "sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=",
|
||||
"downloadHash": {
|
||||
"algorithm": "sha256",
|
||||
"format": "base64",
|
||||
"hash": "FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc="
|
||||
},
|
||||
"downloadSize": 4029176,
|
||||
"narHash": "sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=",
|
||||
"narHash": {
|
||||
"algorithm": "sha256",
|
||||
"format": "base64",
|
||||
"hash": "FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc="
|
||||
},
|
||||
"narSize": 34878,
|
||||
"references": [
|
||||
"/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar",
|
||||
|
||||
@@ -7,7 +7,11 @@
|
||||
},
|
||||
"method": "nar"
|
||||
},
|
||||
"narHash": "sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=",
|
||||
"narHash": {
|
||||
"algorithm": "sha256",
|
||||
"format": "base64",
|
||||
"hash": "FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc="
|
||||
},
|
||||
"narSize": 34878,
|
||||
"references": [
|
||||
"/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar",
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
{
|
||||
"ca": null,
|
||||
"deriver": null,
|
||||
"narHash": "sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=",
|
||||
"narHash": {
|
||||
"algorithm": "sha256",
|
||||
"format": "base64",
|
||||
"hash": "FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc="
|
||||
},
|
||||
"narSize": 0,
|
||||
"references": [],
|
||||
"registrationTime": null,
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
{
|
||||
"ca": null,
|
||||
"narHash": "sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=",
|
||||
"narHash": {
|
||||
"algorithm": "sha256",
|
||||
"format": "base64",
|
||||
"hash": "FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc="
|
||||
},
|
||||
"narSize": 0,
|
||||
"references": [],
|
||||
"version": 2
|
||||
|
||||
@@ -8,7 +8,11 @@
|
||||
"method": "nar"
|
||||
},
|
||||
"deriver": "/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar.drv",
|
||||
"narHash": "sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=",
|
||||
"narHash": {
|
||||
"algorithm": "sha256",
|
||||
"format": "base64",
|
||||
"hash": "FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc="
|
||||
},
|
||||
"narSize": 34878,
|
||||
"references": [
|
||||
"/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar",
|
||||
|
||||
@@ -7,7 +7,11 @@
|
||||
},
|
||||
"method": "nar"
|
||||
},
|
||||
"narHash": "sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=",
|
||||
"narHash": {
|
||||
"algorithm": "sha256",
|
||||
"format": "base64",
|
||||
"hash": "FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc="
|
||||
},
|
||||
"narSize": 34878,
|
||||
"references": [
|
||||
"/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar",
|
||||
|
||||
@@ -127,6 +127,21 @@ TEST_ATERM_JSON(advancedAttributes_structuredAttrs_defaults, "advanced-attribute
|
||||
|
||||
#undef TEST_ATERM_JSON
|
||||
|
||||
/**
|
||||
* Since these are both repeated and sensative opaque values, it makes
|
||||
* sense to give them names in this file.
|
||||
*/
|
||||
static std::string pathFoo = "/nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo",
|
||||
pathFooDev = "/nix/store/z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev",
|
||||
pathBar = "/nix/store/r5cff30838majxk5mp3ip2diffi8vpaj-bar",
|
||||
pathBarDev = "/nix/store/9b61w26b4avv870dw0ymb6rw4r1hzpws-bar-dev",
|
||||
pathBarDrvIA = "/nix/store/vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv",
|
||||
pathBarDrvCA = "/nix/store/qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv",
|
||||
placeholderFoo = "/164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9",
|
||||
placeholderFooDev = "/0nr45p69vn6izw9446wsh9bng9nndhvn19kpsm4n96a5mycw0s4z",
|
||||
placeholderBar = "/0nyw57wm2iicnm9rglvjmbci3ikmcp823czdqdzdcgsnnwqps71g",
|
||||
placeholderBarDev = "/07f301yqyz8c6wf6bbbavb2q39j4n8kmcly1s09xadyhgy6x2wr8";
|
||||
|
||||
using ExportReferencesMap = decltype(DerivationOptions::exportReferencesGraph);
|
||||
|
||||
static const DerivationOptions advancedAttributes_defaults = {
|
||||
@@ -216,16 +231,16 @@ DerivationOptions advancedAttributes_ia = {
|
||||
.outputChecks =
|
||||
DerivationOptions::OutputChecks{
|
||||
.ignoreSelfRefs = true,
|
||||
.allowedReferences = StringSet{"/nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo"},
|
||||
.disallowedReferences = StringSet{"/nix/store/r5cff30838majxk5mp3ip2diffi8vpaj-bar"},
|
||||
.allowedRequisites = StringSet{"/nix/store/z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev"},
|
||||
.disallowedRequisites = StringSet{"/nix/store/9b61w26b4avv870dw0ymb6rw4r1hzpws-bar-dev"},
|
||||
.allowedReferences = StringSet{pathFoo},
|
||||
.disallowedReferences = StringSet{pathBar, "dev"},
|
||||
.allowedRequisites = StringSet{pathFooDev, "bin"},
|
||||
.disallowedRequisites = StringSet{pathBarDev},
|
||||
},
|
||||
.unsafeDiscardReferences = {},
|
||||
.passAsFile = {},
|
||||
.exportReferencesGraph{
|
||||
{"refs1", {"/nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo"}},
|
||||
{"refs2", {"/nix/store/vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv"}},
|
||||
{"refs1", {pathFoo}},
|
||||
{"refs2", {pathBarDrvIA}},
|
||||
},
|
||||
.additionalSandboxProfile = "sandcastle",
|
||||
.noChroot = true,
|
||||
@@ -246,16 +261,16 @@ DerivationOptions advancedAttributes_ca = {
|
||||
.outputChecks =
|
||||
DerivationOptions::OutputChecks{
|
||||
.ignoreSelfRefs = true,
|
||||
.allowedReferences = StringSet{"/164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9"},
|
||||
.disallowedReferences = StringSet{"/0nyw57wm2iicnm9rglvjmbci3ikmcp823czdqdzdcgsnnwqps71g"},
|
||||
.allowedRequisites = StringSet{"/0nr45p69vn6izw9446wsh9bng9nndhvn19kpsm4n96a5mycw0s4z"},
|
||||
.disallowedRequisites = StringSet{"/07f301yqyz8c6wf6bbbavb2q39j4n8kmcly1s09xadyhgy6x2wr8"},
|
||||
.allowedReferences = StringSet{placeholderFoo},
|
||||
.disallowedReferences = StringSet{placeholderBar, "dev"},
|
||||
.allowedRequisites = StringSet{placeholderFooDev, "bin"},
|
||||
.disallowedRequisites = StringSet{placeholderBarDev},
|
||||
},
|
||||
.unsafeDiscardReferences = {},
|
||||
.passAsFile = {},
|
||||
.exportReferencesGraph{
|
||||
{"refs1", {"/164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9"}},
|
||||
{"refs2", {"/nix/store/qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv"}},
|
||||
{"refs1", {placeholderFoo}},
|
||||
{"refs2", {pathBarDrvCA}},
|
||||
},
|
||||
.additionalSandboxProfile = "sandcastle",
|
||||
.noChroot = true,
|
||||
@@ -375,13 +390,13 @@ DerivationOptions advancedAttributes_structuredAttrs_ia = {
|
||||
std::map<std::string, DerivationOptions::OutputChecks>{
|
||||
{"out",
|
||||
DerivationOptions::OutputChecks{
|
||||
.allowedReferences = StringSet{"/nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo"},
|
||||
.allowedRequisites = StringSet{"/nix/store/z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev"},
|
||||
.allowedReferences = StringSet{pathFoo},
|
||||
.allowedRequisites = StringSet{pathFooDev, "bin"},
|
||||
}},
|
||||
{"bin",
|
||||
DerivationOptions::OutputChecks{
|
||||
.disallowedReferences = StringSet{"/nix/store/r5cff30838majxk5mp3ip2diffi8vpaj-bar"},
|
||||
.disallowedRequisites = StringSet{"/nix/store/9b61w26b4avv870dw0ymb6rw4r1hzpws-bar-dev"},
|
||||
.disallowedReferences = StringSet{pathBar, "dev"},
|
||||
.disallowedRequisites = StringSet{pathBarDev},
|
||||
}},
|
||||
{"dev",
|
||||
DerivationOptions::OutputChecks{
|
||||
@@ -393,8 +408,8 @@ DerivationOptions advancedAttributes_structuredAttrs_ia = {
|
||||
.passAsFile = {},
|
||||
.exportReferencesGraph =
|
||||
{
|
||||
{"refs1", {"/nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo"}},
|
||||
{"refs2", {"/nix/store/vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv"}},
|
||||
{"refs1", {pathFoo}},
|
||||
{"refs2", {pathBarDrvIA}},
|
||||
},
|
||||
.additionalSandboxProfile = "sandcastle",
|
||||
.noChroot = true,
|
||||
@@ -417,13 +432,13 @@ DerivationOptions advancedAttributes_structuredAttrs_ca = {
|
||||
std::map<std::string, DerivationOptions::OutputChecks>{
|
||||
{"out",
|
||||
DerivationOptions::OutputChecks{
|
||||
.allowedReferences = StringSet{"/164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9"},
|
||||
.allowedRequisites = StringSet{"/0nr45p69vn6izw9446wsh9bng9nndhvn19kpsm4n96a5mycw0s4z"},
|
||||
.allowedReferences = StringSet{placeholderFoo},
|
||||
.allowedRequisites = StringSet{placeholderFooDev, "bin"},
|
||||
}},
|
||||
{"bin",
|
||||
DerivationOptions::OutputChecks{
|
||||
.disallowedReferences = StringSet{"/0nyw57wm2iicnm9rglvjmbci3ikmcp823czdqdzdcgsnnwqps71g"},
|
||||
.disallowedRequisites = StringSet{"/07f301yqyz8c6wf6bbbavb2q39j4n8kmcly1s09xadyhgy6x2wr8"},
|
||||
.disallowedReferences = StringSet{placeholderBar, "dev"},
|
||||
.disallowedRequisites = StringSet{placeholderBarDev},
|
||||
}},
|
||||
{"dev",
|
||||
DerivationOptions::OutputChecks{
|
||||
@@ -435,8 +450,8 @@ DerivationOptions advancedAttributes_structuredAttrs_ca = {
|
||||
.passAsFile = {},
|
||||
.exportReferencesGraph =
|
||||
{
|
||||
{"refs1", {"/164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9"}},
|
||||
{"refs2", {"/nix/store/qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv"}},
|
||||
{"refs1", {placeholderFoo}},
|
||||
{"refs2", {pathBarDrvCA}},
|
||||
},
|
||||
.additionalSandboxProfile = "sandcastle",
|
||||
.noChroot = true,
|
||||
|
||||
@@ -59,24 +59,24 @@ static NarInfo makeNarInfo(const Store & store, bool includeImpureInfo)
|
||||
return info;
|
||||
}
|
||||
|
||||
#define JSON_TEST(STEM, PURE) \
|
||||
TEST_F(NarInfoTest, NarInfo_##STEM##_from_json) \
|
||||
{ \
|
||||
readTest(#STEM, [&](const auto & encoded_) { \
|
||||
auto encoded = json::parse(encoded_); \
|
||||
auto expected = makeNarInfo(*store, PURE); \
|
||||
NarInfo got = NarInfo::fromJSON(*store, expected.path, encoded); \
|
||||
ASSERT_EQ(got, expected); \
|
||||
}); \
|
||||
} \
|
||||
\
|
||||
TEST_F(NarInfoTest, NarInfo_##STEM##_to_json) \
|
||||
{ \
|
||||
writeTest( \
|
||||
#STEM, \
|
||||
[&]() -> json { return makeNarInfo(*store, PURE).toJSON(*store, PURE, HashFormat::SRI); }, \
|
||||
[](const auto & file) { return json::parse(readFile(file)); }, \
|
||||
[](const auto & file, const auto & got) { return writeFile(file, got.dump(2) + "\n"); }); \
|
||||
#define JSON_TEST(STEM, PURE) \
|
||||
TEST_F(NarInfoTest, NarInfo_##STEM##_from_json) \
|
||||
{ \
|
||||
readTest(#STEM, [&](const auto & encoded_) { \
|
||||
auto encoded = json::parse(encoded_); \
|
||||
auto expected = makeNarInfo(*store, PURE); \
|
||||
NarInfo got = NarInfo::fromJSON(*store, expected.path, encoded); \
|
||||
ASSERT_EQ(got, expected); \
|
||||
}); \
|
||||
} \
|
||||
\
|
||||
TEST_F(NarInfoTest, NarInfo_##STEM##_to_json) \
|
||||
{ \
|
||||
writeTest( \
|
||||
#STEM, \
|
||||
[&]() -> json { return makeNarInfo(*store, PURE).toJSON(*store, PURE); }, \
|
||||
[](const auto & file) { return json::parse(readFile(file)); }, \
|
||||
[](const auto & file, const auto & got) { return writeFile(file, got.dump(2) + "\n"); }); \
|
||||
}
|
||||
|
||||
JSON_TEST(pure, false)
|
||||
|
||||
@@ -80,7 +80,7 @@ static UnkeyedValidPathInfo makeFull(const Store & store, bool includeImpureInfo
|
||||
{ \
|
||||
writeTest( \
|
||||
#STEM, \
|
||||
[&]() -> json { return OBJ.toJSON(*store, PURE, HashFormat::SRI); }, \
|
||||
[&]() -> json { return OBJ.toJSON(*store, PURE); }, \
|
||||
[](const auto & file) { return json::parse(readFile(file)); }, \
|
||||
[](const auto & file, const auto & got) { return writeFile(file, got.dump(2) + "\n"); }); \
|
||||
}
|
||||
|
||||
@@ -423,15 +423,6 @@ void adl_serializer<DerivationOptions>::to_json(json & json, const DerivationOpt
|
||||
json["allowSubstitutes"] = o.allowSubstitutes;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static inline std::optional<T> ptrToOwned(const json * ptr)
|
||||
{
|
||||
if (ptr)
|
||||
return std::optional{*ptr};
|
||||
else
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
DerivationOptions::OutputChecks adl_serializer<DerivationOptions::OutputChecks>::from_json(const json & json_)
|
||||
{
|
||||
auto & json = getObject(json_);
|
||||
|
||||
@@ -1381,13 +1381,15 @@ adl_serializer<DerivationOutput>::from_json(const json & _json, const Experiment
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned constexpr expectedJsonVersionDerivation = 4;
|
||||
|
||||
void adl_serializer<Derivation>::to_json(json & res, const Derivation & d)
|
||||
{
|
||||
res = nlohmann::json::object();
|
||||
|
||||
res["name"] = d.name;
|
||||
|
||||
res["version"] = 4;
|
||||
res["version"] = expectedJsonVersionDerivation;
|
||||
|
||||
{
|
||||
nlohmann::json & outputsObj = res["outputs"];
|
||||
@@ -1446,8 +1448,14 @@ Derivation adl_serializer<Derivation>::from_json(const json & _json, const Exper
|
||||
|
||||
res.name = getString(valueAt(json, "name"));
|
||||
|
||||
if (valueAt(json, "version") != 4)
|
||||
throw Error("Only derivation format version 4 is currently supported.");
|
||||
{
|
||||
auto version = getUnsigned(valueAt(json, "version"));
|
||||
if (valueAt(json, "version") != expectedJsonVersionDerivation)
|
||||
throw Error(
|
||||
"Unsupported derivation JSON format version %d, only format version %d is currently supported.",
|
||||
version,
|
||||
expectedJsonVersionDerivation);
|
||||
}
|
||||
|
||||
try {
|
||||
auto outputs = getObject(valueAt(json, "outputs"));
|
||||
|
||||
@@ -42,7 +42,7 @@ struct NarInfo : ValidPathInfo
|
||||
|
||||
std::string to_string(const StoreDirConfig & store) const;
|
||||
|
||||
nlohmann::json toJSON(const StoreDirConfig & store, bool includeImpureInfo, HashFormat hashFormat) const override;
|
||||
nlohmann::json toJSON(const StoreDirConfig & store, bool includeImpureInfo) const override;
|
||||
static NarInfo fromJSON(const StoreDirConfig & store, const StorePath & path, const nlohmann::json & json);
|
||||
};
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ struct UnkeyedValidPathInfo
|
||||
* @param includeImpureInfo If true, variable elements such as the
|
||||
* registration time are included.
|
||||
*/
|
||||
virtual nlohmann::json toJSON(const StoreDirConfig & store, bool includeImpureInfo, HashFormat hashFormat) const;
|
||||
virtual nlohmann::json toJSON(const StoreDirConfig & store, bool includeImpureInfo) const;
|
||||
static UnkeyedValidPathInfo fromJSON(const StoreDirConfig & store, const nlohmann::json & json);
|
||||
};
|
||||
|
||||
|
||||
@@ -52,7 +52,21 @@ struct RestrictionContext
|
||||
* Add 'path' to the set of paths that may be referenced by the
|
||||
* outputs, and make it appear in the sandbox.
|
||||
*/
|
||||
virtual void addDependency(const StorePath & path) = 0;
|
||||
void addDependency(const StorePath & path)
|
||||
{
|
||||
if (isAllowed(path))
|
||||
return;
|
||||
addDependencyImpl(path);
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
/**
|
||||
* This is the underlying implementation to be defined. The caller
|
||||
* will ensure that this is only called on newly added dependencies,
|
||||
* and that idempotent calls are a no-op.
|
||||
*/
|
||||
virtual void addDependencyImpl(const StorePath & path) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -989,19 +989,22 @@ void LocalStore::registerValidPaths(const ValidPathInfos & infos)
|
||||
error if a cycle is detected and roll back the
|
||||
transaction. Cycles can only occur when a derivation
|
||||
has multiple outputs. */
|
||||
topoSort(
|
||||
paths,
|
||||
{[&](const StorePath & path) {
|
||||
auto i = infos.find(path);
|
||||
return i == infos.end() ? StorePathSet() : i->second.references;
|
||||
}},
|
||||
{[&](const StorePath & path, const StorePath & parent) {
|
||||
return BuildError(
|
||||
BuildResult::Failure::OutputRejected,
|
||||
"cycle detected in the references of '%s' from '%s'",
|
||||
printStorePath(path),
|
||||
printStorePath(parent));
|
||||
}});
|
||||
auto topoSortResult = topoSort(paths, {[&](const StorePath & path) {
|
||||
auto i = infos.find(path);
|
||||
return i == infos.end() ? StorePathSet() : i->second.references;
|
||||
}});
|
||||
|
||||
std::visit(
|
||||
overloaded{
|
||||
[&](const Cycle<StorePath> & cycle) {
|
||||
throw BuildError(
|
||||
BuildResult::Failure::OutputRejected,
|
||||
"cycle detected in the references of '%s' from '%s'",
|
||||
printStorePath(cycle.path),
|
||||
printStorePath(cycle.parent));
|
||||
},
|
||||
[](auto &) { /* Success, continue */ }},
|
||||
topoSortResult);
|
||||
|
||||
txn.commit();
|
||||
});
|
||||
|
||||
@@ -311,22 +311,25 @@ MissingPaths Store::queryMissing(const std::vector<DerivedPath> & targets)
|
||||
|
||||
StorePaths Store::topoSortPaths(const StorePathSet & paths)
|
||||
{
|
||||
return topoSort(
|
||||
paths,
|
||||
{[&](const StorePath & path) {
|
||||
try {
|
||||
return queryPathInfo(path)->references;
|
||||
} catch (InvalidPath &) {
|
||||
return StorePathSet();
|
||||
}
|
||||
}},
|
||||
{[&](const StorePath & path, const StorePath & parent) {
|
||||
return BuildError(
|
||||
BuildResult::Failure::OutputRejected,
|
||||
"cycle detected in the references of '%s' from '%s'",
|
||||
printStorePath(path),
|
||||
printStorePath(parent));
|
||||
}});
|
||||
auto result = topoSort(paths, {[&](const StorePath & path) {
|
||||
try {
|
||||
return queryPathInfo(path)->references;
|
||||
} catch (InvalidPath &) {
|
||||
return StorePathSet();
|
||||
}
|
||||
}});
|
||||
|
||||
return std::visit(
|
||||
overloaded{
|
||||
[&](const Cycle<StorePath> & cycle) -> StorePaths {
|
||||
throw BuildError(
|
||||
BuildResult::Failure::OutputRejected,
|
||||
"cycle detected in the references of '%s' from '%s'",
|
||||
printStorePath(cycle.path),
|
||||
printStorePath(cycle.parent));
|
||||
},
|
||||
[](const auto & sorted) { return sorted; }},
|
||||
result);
|
||||
}
|
||||
|
||||
std::map<DrvOutput, StorePath>
|
||||
|
||||
@@ -130,11 +130,11 @@ std::string NarInfo::to_string(const StoreDirConfig & store) const
|
||||
return res;
|
||||
}
|
||||
|
||||
nlohmann::json NarInfo::toJSON(const StoreDirConfig & store, bool includeImpureInfo, HashFormat hashFormat) const
|
||||
nlohmann::json NarInfo::toJSON(const StoreDirConfig & store, bool includeImpureInfo) const
|
||||
{
|
||||
using nlohmann::json;
|
||||
|
||||
auto jsonObject = ValidPathInfo::toJSON(store, includeImpureInfo, hashFormat);
|
||||
auto jsonObject = ValidPathInfo::toJSON(store, includeImpureInfo);
|
||||
|
||||
if (includeImpureInfo) {
|
||||
if (!url.empty())
|
||||
@@ -142,7 +142,7 @@ nlohmann::json NarInfo::toJSON(const StoreDirConfig & store, bool includeImpureI
|
||||
if (!compression.empty())
|
||||
jsonObject["compression"] = compression;
|
||||
if (fileHash)
|
||||
jsonObject["downloadHash"] = fileHash->to_string(hashFormat, true);
|
||||
jsonObject["downloadHash"] = *fileHash;
|
||||
if (fileSize)
|
||||
jsonObject["downloadSize"] = fileSize;
|
||||
}
|
||||
@@ -161,17 +161,17 @@ NarInfo NarInfo::fromJSON(const StoreDirConfig & store, const StorePath & path,
|
||||
|
||||
auto & obj = getObject(json);
|
||||
|
||||
if (json.contains("url"))
|
||||
res.url = getString(valueAt(obj, "url"));
|
||||
if (auto * url = get(obj, "url"))
|
||||
res.url = getString(*url);
|
||||
|
||||
if (json.contains("compression"))
|
||||
res.compression = getString(valueAt(obj, "compression"));
|
||||
if (auto * compression = get(obj, "compression"))
|
||||
res.compression = getString(*compression);
|
||||
|
||||
if (json.contains("downloadHash"))
|
||||
res.fileHash = Hash::parseAny(getString(valueAt(obj, "downloadHash")), std::nullopt);
|
||||
if (auto * downloadHash = get(obj, "downloadHash"))
|
||||
res.fileHash = *downloadHash;
|
||||
|
||||
if (json.contains("downloadSize"))
|
||||
res.fileSize = getUnsigned(valueAt(obj, "downloadSize"));
|
||||
if (auto * downloadSize = get(obj, "downloadSize"))
|
||||
res.fileSize = getUnsigned(*downloadSize);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -149,8 +149,7 @@ ValidPathInfo ValidPathInfo::makeFromCA(
|
||||
return res;
|
||||
}
|
||||
|
||||
nlohmann::json
|
||||
UnkeyedValidPathInfo::toJSON(const StoreDirConfig & store, bool includeImpureInfo, HashFormat hashFormat) const
|
||||
nlohmann::json UnkeyedValidPathInfo::toJSON(const StoreDirConfig & store, bool includeImpureInfo) const
|
||||
{
|
||||
using nlohmann::json;
|
||||
|
||||
@@ -158,7 +157,7 @@ UnkeyedValidPathInfo::toJSON(const StoreDirConfig & store, bool includeImpureInf
|
||||
|
||||
jsonObject["version"] = 2;
|
||||
|
||||
jsonObject["narHash"] = narHash.to_string(hashFormat, true);
|
||||
jsonObject["narHash"] = narHash;
|
||||
jsonObject["narSize"] = narSize;
|
||||
|
||||
{
|
||||
@@ -192,16 +191,13 @@ UnkeyedValidPathInfo UnkeyedValidPathInfo::fromJSON(const StoreDirConfig & store
|
||||
|
||||
auto & json = getObject(_json);
|
||||
|
||||
// Check version (optional for backward compatibility)
|
||||
nlohmann::json::number_unsigned_t version = 1;
|
||||
if (json.contains("version")) {
|
||||
version = getUnsigned(valueAt(json, "version"));
|
||||
if (version != 1 && version != 2) {
|
||||
throw Error("Unsupported path info JSON format version %d, expected 1 through 2", version);
|
||||
}
|
||||
{
|
||||
auto version = getUnsigned(valueAt(json, "version"));
|
||||
if (version != 2)
|
||||
throw Error("Unsupported path info JSON format version %d, only version 2 is currently supported", version);
|
||||
}
|
||||
|
||||
res.narHash = Hash::parseAny(getString(valueAt(json, "narHash")), std::nullopt);
|
||||
res.narHash = valueAt(json, "narHash");
|
||||
res.narSize = getUnsigned(valueAt(json, "narSize"));
|
||||
|
||||
try {
|
||||
@@ -213,19 +209,12 @@ UnkeyedValidPathInfo UnkeyedValidPathInfo::fromJSON(const StoreDirConfig & store
|
||||
throw;
|
||||
}
|
||||
|
||||
// New format as this as nullable but mandatory field; handling
|
||||
// missing is for back-compat.
|
||||
if (auto * rawCa0 = optionalValueAt(json, "ca"))
|
||||
if (auto * rawCa = getNullable(*rawCa0))
|
||||
switch (version) {
|
||||
case 1:
|
||||
// old string format also used in SQLite DB and .narinfo
|
||||
res.ca = ContentAddress::parse(getString(*rawCa));
|
||||
break;
|
||||
case 2 ... std::numeric_limits<decltype(version)>::max():
|
||||
res.ca = *rawCa;
|
||||
break;
|
||||
}
|
||||
try {
|
||||
res.ca = ptrToOwned<ContentAddress>(getNullable(valueAt(json, "ca")));
|
||||
} catch (Error & e) {
|
||||
e.addTrace({}, "while reading key 'ca'");
|
||||
throw;
|
||||
}
|
||||
|
||||
if (auto * rawDeriver0 = optionalValueAt(json, "deriver"))
|
||||
if (auto * rawDeriver = getNullable(*rawDeriver0))
|
||||
|
||||
@@ -334,7 +334,7 @@ private:
|
||||
|
||||
protected:
|
||||
|
||||
void addDependency(const StorePath & path) override;
|
||||
void addDependencyImpl(const StorePath & path) override;
|
||||
|
||||
/**
|
||||
* Make a file owned by the builder.
|
||||
@@ -1203,11 +1203,8 @@ void DerivationBuilderImpl::stopDaemon()
|
||||
daemonSocket.close();
|
||||
}
|
||||
|
||||
void DerivationBuilderImpl::addDependency(const StorePath & path)
|
||||
void DerivationBuilderImpl::addDependencyImpl(const StorePath & path)
|
||||
{
|
||||
if (isAllowed(path))
|
||||
return;
|
||||
|
||||
addedPaths.insert(path);
|
||||
}
|
||||
|
||||
@@ -1473,43 +1470,46 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs()
|
||||
outputStats.insert_or_assign(outputName, std::move(st));
|
||||
}
|
||||
|
||||
auto sortedOutputNames = topoSort(
|
||||
outputsToSort,
|
||||
{[&](const std::string & name) {
|
||||
auto orifu = get(outputReferencesIfUnregistered, name);
|
||||
if (!orifu)
|
||||
auto topoSortResult = topoSort(outputsToSort, {[&](const std::string & name) {
|
||||
auto orifu = get(outputReferencesIfUnregistered, name);
|
||||
if (!orifu)
|
||||
throw BuildError(
|
||||
BuildResult::Failure::OutputRejected,
|
||||
"no output reference for '%s' in build of '%s'",
|
||||
name,
|
||||
store.printStorePath(drvPath));
|
||||
return std::visit(
|
||||
overloaded{
|
||||
/* Since we'll use the already installed versions of these, we
|
||||
can treat them as leaves and ignore any references they
|
||||
have. */
|
||||
[&](const AlreadyRegistered &) { return StringSet{}; },
|
||||
[&](const PerhapsNeedToRegister & refs) {
|
||||
StringSet referencedOutputs;
|
||||
/* FIXME build inverted map up front so no quadratic waste here */
|
||||
for (auto & r : refs.refs)
|
||||
for (auto & [o, p] : scratchOutputs)
|
||||
if (r == p)
|
||||
referencedOutputs.insert(o);
|
||||
return referencedOutputs;
|
||||
},
|
||||
},
|
||||
*orifu);
|
||||
}});
|
||||
|
||||
auto sortedOutputNames = std::visit(
|
||||
overloaded{
|
||||
[&](Cycle<std::string> & cycle) -> std::vector<std::string> {
|
||||
// TODO with more -vvvv also show the temporary paths for manual inspection.
|
||||
throw BuildError(
|
||||
BuildResult::Failure::OutputRejected,
|
||||
"no output reference for '%s' in build of '%s'",
|
||||
name,
|
||||
store.printStorePath(drvPath));
|
||||
return std::visit(
|
||||
overloaded{
|
||||
/* Since we'll use the already installed versions of these, we
|
||||
can treat them as leaves and ignore any references they
|
||||
have. */
|
||||
[&](const AlreadyRegistered &) { return StringSet{}; },
|
||||
[&](const PerhapsNeedToRegister & refs) {
|
||||
StringSet referencedOutputs;
|
||||
/* FIXME build inverted map up front so no quadratic waste here */
|
||||
for (auto & r : refs.refs)
|
||||
for (auto & [o, p] : scratchOutputs)
|
||||
if (r == p)
|
||||
referencedOutputs.insert(o);
|
||||
return referencedOutputs;
|
||||
},
|
||||
},
|
||||
*orifu);
|
||||
}},
|
||||
{[&](const std::string & path, const std::string & parent) {
|
||||
// TODO with more -vvvv also show the temporary paths for manual inspection.
|
||||
return BuildError(
|
||||
BuildResult::Failure::OutputRejected,
|
||||
"cycle detected in build of '%s' in the references of output '%s' from output '%s'",
|
||||
store.printStorePath(drvPath),
|
||||
path,
|
||||
parent);
|
||||
}});
|
||||
"cycle detected in build of '%s' in the references of output '%s' from output '%s'",
|
||||
store.printStorePath(drvPath),
|
||||
cycle.path,
|
||||
cycle.parent);
|
||||
},
|
||||
[](auto & sorted) { return sorted; }},
|
||||
topoSortResult);
|
||||
|
||||
std::reverse(sortedOutputNames.begin(), sortedOutputNames.end());
|
||||
|
||||
|
||||
@@ -709,8 +709,11 @@ struct ChrootLinuxDerivationBuilder : ChrootDerivationBuilder, LinuxDerivationBu
|
||||
DerivationBuilderImpl::killSandbox(getStats);
|
||||
}
|
||||
|
||||
void addDependency(const StorePath & path) override
|
||||
void addDependencyImpl(const StorePath & path) override
|
||||
{
|
||||
if (isAllowed(path))
|
||||
return;
|
||||
|
||||
auto [source, target] = ChrootDerivationBuilder::addDependencyPrep(path);
|
||||
|
||||
/* Bind-mount the path into the sandbox. This requires
|
||||
|
||||
@@ -74,6 +74,7 @@ sources = files(
|
||||
'strings.cc',
|
||||
'suggestions.cc',
|
||||
'terminal.cc',
|
||||
'topo-sort.cc',
|
||||
'url.cc',
|
||||
'util.cc',
|
||||
'xml-writer.cc',
|
||||
|
||||
318
src/libutil-tests/topo-sort.cc
Normal file
318
src/libutil-tests/topo-sort.cc
Normal file
@@ -0,0 +1,318 @@
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "nix/util/topo-sort.hh"
|
||||
#include "nix/util/util.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
/**
|
||||
* Helper function to create a graph and run topoSort
|
||||
*/
|
||||
TopoSortResult<std::string>
|
||||
runTopoSort(const std::set<std::string> & nodes, const std::map<std::string, std::set<std::string>> & edges)
|
||||
{
|
||||
return topoSort(
|
||||
nodes,
|
||||
std::function<std::set<std::string>(const std::string &)>(
|
||||
[&](const std::string & node) -> std::set<std::string> {
|
||||
auto it = edges.find(node);
|
||||
return it != edges.end() ? it->second : std::set<std::string>{};
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to check if a sorted result respects dependencies
|
||||
*
|
||||
* @note `topoSort` returns results in REVERSE topological order (see
|
||||
* line 61 of topo-sort.hh). This means dependents come BEFORE their
|
||||
* dependencies in the output.
|
||||
*
|
||||
* In the edges std::map, if parent -> child, it means parent depends on
|
||||
* child, so parent must come BEFORE child in the output from topoSort.
|
||||
*/
|
||||
bool isValidTopologicalOrder(
|
||||
const std::vector<std::string> & sorted, const std::map<std::string, std::set<std::string>> & edges)
|
||||
{
|
||||
std::map<std::string, size_t> position;
|
||||
for (size_t i = 0; i < sorted.size(); ++i) {
|
||||
position[sorted[i]] = i;
|
||||
}
|
||||
|
||||
// For each edge parent -> children, parent depends on children
|
||||
// topoSort reverses the output, so parent comes BEFORE children
|
||||
for (const auto & [parent, children] : edges) {
|
||||
for (const auto & child : children) {
|
||||
if (position.count(parent) && position.count(child)) {
|
||||
// parent should come before child (have a smaller index)
|
||||
if (position[parent] > position[child]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Parametrized Tests for Topological Sort
|
||||
// ============================================================================
|
||||
|
||||
struct ExpectSuccess
|
||||
{
|
||||
std::optional<std::vector<std::string>> order; // std::nullopt = any valid order is acceptable
|
||||
};
|
||||
|
||||
struct ExpectCycle
|
||||
{
|
||||
std::set<std::string> involvedNodes;
|
||||
};
|
||||
|
||||
using ExpectedResult = std::variant<ExpectSuccess, ExpectCycle>;
|
||||
|
||||
struct TopoSortCase
|
||||
{
|
||||
std::string name;
|
||||
std::set<std::string> nodes;
|
||||
std::map<std::string, std::set<std::string>> edges;
|
||||
ExpectedResult expected;
|
||||
};
|
||||
|
||||
class TopoSortTest : public ::testing::TestWithParam<TopoSortCase>
|
||||
{};
|
||||
|
||||
TEST_P(TopoSortTest, ProducesCorrectResult)
|
||||
{
|
||||
const auto & testCase = GetParam();
|
||||
auto result = runTopoSort(testCase.nodes, testCase.edges);
|
||||
|
||||
std::visit(
|
||||
overloaded{
|
||||
[&](const ExpectSuccess & expect) {
|
||||
// Success case
|
||||
ASSERT_TRUE(holds_alternative<std::vector<std::string>>(result))
|
||||
<< "Expected successful sort for: " << testCase.name;
|
||||
|
||||
auto sorted = get<std::vector<std::string>>(result);
|
||||
ASSERT_EQ(sorted.size(), testCase.nodes.size())
|
||||
<< "Sorted output should contain all nodes for: " << testCase.name;
|
||||
|
||||
ASSERT_TRUE(isValidTopologicalOrder(sorted, testCase.edges))
|
||||
<< "Invalid topological order for: " << testCase.name;
|
||||
|
||||
if (expect.order) {
|
||||
ASSERT_EQ(sorted, *expect.order) << "Expected specific order for: " << testCase.name;
|
||||
}
|
||||
},
|
||||
[&](const ExpectCycle & expect) {
|
||||
// Cycle detection case
|
||||
ASSERT_TRUE(holds_alternative<Cycle<std::string>>(result))
|
||||
<< "Expected cycle detection for: " << testCase.name;
|
||||
|
||||
auto cycle = get<Cycle<std::string>>(result);
|
||||
|
||||
// Verify that the cycle involves expected nodes
|
||||
ASSERT_TRUE(expect.involvedNodes.count(cycle.path) > 0)
|
||||
<< "Cycle path '" << cycle.path << "' not in expected cycle nodes for: " << testCase.name;
|
||||
ASSERT_TRUE(expect.involvedNodes.count(cycle.parent) > 0)
|
||||
<< "Cycle parent '" << cycle.parent << "' not in expected cycle nodes for: " << testCase.name;
|
||||
|
||||
// Verify that there's actually an edge in the cycle
|
||||
auto it = testCase.edges.find(cycle.parent);
|
||||
ASSERT_TRUE(it != testCase.edges.end()) << "Parent node should have edges for: " << testCase.name;
|
||||
ASSERT_TRUE(it->second.count(cycle.path) > 0)
|
||||
<< "Should be an edge from parent to path for: " << testCase.name;
|
||||
}},
|
||||
testCase.expected);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
TopoSort,
|
||||
TopoSortTest,
|
||||
::testing::Values(
|
||||
// Success cases
|
||||
TopoSortCase{
|
||||
.name = "EmptySet",
|
||||
.nodes = {},
|
||||
.edges = {},
|
||||
.expected = ExpectSuccess{.order = std::vector<std::string>{}},
|
||||
},
|
||||
TopoSortCase{
|
||||
.name = "SingleNode",
|
||||
.nodes = {"A"},
|
||||
.edges = {},
|
||||
.expected = ExpectSuccess{.order = std::vector<std::string>{"A"}},
|
||||
},
|
||||
TopoSortCase{
|
||||
.name = "TwoIndependentNodes",
|
||||
.nodes = {"A", "B"},
|
||||
.edges = {},
|
||||
// Order between independent nodes is unspecified
|
||||
.expected = ExpectSuccess{.order = std::nullopt},
|
||||
},
|
||||
TopoSortCase{
|
||||
.name = "SimpleChain",
|
||||
.nodes = {"A", "B", "C"},
|
||||
.edges{
|
||||
{"A", {"B"}},
|
||||
{"B", {"C"}},
|
||||
},
|
||||
.expected = ExpectSuccess{.order = std::vector<std::string>{"A", "B", "C"}},
|
||||
},
|
||||
TopoSortCase{
|
||||
.name = "SimpleDag",
|
||||
.nodes = {"A", "B", "C", "D"},
|
||||
.edges{
|
||||
{"A", {"B", "C"}},
|
||||
{"B", {"D"}},
|
||||
{"C", {"D"}},
|
||||
},
|
||||
.expected = ExpectSuccess{.order = std::nullopt},
|
||||
},
|
||||
TopoSortCase{
|
||||
.name = "DiamondDependency",
|
||||
.nodes = {"A", "B", "C", "D"},
|
||||
.edges{
|
||||
{"A", {"B", "C"}},
|
||||
{"B", {"D"}},
|
||||
{"C", {"D"}},
|
||||
},
|
||||
.expected = ExpectSuccess{.order = std::nullopt},
|
||||
},
|
||||
TopoSortCase{
|
||||
.name = "DisconnectedComponents",
|
||||
.nodes = {"A", "B", "C", "D"},
|
||||
.edges{
|
||||
{"A", {"B"}},
|
||||
{"C", {"D"}},
|
||||
},
|
||||
.expected = ExpectSuccess{.order = std::nullopt},
|
||||
},
|
||||
TopoSortCase{
|
||||
.name = "NodeWithNoReferences",
|
||||
.nodes = {"A", "B", "C"},
|
||||
.edges{
|
||||
{"A", {"B"}},
|
||||
// C has no dependencies
|
||||
},
|
||||
.expected = ExpectSuccess{.order = std::nullopt},
|
||||
},
|
||||
TopoSortCase{
|
||||
.name = "MissingReferences",
|
||||
.nodes = {"A", "B"},
|
||||
.edges{
|
||||
// Z doesn't exist in nodes, should be ignored
|
||||
{"A", {"B", "Z"}},
|
||||
},
|
||||
.expected = ExpectSuccess{.order = std::vector<std::string>{"A", "B"}},
|
||||
},
|
||||
TopoSortCase{
|
||||
.name = "ComplexDag",
|
||||
.nodes = {"A", "B", "C", "D", "E", "F", "G", "H"},
|
||||
.edges{
|
||||
{"A", {"B", "C", "D"}},
|
||||
{"B", {"E", "F"}},
|
||||
{"C", {"E", "F"}},
|
||||
{"D", {"G"}},
|
||||
{"E", {"H"}},
|
||||
{"F", {"H"}},
|
||||
{"G", {"H"}},
|
||||
},
|
||||
.expected = ExpectSuccess{.order = std::nullopt},
|
||||
},
|
||||
TopoSortCase{
|
||||
.name = "LongChain",
|
||||
.nodes = {"A", "B", "C", "D", "E", "F", "G", "H"},
|
||||
.edges{
|
||||
{"A", {"B"}},
|
||||
{"B", {"C"}},
|
||||
{"C", {"D"}},
|
||||
{"D", {"E"}},
|
||||
{"E", {"F"}},
|
||||
{"F", {"G"}},
|
||||
{"G", {"H"}},
|
||||
},
|
||||
.expected = ExpectSuccess{.order = std::vector<std::string>{"A", "B", "C", "D", "E", "F", "G", "H"}},
|
||||
},
|
||||
TopoSortCase{
|
||||
.name = "SelfLoopIgnored",
|
||||
.nodes = {"A"},
|
||||
.edges{
|
||||
// Self-reference should be ignored per line 41 of topo-sort.hh
|
||||
{"A", {"A"}},
|
||||
},
|
||||
.expected = ExpectSuccess{.order = std::vector<std::string>{"A"}},
|
||||
},
|
||||
TopoSortCase{
|
||||
.name = "SelfLoopInChainIgnored",
|
||||
.nodes = {"A", "B", "C"},
|
||||
.edges{
|
||||
// B has self-reference that should be ignored
|
||||
{"A", {"B"}},
|
||||
{"B", {"B", "C"}},
|
||||
},
|
||||
.expected = ExpectSuccess{.order = std::vector<std::string>{"A", "B", "C"}},
|
||||
},
|
||||
// Cycle detection cases
|
||||
TopoSortCase{
|
||||
.name = "TwoNodeCycle",
|
||||
.nodes = {"A", "B"},
|
||||
.edges{
|
||||
{"A", {"B"}},
|
||||
{"B", {"A"}},
|
||||
},
|
||||
.expected = ExpectCycle{.involvedNodes = {"A", "B"}},
|
||||
},
|
||||
TopoSortCase{
|
||||
.name = "ThreeNodeCycle",
|
||||
.nodes = {"A", "B", "C"},
|
||||
.edges{
|
||||
{"A", {"B"}},
|
||||
{"B", {"C"}},
|
||||
{"C", {"A"}},
|
||||
},
|
||||
.expected = ExpectCycle{.involvedNodes = {"A", "B", "C"}},
|
||||
},
|
||||
TopoSortCase{
|
||||
.name = "CycleInLargerGraph",
|
||||
.nodes = {"A", "B", "C", "D"},
|
||||
.edges{
|
||||
{"A", {"B"}},
|
||||
{"B", {"C"}},
|
||||
{"C", {"A"}},
|
||||
{"D", {"A"}},
|
||||
},
|
||||
.expected = ExpectCycle{.involvedNodes = {"A", "B", "C"}},
|
||||
},
|
||||
TopoSortCase{
|
||||
.name = "MultipleCycles",
|
||||
.nodes = {"A", "B", "C", "D"},
|
||||
.edges{
|
||||
{"A", {"B"}},
|
||||
{"B", {"A"}},
|
||||
{"C", {"D"}},
|
||||
{"D", {"C"}},
|
||||
},
|
||||
// Either cycle is valid
|
||||
.expected = ExpectCycle{.involvedNodes = {"A", "B", "C", "D"}},
|
||||
},
|
||||
TopoSortCase{
|
||||
.name = "ComplexCycleWithBranches",
|
||||
.nodes = {"A", "B", "C", "D", "E"},
|
||||
.edges{
|
||||
// Cycle: B -> D -> E -> B
|
||||
{"A", {"B", "C"}},
|
||||
{"B", {"D"}},
|
||||
{"C", {"D"}},
|
||||
{"D", {"E"}},
|
||||
{"E", {"B"}},
|
||||
},
|
||||
.expected = ExpectCycle{.involvedNodes = {"B", "D", "E"}},
|
||||
}));
|
||||
|
||||
} // namespace nix
|
||||
@@ -35,10 +35,10 @@ INSTANTIATE_TEST_SUITE_P(
|
||||
// Already proper URL with git+ssh
|
||||
FixGitURLParam{
|
||||
.input = "git+ssh://user@domain:1234/path",
|
||||
.expected = "git+ssh://user@domain:1234/path",
|
||||
.expected = "ssh://user@domain:1234/path",
|
||||
.parsed =
|
||||
ParsedURL{
|
||||
.scheme = "git+ssh",
|
||||
.scheme = "ssh",
|
||||
.authority =
|
||||
ParsedURL::Authority{
|
||||
.host = "domain",
|
||||
|
||||
@@ -146,6 +146,59 @@ TEST(string2Int, trivialConversions)
|
||||
ASSERT_EQ(string2Int<int>("-100"), -100);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* getSizeUnit
|
||||
* --------------------------------------------------------------------------*/
|
||||
|
||||
TEST(getSizeUnit, misc)
|
||||
{
|
||||
ASSERT_EQ(getSizeUnit(0), SizeUnit::Base);
|
||||
ASSERT_EQ(getSizeUnit(100), SizeUnit::Base);
|
||||
ASSERT_EQ(getSizeUnit(100), SizeUnit::Base);
|
||||
ASSERT_EQ(getSizeUnit(972), SizeUnit::Base);
|
||||
ASSERT_EQ(getSizeUnit(973), SizeUnit::Base); // FIXME: should round down
|
||||
ASSERT_EQ(getSizeUnit(1024), SizeUnit::Base);
|
||||
ASSERT_EQ(getSizeUnit(-1024), SizeUnit::Base);
|
||||
ASSERT_EQ(getSizeUnit(1024 * 1024), SizeUnit::Kilo);
|
||||
ASSERT_EQ(getSizeUnit(1100 * 1024), SizeUnit::Mega);
|
||||
ASSERT_EQ(getSizeUnit(2ULL * 1024 * 1024 * 1024), SizeUnit::Giga);
|
||||
ASSERT_EQ(getSizeUnit(2100ULL * 1024 * 1024 * 1024), SizeUnit::Tera);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* getCommonSizeUnit
|
||||
* --------------------------------------------------------------------------*/
|
||||
|
||||
TEST(getCommonSizeUnit, misc)
|
||||
{
|
||||
ASSERT_EQ(getCommonSizeUnit({0}), SizeUnit::Base);
|
||||
ASSERT_EQ(getCommonSizeUnit({0, 100}), SizeUnit::Base);
|
||||
ASSERT_EQ(getCommonSizeUnit({100, 0}), SizeUnit::Base);
|
||||
ASSERT_EQ(getCommonSizeUnit({100, 1024 * 1024}), std::nullopt);
|
||||
ASSERT_EQ(getCommonSizeUnit({1024 * 1024, 100}), std::nullopt);
|
||||
ASSERT_EQ(getCommonSizeUnit({1024 * 1024, 1024 * 1024}), SizeUnit::Kilo);
|
||||
ASSERT_EQ(getCommonSizeUnit({2100ULL * 1024 * 1024 * 1024, 2100ULL * 1024 * 1024 * 1024}), SizeUnit::Tera);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* renderSizeWithoutUnit
|
||||
* --------------------------------------------------------------------------*/
|
||||
|
||||
TEST(renderSizeWithoutUnit, misc)
|
||||
{
|
||||
ASSERT_EQ(renderSizeWithoutUnit(0, SizeUnit::Base, true), " 0.0");
|
||||
ASSERT_EQ(renderSizeWithoutUnit(100, SizeUnit::Base, true), " 0.1");
|
||||
ASSERT_EQ(renderSizeWithoutUnit(100, SizeUnit::Base), "0.1");
|
||||
ASSERT_EQ(renderSizeWithoutUnit(972, SizeUnit::Base, true), " 0.9");
|
||||
ASSERT_EQ(renderSizeWithoutUnit(973, SizeUnit::Base, true), " 1.0"); // FIXME: should round down
|
||||
ASSERT_EQ(renderSizeWithoutUnit(1024, SizeUnit::Base, true), " 1.0");
|
||||
ASSERT_EQ(renderSizeWithoutUnit(-1024, SizeUnit::Base, true), " -1.0");
|
||||
ASSERT_EQ(renderSizeWithoutUnit(1024 * 1024, SizeUnit::Kilo, true), "1024.0");
|
||||
ASSERT_EQ(renderSizeWithoutUnit(1100 * 1024, SizeUnit::Mega, true), " 1.1");
|
||||
ASSERT_EQ(renderSizeWithoutUnit(2ULL * 1024 * 1024 * 1024, SizeUnit::Giga, true), " 2.0");
|
||||
ASSERT_EQ(renderSizeWithoutUnit(2100ULL * 1024 * 1024 * 1024, SizeUnit::Tera, true), " 2.1");
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* renderSize
|
||||
* --------------------------------------------------------------------------*/
|
||||
|
||||
@@ -114,4 +114,13 @@ struct adl_serializer<std::optional<T>>
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
static inline std::optional<T> ptrToOwned(const json * ptr)
|
||||
{
|
||||
if (ptr)
|
||||
return std::optional{*ptr};
|
||||
else
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace nlohmann
|
||||
|
||||
@@ -2,39 +2,61 @@
|
||||
///@file
|
||||
|
||||
#include "nix/util/error.hh"
|
||||
#include <variant>
|
||||
|
||||
namespace nix {
|
||||
|
||||
template<typename T>
|
||||
struct Cycle
|
||||
{
|
||||
T path;
|
||||
T parent;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
using TopoSortResult = std::variant<std::vector<T>, Cycle<T>>;
|
||||
|
||||
template<typename T, typename Compare>
|
||||
std::vector<T> topoSort(
|
||||
std::set<T, Compare> items,
|
||||
std::function<std::set<T, Compare>(const T &)> getChildren,
|
||||
std::function<Error(const T &, const T &)> makeCycleError)
|
||||
TopoSortResult<T> topoSort(std::set<T, Compare> items, std::function<std::set<T, Compare>(const T &)> getChildren)
|
||||
{
|
||||
std::vector<T> sorted;
|
||||
decltype(items) visited, parents;
|
||||
|
||||
auto dfsVisit = [&](this auto & dfsVisit, const T & path, const T * parent) {
|
||||
if (parents.count(path))
|
||||
throw makeCycleError(path, *parent);
|
||||
std::function<std::optional<Cycle<T>>(const T & path, const T * parent)> dfsVisit;
|
||||
|
||||
if (!visited.insert(path).second)
|
||||
return;
|
||||
dfsVisit = [&](const T & path, const T * parent) -> std::optional<Cycle<T>> {
|
||||
if (parents.count(path)) {
|
||||
return Cycle{path, *parent};
|
||||
}
|
||||
|
||||
if (!visited.insert(path).second) {
|
||||
return std::nullopt;
|
||||
}
|
||||
parents.insert(path);
|
||||
|
||||
auto references = getChildren(path);
|
||||
|
||||
for (auto & i : references)
|
||||
/* Don't traverse into items that don't exist in our starting set. */
|
||||
if (i != path && items.count(i))
|
||||
dfsVisit(i, &path);
|
||||
if (i != path && items.count(i)) {
|
||||
auto result = dfsVisit(i, &path);
|
||||
if (result.has_value()) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
sorted.push_back(path);
|
||||
parents.erase(path);
|
||||
|
||||
return std::nullopt;
|
||||
};
|
||||
|
||||
for (auto & i : items)
|
||||
dfsVisit(i, nullptr);
|
||||
for (auto & i : items) {
|
||||
auto cycle = dfsVisit(i, nullptr);
|
||||
if (cycle.has_value()) {
|
||||
return *cycle;
|
||||
}
|
||||
}
|
||||
|
||||
std::reverse(sorted.begin(), sorted.end());
|
||||
|
||||
|
||||
@@ -330,10 +330,13 @@ struct ParsedUrlScheme
|
||||
|
||||
ParsedUrlScheme parseUrlScheme(std::string_view scheme);
|
||||
|
||||
/* Detects scp-style uris (e.g. git@github.com:NixOS/nix) and fixes
|
||||
them by removing the `:` and assuming a scheme of `ssh://`. Also
|
||||
changes absolute paths into file:// URLs. */
|
||||
ParsedURL fixGitURL(const std::string & url);
|
||||
/**
|
||||
* Detects scp-style uris (e.g. `git@github.com:NixOS/nix`) and fixes
|
||||
* them by removing the `:` and assuming a scheme of `ssh://`. Also
|
||||
* drops `git+` from the scheme (e.g. `git+https://` to `https://`)
|
||||
* and changes absolute paths into `file://` URLs.
|
||||
*/
|
||||
ParsedURL fixGitURL(std::string url);
|
||||
|
||||
/**
|
||||
* Whether a string is valid as RFC 3986 scheme name.
|
||||
|
||||
@@ -99,6 +99,42 @@ N string2IntWithUnitPrefix(std::string_view s)
|
||||
throw UsageError("'%s' is not an integer", s);
|
||||
}
|
||||
|
||||
// Base also uses 'K', because it should also displayed as KiB => 100 Bytes => 0.1 KiB
|
||||
#define NIX_UTIL_SIZE_UNITS \
|
||||
NIX_UTIL_DEFINE_SIZE_UNIT(Base, 'K') \
|
||||
NIX_UTIL_DEFINE_SIZE_UNIT(Kilo, 'K') \
|
||||
NIX_UTIL_DEFINE_SIZE_UNIT(Mega, 'M') \
|
||||
NIX_UTIL_DEFINE_SIZE_UNIT(Giga, 'G') \
|
||||
NIX_UTIL_DEFINE_SIZE_UNIT(Tera, 'T') \
|
||||
NIX_UTIL_DEFINE_SIZE_UNIT(Peta, 'P') \
|
||||
NIX_UTIL_DEFINE_SIZE_UNIT(Exa, 'E') \
|
||||
NIX_UTIL_DEFINE_SIZE_UNIT(Zetta, 'Z') \
|
||||
NIX_UTIL_DEFINE_SIZE_UNIT(Yotta, 'Y')
|
||||
|
||||
enum class SizeUnit {
|
||||
#define NIX_UTIL_DEFINE_SIZE_UNIT(name, suffix) name,
|
||||
NIX_UTIL_SIZE_UNITS
|
||||
#undef NIX_UTIL_DEFINE_SIZE_UNIT
|
||||
};
|
||||
|
||||
constexpr inline auto sizeUnits = std::to_array<SizeUnit>({
|
||||
#define NIX_UTIL_DEFINE_SIZE_UNIT(name, suffix) SizeUnit::name,
|
||||
NIX_UTIL_SIZE_UNITS
|
||||
#undef NIX_UTIL_DEFINE_SIZE_UNIT
|
||||
});
|
||||
|
||||
SizeUnit getSizeUnit(int64_t value);
|
||||
|
||||
/**
|
||||
* Returns the unit if all values would be rendered using the same unit
|
||||
* otherwise returns `std::nullopt`.
|
||||
*/
|
||||
std::optional<SizeUnit> getCommonSizeUnit(std::initializer_list<int64_t> values);
|
||||
|
||||
std::string renderSizeWithoutUnit(int64_t value, SizeUnit unit, bool align = false);
|
||||
|
||||
char getSizeUnitSuffix(SizeUnit unit);
|
||||
|
||||
/**
|
||||
* Pretty-print a byte value, e.g. 12433615056 is rendered as `11.6
|
||||
* GiB`. If `align` is set, the number will be right-justified by
|
||||
|
||||
@@ -409,21 +409,23 @@ ParsedUrlScheme parseUrlScheme(std::string_view scheme)
|
||||
};
|
||||
}
|
||||
|
||||
ParsedURL fixGitURL(const std::string & url)
|
||||
ParsedURL fixGitURL(std::string url)
|
||||
{
|
||||
std::regex scpRegex("([^/]*)@(.*):(.*)");
|
||||
if (!hasPrefix(url, "/") && std::regex_match(url, scpRegex))
|
||||
return parseURL(std::regex_replace(url, scpRegex, "ssh://$1@$2/$3"));
|
||||
if (hasPrefix(url, "file:"))
|
||||
return parseURL(url);
|
||||
if (url.find("://") == std::string::npos) {
|
||||
url = std::regex_replace(url, scpRegex, "ssh://$1@$2/$3");
|
||||
if (!hasPrefix(url, "file:") && !hasPrefix(url, "git+file:") && url.find("://") == std::string::npos)
|
||||
return ParsedURL{
|
||||
.scheme = "file",
|
||||
.authority = ParsedURL::Authority{},
|
||||
.path = splitString<std::vector<std::string>>(url, "/"),
|
||||
};
|
||||
}
|
||||
return parseURL(url);
|
||||
auto parsed = parseURL(url);
|
||||
// Drop the superfluous "git+" from the scheme.
|
||||
auto scheme = parseUrlScheme(parsed.scheme);
|
||||
if (scheme.application == "git")
|
||||
parsed.scheme = scheme.transport;
|
||||
return parsed;
|
||||
}
|
||||
|
||||
// https://www.rfc-editor.org/rfc/rfc3986#section-3.1
|
||||
|
||||
@@ -132,17 +132,62 @@ std::optional<N> string2Float(const std::string_view s)
|
||||
template std::optional<double> string2Float<double>(const std::string_view s);
|
||||
template std::optional<float> string2Float<float>(const std::string_view s);
|
||||
|
||||
static const int64_t conversionNumber = 1024;
|
||||
|
||||
SizeUnit getSizeUnit(int64_t value)
|
||||
{
|
||||
auto unit = sizeUnits.begin();
|
||||
uint64_t absValue = std::abs(value);
|
||||
while (absValue > conversionNumber && unit < sizeUnits.end()) {
|
||||
unit++;
|
||||
absValue /= conversionNumber;
|
||||
}
|
||||
return *unit;
|
||||
}
|
||||
|
||||
std::optional<SizeUnit> getCommonSizeUnit(std::initializer_list<int64_t> values)
|
||||
{
|
||||
assert(values.size() > 0);
|
||||
|
||||
auto it = values.begin();
|
||||
SizeUnit unit = getSizeUnit(*it);
|
||||
it++;
|
||||
|
||||
for (; it != values.end(); it++) {
|
||||
if (unit != getSizeUnit(*it)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
return unit;
|
||||
}
|
||||
|
||||
std::string renderSizeWithoutUnit(int64_t value, SizeUnit unit, bool align)
|
||||
{
|
||||
// bytes should also displayed as KiB => 100 Bytes => 0.1 KiB
|
||||
auto power = std::max<std::underlying_type_t<SizeUnit>>(1, std::to_underlying(unit));
|
||||
double denominator = std::pow(conversionNumber, power);
|
||||
double result = (double) value / denominator;
|
||||
return fmt(align ? "%6.1f" : "%.1f", result);
|
||||
}
|
||||
|
||||
char getSizeUnitSuffix(SizeUnit unit)
|
||||
{
|
||||
switch (unit) {
|
||||
#define NIX_UTIL_DEFINE_SIZE_UNIT(name, suffix) \
|
||||
case SizeUnit::name: \
|
||||
return suffix;
|
||||
NIX_UTIL_SIZE_UNITS
|
||||
#undef NIX_UTIL_DEFINE_SIZE_UNIT
|
||||
}
|
||||
|
||||
assert(false);
|
||||
}
|
||||
|
||||
std::string renderSize(int64_t value, bool align)
|
||||
{
|
||||
static const std::array<char, 9> prefixes{{'K', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'}};
|
||||
size_t power = 0;
|
||||
double abs_value = std::abs(value);
|
||||
while (abs_value > 1024 && power < prefixes.size()) {
|
||||
++power;
|
||||
abs_value /= 1024;
|
||||
}
|
||||
double res = (double) value / std::pow(1024.0, power);
|
||||
return fmt(align ? "%6.1f %ciB" : "%.1f %ciB", power == 0 ? res / 1024 : res, prefixes.at(power));
|
||||
SizeUnit unit = getSizeUnit(value);
|
||||
return fmt("%s %ciB", renderSizeWithoutUnit(value, unit, align), getSizeUnitSuffix(unit));
|
||||
}
|
||||
|
||||
bool hasPrefix(std::string_view s, std::string_view prefix)
|
||||
|
||||
@@ -227,11 +227,13 @@ const static std::string getEnvSh =
|
||||
#include "get-env.sh.gen.hh"
|
||||
;
|
||||
|
||||
/* Given an existing derivation, return the shell environment as
|
||||
initialised by stdenv's setup script. We do this by building a
|
||||
modified derivation with the same dependencies and nearly the same
|
||||
initial environment variables, that just writes the resulting
|
||||
environment to a file and exits. */
|
||||
/**
|
||||
* Given an existing derivation, return the shell environment as
|
||||
* initialised by stdenv's setup script. We do this by building a
|
||||
* modified derivation with the same dependencies and nearly the same
|
||||
* initial environment variables, that just writes the resulting
|
||||
* environment to a file and exits.
|
||||
*/
|
||||
static StorePath getDerivationEnvironment(ref<Store> store, ref<Store> evalStore, const StorePath & drvPath)
|
||||
{
|
||||
auto drv = evalStore->derivationFromPath(drvPath);
|
||||
@@ -302,6 +304,8 @@ static StorePath getDerivationEnvironment(ref<Store> store, ref<Store> evalStore
|
||||
bmNormal,
|
||||
evalStore);
|
||||
|
||||
// `get-env.sh` will write its JSON output to an arbitrary output
|
||||
// path, so return the first non-empty output path.
|
||||
for (auto & [_0, optPath] : evalStore->queryPartialDerivationOutputMap(shellDrvPath)) {
|
||||
assert(optPath);
|
||||
auto accessor = evalStore->requireStoreObjectAccessor(*optPath);
|
||||
@@ -495,19 +499,18 @@ struct Common : InstallableCommand, MixProfile
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<BuildEnvironment, std::string> getBuildEnvironment(ref<Store> store, ref<Installable> installable)
|
||||
std::pair<BuildEnvironment, StorePath> getBuildEnvironment(ref<Store> store, ref<Installable> installable)
|
||||
{
|
||||
auto shellOutPath = getShellOutPath(store, installable);
|
||||
|
||||
auto strPath = store->printStorePath(shellOutPath);
|
||||
|
||||
updateProfile(shellOutPath);
|
||||
|
||||
debug("reading environment file '%s'", strPath);
|
||||
debug("reading environment file '%s'", store->printStorePath(shellOutPath));
|
||||
|
||||
return {
|
||||
BuildEnvironment::parseJSON(store->requireStoreObjectAccessor(shellOutPath)->readFile(CanonPath::root)),
|
||||
strPath};
|
||||
shellOutPath,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -634,7 +637,7 @@ struct CmdDevelop : Common, MixEnvironment
|
||||
|
||||
setEnviron();
|
||||
// prevent garbage collection until shell exits
|
||||
setEnv("NIX_GCROOT", gcroot.c_str());
|
||||
setEnv("NIX_GCROOT", store->printStorePath(gcroot).c_str());
|
||||
|
||||
Path shell = "bash";
|
||||
bool foundInteractive = false;
|
||||
|
||||
@@ -362,7 +362,7 @@ struct CmdFlakeCheck : FlakeCommand
|
||||
throw;
|
||||
} catch (Error & e) {
|
||||
if (settings.keepGoing) {
|
||||
ignoreExceptionExceptInterrupt();
|
||||
logError(e.info());
|
||||
hasErrors = true;
|
||||
} else
|
||||
throw;
|
||||
@@ -418,7 +418,7 @@ struct CmdFlakeCheck : FlakeCommand
|
||||
return std::nullopt;
|
||||
};
|
||||
|
||||
std::vector<DerivedPath> drvPaths;
|
||||
std::map<DerivedPath, std::vector<AttrPath>> attrPathsByDrv;
|
||||
|
||||
auto checkApp = [&](const std::string & attrPath, Value & v, const PosIdx pos) {
|
||||
try {
|
||||
@@ -616,7 +616,13 @@ struct CmdFlakeCheck : FlakeCommand
|
||||
.drvPath = makeConstantStorePathRef(*drvPath),
|
||||
.outputs = OutputsSpec::All{},
|
||||
};
|
||||
drvPaths.push_back(std::move(path));
|
||||
|
||||
// Build and store the attribute path for error reporting
|
||||
AttrPath attrPath;
|
||||
attrPath.push_back(AttrName(state->symbols.create(name)));
|
||||
attrPath.push_back(AttrName(attr.name));
|
||||
attrPath.push_back(AttrName(attr2.name));
|
||||
attrPathsByDrv[path].push_back(std::move(attrPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -780,7 +786,9 @@ struct CmdFlakeCheck : FlakeCommand
|
||||
});
|
||||
}
|
||||
|
||||
if (build && !drvPaths.empty()) {
|
||||
if (build && !attrPathsByDrv.empty()) {
|
||||
auto keys = std::views::keys(attrPathsByDrv);
|
||||
std::vector<DerivedPath> drvPaths(keys.begin(), keys.end());
|
||||
// TODO: This filtering of substitutable paths is a temporary workaround until
|
||||
// https://github.com/NixOS/nix/issues/5025 (union stores) is implemented.
|
||||
//
|
||||
@@ -804,7 +812,28 @@ struct CmdFlakeCheck : FlakeCommand
|
||||
}
|
||||
|
||||
Activity act(*logger, lvlInfo, actUnknown, fmt("running %d flake checks", toBuild.size()));
|
||||
store->buildPaths(toBuild);
|
||||
auto results = store->buildPathsWithResults(toBuild);
|
||||
|
||||
// Report build failures with attribute paths
|
||||
for (auto & result : results) {
|
||||
if (auto * failure = result.tryGetFailure()) {
|
||||
auto it = attrPathsByDrv.find(result.path);
|
||||
if (it != attrPathsByDrv.end() && !it->second.empty()) {
|
||||
for (auto & attrPath : it->second) {
|
||||
auto attrPathStr = showAttrPath(state->symbols, attrPath);
|
||||
reportError(Error(
|
||||
"failed to build attribute '%s', build of '%s' failed: %s",
|
||||
attrPathStr,
|
||||
result.path.to_string(*store),
|
||||
failure->errorMsg));
|
||||
}
|
||||
} else {
|
||||
// Derivation has no attribute path (e.g., a build dependency)
|
||||
reportError(
|
||||
Error("build of '%s' failed: %s", result.path.to_string(*store), failure->errorMsg));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasErrors)
|
||||
throw Error("some errors were encountered during the evaluation");
|
||||
|
||||
@@ -17,6 +17,7 @@ __functions="$(declare -F)"
|
||||
|
||||
__dumpEnv() {
|
||||
printf '{\n'
|
||||
printf ' "version": 1,\n'
|
||||
|
||||
printf ' "bashFunctions": {\n'
|
||||
local __first=1
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "nix/expr/eval-inline.hh"
|
||||
#include "nix/store/profiles.hh"
|
||||
#include "nix/expr/print-ambiguous.hh"
|
||||
#include "nix/expr/static-string-data.hh"
|
||||
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
@@ -56,7 +57,7 @@ bool createUserEnv(
|
||||
|
||||
auto attrs = state.buildBindings(7 + outputs.size());
|
||||
|
||||
attrs.alloc(state.s.type).mkStringNoCopy("derivation");
|
||||
attrs.alloc(state.s.type).mkStringNoCopy("derivation"_sds);
|
||||
attrs.alloc(state.s.name).mkString(i.queryName());
|
||||
auto system = i.querySystem();
|
||||
if (!system.empty())
|
||||
|
||||
@@ -51,7 +51,7 @@ static json pathInfoToJSON(Store & store, const StorePathSet & storePaths, bool
|
||||
// know the name yet until we've read the NAR info.
|
||||
printedStorePath = store.printStorePath(info->path);
|
||||
|
||||
jsonObject = info->toJSON(store, true, HashFormat::SRI);
|
||||
jsonObject = info->toJSON(store, true);
|
||||
|
||||
if (showClosureSize) {
|
||||
StorePathSet closure;
|
||||
|
||||
@@ -66,10 +66,16 @@ derivation' {
|
||||
outputChecks = {
|
||||
out = {
|
||||
allowedReferences = [ foo ];
|
||||
allowedRequisites = [ foo.dev ];
|
||||
allowedRequisites = [
|
||||
foo.dev
|
||||
"bin"
|
||||
];
|
||||
};
|
||||
bin = {
|
||||
disallowedReferences = [ bar ];
|
||||
disallowedReferences = [
|
||||
bar
|
||||
"dev"
|
||||
];
|
||||
disallowedRequisites = [ bar.dev ];
|
||||
};
|
||||
dev = {
|
||||
|
||||
@@ -58,8 +58,14 @@ derivation' {
|
||||
impureEnvVars = [ "UNICORN" ];
|
||||
__darwinAllowLocalNetworking = true;
|
||||
allowedReferences = [ foo ];
|
||||
allowedRequisites = [ foo.dev ];
|
||||
disallowedReferences = [ bar ];
|
||||
allowedRequisites = [
|
||||
foo.dev
|
||||
"bin"
|
||||
];
|
||||
disallowedReferences = [
|
||||
bar
|
||||
"dev"
|
||||
];
|
||||
disallowedRequisites = [ bar.dev ];
|
||||
requiredSystemFeatures = [
|
||||
"rainbow"
|
||||
|
||||
@@ -1 +1 @@
|
||||
Derive([("bin","","r:sha256",""),("dev","","r:sha256",""),("out","","r:sha256","")],[("/nix/store/j56sf12rxpcv5swr14vsjn5cwm6bj03h-foo.drv",["dev","out"]),("/nix/store/qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv",["dev","out"])],["/nix/store/qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv"],"my-system","/bin/bash",["-c","echo hello > $out"],[("__json","{\"__darwinAllowLocalNetworking\":true,\"__impureHostDeps\":[\"/usr/bin/ditto\"],\"__noChroot\":true,\"__sandboxProfile\":\"sandcastle\",\"allowSubstitutes\":false,\"builder\":\"/bin/bash\",\"exportReferencesGraph\":{\"refs1\":[\"/164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9\"],\"refs2\":[\"/nix/store/qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv\"]},\"impureEnvVars\":[\"UNICORN\"],\"name\":\"advanced-attributes-structured-attrs\",\"outputChecks\":{\"bin\":{\"disallowedReferences\":[\"/0nyw57wm2iicnm9rglvjmbci3ikmcp823czdqdzdcgsnnwqps71g\"],\"disallowedRequisites\":[\"/07f301yqyz8c6wf6bbbavb2q39j4n8kmcly1s09xadyhgy6x2wr8\"]},\"dev\":{\"maxClosureSize\":5909,\"maxSize\":789},\"out\":{\"allowedReferences\":[\"/164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9\"],\"allowedRequisites\":[\"/0nr45p69vn6izw9446wsh9bng9nndhvn19kpsm4n96a5mycw0s4z\"]}},\"outputHashAlgo\":\"sha256\",\"outputHashMode\":\"recursive\",\"outputs\":[\"out\",\"bin\",\"dev\"],\"preferLocalBuild\":true,\"requiredSystemFeatures\":[\"rainbow\",\"uid-range\"],\"system\":\"my-system\"}"),("bin","/04f3da1kmbr67m3gzxikmsl4vjz5zf777sv6m14ahv22r65aac9m"),("dev","/02qcpld1y6xhs5gz9bchpxaw0xdhmsp5dv88lh25r2ss44kh8dxz"),("out","/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9")])
|
||||
Derive([("bin","","r:sha256",""),("dev","","r:sha256",""),("out","","r:sha256","")],[("/nix/store/j56sf12rxpcv5swr14vsjn5cwm6bj03h-foo.drv",["dev","out"]),("/nix/store/qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv",["dev","out"])],["/nix/store/qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv"],"my-system","/bin/bash",["-c","echo hello > $out"],[("__json","{\"__darwinAllowLocalNetworking\":true,\"__impureHostDeps\":[\"/usr/bin/ditto\"],\"__noChroot\":true,\"__sandboxProfile\":\"sandcastle\",\"allowSubstitutes\":false,\"builder\":\"/bin/bash\",\"exportReferencesGraph\":{\"refs1\":[\"/164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9\"],\"refs2\":[\"/nix/store/qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv\"]},\"impureEnvVars\":[\"UNICORN\"],\"name\":\"advanced-attributes-structured-attrs\",\"outputChecks\":{\"bin\":{\"disallowedReferences\":[\"/0nyw57wm2iicnm9rglvjmbci3ikmcp823czdqdzdcgsnnwqps71g\",\"dev\"],\"disallowedRequisites\":[\"/07f301yqyz8c6wf6bbbavb2q39j4n8kmcly1s09xadyhgy6x2wr8\"]},\"dev\":{\"maxClosureSize\":5909,\"maxSize\":789},\"out\":{\"allowedReferences\":[\"/164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9\"],\"allowedRequisites\":[\"/0nr45p69vn6izw9446wsh9bng9nndhvn19kpsm4n96a5mycw0s4z\",\"bin\"]}},\"outputHashAlgo\":\"sha256\",\"outputHashMode\":\"recursive\",\"outputs\":[\"out\",\"bin\",\"dev\"],\"preferLocalBuild\":true,\"requiredSystemFeatures\":[\"rainbow\",\"uid-range\"],\"system\":\"my-system\"}"),("bin","/04f3da1kmbr67m3gzxikmsl4vjz5zf777sv6m14ahv22r65aac9m"),("dev","/02qcpld1y6xhs5gz9bchpxaw0xdhmsp5dv88lh25r2ss44kh8dxz"),("out","/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9")])
|
||||
@@ -1 +1 @@
|
||||
Derive([("out","","r:sha256","")],[("/nix/store/j56sf12rxpcv5swr14vsjn5cwm6bj03h-foo.drv",["dev","out"]),("/nix/store/qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv",["dev","out"])],["/nix/store/qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv"],"my-system","/bin/bash",["-c","echo hello > $out"],[("__darwinAllowLocalNetworking","1"),("__impureHostDeps","/usr/bin/ditto"),("__noChroot","1"),("__sandboxProfile","sandcastle"),("allowSubstitutes",""),("allowedReferences","/164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9"),("allowedRequisites","/0nr45p69vn6izw9446wsh9bng9nndhvn19kpsm4n96a5mycw0s4z"),("builder","/bin/bash"),("disallowedReferences","/0nyw57wm2iicnm9rglvjmbci3ikmcp823czdqdzdcgsnnwqps71g"),("disallowedRequisites","/07f301yqyz8c6wf6bbbavb2q39j4n8kmcly1s09xadyhgy6x2wr8"),("exportReferencesGraph","refs1 /164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9 refs2 /nix/store/qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv"),("impureEnvVars","UNICORN"),("name","advanced-attributes"),("out","/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9"),("outputHashAlgo","sha256"),("outputHashMode","recursive"),("preferLocalBuild","1"),("requiredSystemFeatures","rainbow uid-range"),("system","my-system")])
|
||||
Derive([("out","","r:sha256","")],[("/nix/store/j56sf12rxpcv5swr14vsjn5cwm6bj03h-foo.drv",["dev","out"]),("/nix/store/qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv",["dev","out"])],["/nix/store/qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv"],"my-system","/bin/bash",["-c","echo hello > $out"],[("__darwinAllowLocalNetworking","1"),("__impureHostDeps","/usr/bin/ditto"),("__noChroot","1"),("__sandboxProfile","sandcastle"),("allowSubstitutes",""),("allowedReferences","/164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9"),("allowedRequisites","/0nr45p69vn6izw9446wsh9bng9nndhvn19kpsm4n96a5mycw0s4z bin"),("builder","/bin/bash"),("disallowedReferences","/0nyw57wm2iicnm9rglvjmbci3ikmcp823czdqdzdcgsnnwqps71g dev"),("disallowedRequisites","/07f301yqyz8c6wf6bbbavb2q39j4n8kmcly1s09xadyhgy6x2wr8"),("exportReferencesGraph","refs1 /164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9 refs2 /nix/store/qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv"),("impureEnvVars","UNICORN"),("name","advanced-attributes"),("out","/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9"),("outputHashAlgo","sha256"),("outputHashMode","recursive"),("preferLocalBuild","1"),("requiredSystemFeatures","rainbow uid-range"),("system","my-system")])
|
||||
@@ -1 +1 @@
|
||||
Derive([("bin","/nix/store/33qms3h55wlaspzba3brlzlrm8m2239g-advanced-attributes-structured-attrs-bin","",""),("dev","/nix/store/wyfgwsdi8rs851wmy1xfzdxy7y5vrg5l-advanced-attributes-structured-attrs-dev","",""),("out","/nix/store/7cxy4zx1vqc885r4jl2l64pymqbdmhii-advanced-attributes-structured-attrs","","")],[("/nix/store/afc3vbjbzql750v2lp8gxgaxsajphzih-foo.drv",["dev","out"]),("/nix/store/vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv",["dev","out"])],["/nix/store/vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv"],"my-system","/bin/bash",["-c","echo hello > $out"],[("__json","{\"__darwinAllowLocalNetworking\":true,\"__impureHostDeps\":[\"/usr/bin/ditto\"],\"__noChroot\":true,\"__sandboxProfile\":\"sandcastle\",\"allowSubstitutes\":false,\"builder\":\"/bin/bash\",\"exportReferencesGraph\":{\"refs1\":[\"/nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo\"],\"refs2\":[\"/nix/store/vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv\"]},\"impureEnvVars\":[\"UNICORN\"],\"name\":\"advanced-attributes-structured-attrs\",\"outputChecks\":{\"bin\":{\"disallowedReferences\":[\"/nix/store/r5cff30838majxk5mp3ip2diffi8vpaj-bar\"],\"disallowedRequisites\":[\"/nix/store/9b61w26b4avv870dw0ymb6rw4r1hzpws-bar-dev\"]},\"dev\":{\"maxClosureSize\":5909,\"maxSize\":789},\"out\":{\"allowedReferences\":[\"/nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo\"],\"allowedRequisites\":[\"/nix/store/z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev\"]}},\"outputs\":[\"out\",\"bin\",\"dev\"],\"preferLocalBuild\":true,\"requiredSystemFeatures\":[\"rainbow\",\"uid-range\"],\"system\":\"my-system\"}"),("bin","/nix/store/33qms3h55wlaspzba3brlzlrm8m2239g-advanced-attributes-structured-attrs-bin"),("dev","/nix/store/wyfgwsdi8rs851wmy1xfzdxy7y5vrg5l-advanced-attributes-structured-attrs-dev"),("out","/nix/store/7cxy4zx1vqc885r4jl2l64pymqbdmhii-advanced-attributes-structured-attrs")])
|
||||
Derive([("bin","/nix/store/cnpasdljgkhnwaf78cf3qygcp4qbki1c-advanced-attributes-structured-attrs-bin","",""),("dev","/nix/store/ijq6mwpa9jbnpnl33qldfqihrr38kprx-advanced-attributes-structured-attrs-dev","",""),("out","/nix/store/h1vh648d3p088kdimy0r8ngpfx7c3nzw-advanced-attributes-structured-attrs","","")],[("/nix/store/afc3vbjbzql750v2lp8gxgaxsajphzih-foo.drv",["dev","out"]),("/nix/store/vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv",["dev","out"])],["/nix/store/vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv"],"my-system","/bin/bash",["-c","echo hello > $out"],[("__json","{\"__darwinAllowLocalNetworking\":true,\"__impureHostDeps\":[\"/usr/bin/ditto\"],\"__noChroot\":true,\"__sandboxProfile\":\"sandcastle\",\"allowSubstitutes\":false,\"builder\":\"/bin/bash\",\"exportReferencesGraph\":{\"refs1\":[\"/nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo\"],\"refs2\":[\"/nix/store/vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv\"]},\"impureEnvVars\":[\"UNICORN\"],\"name\":\"advanced-attributes-structured-attrs\",\"outputChecks\":{\"bin\":{\"disallowedReferences\":[\"/nix/store/r5cff30838majxk5mp3ip2diffi8vpaj-bar\",\"dev\"],\"disallowedRequisites\":[\"/nix/store/9b61w26b4avv870dw0ymb6rw4r1hzpws-bar-dev\"]},\"dev\":{\"maxClosureSize\":5909,\"maxSize\":789},\"out\":{\"allowedReferences\":[\"/nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo\"],\"allowedRequisites\":[\"/nix/store/z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev\",\"bin\"]}},\"outputs\":[\"out\",\"bin\",\"dev\"],\"preferLocalBuild\":true,\"requiredSystemFeatures\":[\"rainbow\",\"uid-range\"],\"system\":\"my-system\"}"),("bin","/nix/store/cnpasdljgkhnwaf78cf3qygcp4qbki1c-advanced-attributes-structured-attrs-bin"),("dev","/nix/store/ijq6mwpa9jbnpnl33qldfqihrr38kprx-advanced-attributes-structured-attrs-dev"),("out","/nix/store/h1vh648d3p088kdimy0r8ngpfx7c3nzw-advanced-attributes-structured-attrs")])
|
||||
@@ -1 +1 @@
|
||||
Derive([("out","/nix/store/wyhpwd748pns4k7svh48wdrc8kvjk0ra-advanced-attributes","","")],[("/nix/store/afc3vbjbzql750v2lp8gxgaxsajphzih-foo.drv",["dev","out"]),("/nix/store/vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv",["dev","out"])],["/nix/store/vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv"],"my-system","/bin/bash",["-c","echo hello > $out"],[("__darwinAllowLocalNetworking","1"),("__impureHostDeps","/usr/bin/ditto"),("__noChroot","1"),("__sandboxProfile","sandcastle"),("allowSubstitutes",""),("allowedReferences","/nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo"),("allowedRequisites","/nix/store/z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev"),("builder","/bin/bash"),("disallowedReferences","/nix/store/r5cff30838majxk5mp3ip2diffi8vpaj-bar"),("disallowedRequisites","/nix/store/9b61w26b4avv870dw0ymb6rw4r1hzpws-bar-dev"),("exportReferencesGraph","refs1 /nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo refs2 /nix/store/vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv"),("impureEnvVars","UNICORN"),("name","advanced-attributes"),("out","/nix/store/wyhpwd748pns4k7svh48wdrc8kvjk0ra-advanced-attributes"),("preferLocalBuild","1"),("requiredSystemFeatures","rainbow uid-range"),("system","my-system")])
|
||||
Derive([("out","/nix/store/ymqmybkq5j4nd1xplw6ccdpbjnfi017v-advanced-attributes","","")],[("/nix/store/afc3vbjbzql750v2lp8gxgaxsajphzih-foo.drv",["dev","out"]),("/nix/store/vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv",["dev","out"])],["/nix/store/vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv"],"my-system","/bin/bash",["-c","echo hello > $out"],[("__darwinAllowLocalNetworking","1"),("__impureHostDeps","/usr/bin/ditto"),("__noChroot","1"),("__sandboxProfile","sandcastle"),("allowSubstitutes",""),("allowedReferences","/nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo"),("allowedRequisites","/nix/store/z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev bin"),("builder","/bin/bash"),("disallowedReferences","/nix/store/r5cff30838majxk5mp3ip2diffi8vpaj-bar dev"),("disallowedRequisites","/nix/store/9b61w26b4avv870dw0ymb6rw4r1hzpws-bar-dev"),("exportReferencesGraph","refs1 /nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo refs2 /nix/store/vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv"),("impureEnvVars","UNICORN"),("name","advanced-attributes"),("out","/nix/store/ymqmybkq5j4nd1xplw6ccdpbjnfi017v-advanced-attributes"),("preferLocalBuild","1"),("requiredSystemFeatures","rainbow uid-range"),("system","my-system")])
|
||||
@@ -192,3 +192,24 @@ EOF
|
||||
# shellcheck disable=SC2015
|
||||
checkRes=$(nix flake check "$flakeDir" 2>&1 && fail "nix flake check should have failed" || true)
|
||||
echo "$checkRes" | grepQuiet -E "builder( for .*)? failed with exit code 1"
|
||||
|
||||
# Test that attribute paths are shown in error messages
|
||||
cat > "$flakeDir"/flake.nix <<EOF
|
||||
{
|
||||
outputs = { self }: with import ./config.nix; {
|
||||
checks.${system}.failingCheck = mkDerivation {
|
||||
name = "failing-check";
|
||||
buildCommand = "echo 'This check fails'; exit 1";
|
||||
};
|
||||
checks.${system}.anotherFailingCheck = mkDerivation {
|
||||
name = "another-failing-check";
|
||||
buildCommand = "echo 'This also fails'; exit 1";
|
||||
};
|
||||
};
|
||||
}
|
||||
EOF
|
||||
|
||||
# shellcheck disable=SC2015
|
||||
checkRes=$(nix flake check --keep-going "$flakeDir" 2>&1 && fail "nix flake check should have failed" || true)
|
||||
echo "$checkRes" | grepQuiet "checks.${system}.failingCheck"
|
||||
echo "$checkRes" | grepQuiet "checks.${system}.anotherFailingCheck"
|
||||
|
||||
@@ -17,8 +17,16 @@ diff --unified --color=always \
|
||||
jq --sort-keys 'map_values(.narHash)') \
|
||||
<(jq --sort-keys <<-EOF
|
||||
{
|
||||
"$foo": "sha256-QvtAMbUl/uvi+LCObmqOhvNOapHdA2raiI4xG5zI5pA=",
|
||||
"$bar": "sha256-9fhYGu9fqxcQC2Kc81qh2RMo1QcLBUBo8U+pPn+jthQ=",
|
||||
"$foo": {
|
||||
"algorithm": "sha256",
|
||||
"format": "base64",
|
||||
"hash": "QvtAMbUl/uvi+LCObmqOhvNOapHdA2raiI4xG5zI5pA="
|
||||
},
|
||||
"$bar": {
|
||||
"algorithm": "sha256",
|
||||
"format": "base64",
|
||||
"hash": "9fhYGu9fqxcQC2Kc81qh2RMo1QcLBUBo8U+pPn+jthQ="
|
||||
},
|
||||
"$baz": null
|
||||
}
|
||||
EOF
|
||||
|
||||
Reference in New Issue
Block a user