Compare commits

..

3 Commits

Author SHA1 Message Date
John Ericson
156061409a Do some parsing of git hashes based on length.
Note that 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.
2025-11-09 18:52:35 -05:00
John Ericson
6da56d7ed4 Make changes to support libgit2 experimental API
This experimental interface is likely to become stable with libgit2 2.0.
2025-11-09 18:52:35 -05:00
John Ericson
9846305fee Switch to libgit2 experimental
Just patch the package for now, but we should make Nixpkgs provide what
we need (whether that the experimental variant, or a 2.x release with
the new API made non-experimental) before releasing this change.
2025-11-09 18:52:35 -05:00
85 changed files with 501 additions and 1384 deletions

View File

@@ -22,15 +22,7 @@ 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
- **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.
Version 1 format is still accepted when reading for backward compatibility.
**Affected command**: `nix path-info --json`

View File

@@ -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:
"$ref": "./hash-v1.yaml"
type: string
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:
"$ref": "./hash-v1.yaml"
type: string
title: Download Hash
description: |
A digest for the compressed archive itself, as opposed to the data contained within.

View File

@@ -74,4 +74,14 @@ 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/'
'';
});
}

View File

@@ -104,7 +104,6 @@ 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;

View File

@@ -1,6 +1,5 @@
#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
@@ -55,7 +54,7 @@ TEST_F(JSONValueTest, IntNegative)
TEST_F(JSONValueTest, String)
{
Value v;
v.mkStringNoCopy("test"_sds);
v.mkStringNoCopy("test");
ASSERT_EQ(getJSONValue(v), "\"test\"");
}
@@ -63,7 +62,7 @@ TEST_F(JSONValueTest, StringQuotes)
{
Value v;
v.mkStringNoCopy("test\""_sds);
v.mkStringNoCopy("test\"");
ASSERT_EQ(getJSONValue(v), "\"test\\\"\"");
}

View File

@@ -1,5 +1,4 @@
#include "nix/expr/tests/libexpr.hh"
#include "nix/expr/static-string-data.hh"
#include "nix/expr/value.hh"
#include "nix/expr/print.hh"
@@ -36,14 +35,14 @@ TEST_F(ValuePrintingTests, tBool)
TEST_F(ValuePrintingTests, tString)
{
Value vString;
vString.mkStringNoCopy("some-string"_sds);
vString.mkStringNoCopy("some-string");
test(vString, "\"some-string\"");
}
TEST_F(ValuePrintingTests, tPath)
{
Value vPath;
vPath.mkStringNoCopy("/foo"_sds);
vPath.mkStringNoCopy("/foo");
test(vPath, "\"/foo\"");
}
@@ -290,10 +289,10 @@ TEST_F(StringPrintingTests, maxLengthTruncation)
TEST_F(ValuePrintingTests, attrsTypeFirst)
{
Value vType;
vType.mkStringNoCopy("puppy"_sds);
vType.mkStringNoCopy("puppy");
Value vApple;
vApple.mkStringNoCopy("apple"_sds);
vApple.mkStringNoCopy("apple");
BindingsBuilder builder = state.buildBindings(10);
builder.insert(state.symbols.create("type"), &vType);
@@ -334,7 +333,7 @@ TEST_F(ValuePrintingTests, ansiColorsBool)
TEST_F(ValuePrintingTests, ansiColorsString)
{
Value v;
v.mkStringNoCopy("puppy"_sds);
v.mkStringNoCopy("puppy");
test(v, ANSI_MAGENTA "\"puppy\"" ANSI_NORMAL, PrintOptions{.ansiColors = true});
}
@@ -342,7 +341,7 @@ TEST_F(ValuePrintingTests, ansiColorsString)
TEST_F(ValuePrintingTests, ansiColorsStringElided)
{
Value v;
v.mkStringNoCopy("puppy"_sds);
v.mkStringNoCopy("puppy");
test(
v,
@@ -390,7 +389,7 @@ TEST_F(ValuePrintingTests, ansiColorsAttrs)
TEST_F(ValuePrintingTests, ansiColorsDerivation)
{
Value vDerivation;
vDerivation.mkStringNoCopy("derivation"_sds);
vDerivation.mkStringNoCopy("derivation");
BindingsBuilder builder = state.buildBindings(10);
builder.insert(state.s.type, &vDerivation);
@@ -413,7 +412,7 @@ TEST_F(ValuePrintingTests, ansiColorsError)
{
Value throw_ = state.getBuiltin("throw");
Value message;
message.mkStringNoCopy("uh oh!"_sds);
message.mkStringNoCopy("uh oh!");
Value vError;
vError.mkApp(&throw_, &message);
@@ -430,12 +429,12 @@ TEST_F(ValuePrintingTests, ansiColorsDerivationError)
{
Value throw_ = state.getBuiltin("throw");
Value message;
message.mkStringNoCopy("uh oh!"_sds);
message.mkStringNoCopy("uh oh!");
Value vError;
vError.mkApp(&throw_, &message);
Value vDerivation;
vDerivation.mkStringNoCopy("derivation"_sds);
vDerivation.mkStringNoCopy("derivation");
BindingsBuilder builder = state.buildBindings(10);
builder.insert(state.s.type, &vDerivation);

View File

@@ -1,5 +1,4 @@
#include "nix/expr/value.hh"
#include "nix/expr/static-string-data.hh"
#include "nix/store/tests/libstore.hh"
#include <gtest/gtest.h>
@@ -28,17 +27,17 @@ TEST_F(ValueTest, staticString)
{
Value vStr1;
Value vStr2;
vStr1.mkStringNoCopy("foo"_sds);
vStr2.mkStringNoCopy("foo"_sds);
vStr1.mkStringNoCopy("foo");
vStr2.mkStringNoCopy("foo");
auto & sd1 = vStr1.string_data();
auto & sd2 = vStr2.string_data();
auto sd1 = vStr1.string_view();
auto sd2 = vStr2.string_view();
// The strings should be the same
ASSERT_EQ(sd1.view(), sd2.view());
ASSERT_EQ(sd1, sd2);
// The strings should also be backed by the same (static) allocation
ASSERT_EQ(&sd1, &sd2);
ASSERT_EQ(sd1.data(), sd2.data());
}
} // namespace nix

View File

@@ -147,7 +147,7 @@ struct AttrDb
for (auto * elem : *context) {
if (!first)
ctx.push_back(' ');
ctx.append(elem->view());
ctx.append(elem);
first = false;
}
state->insertAttributeWithContext.use()(key.first)(symbols[key.second])(AttrType::String) (s) (ctx)

View File

@@ -3,7 +3,6 @@
#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"
@@ -29,8 +28,6 @@
#include "parser-tab.hh"
#include <algorithm>
#include <cstddef>
#include <cstdlib>
#include <iostream>
#include <sstream>
#include <cstring>
@@ -51,9 +48,6 @@ using json = nlohmann::json;
namespace nix {
/**
* Just for doc strings. Not for regular string values.
*/
static char * allocString(size_t size)
{
char * t;
@@ -67,9 +61,6 @@ 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();
@@ -81,25 +72,6 @@ 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);
@@ -613,9 +585,7 @@ 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 = {},
/* 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()),
.doc = makeImmutableString(s.view()), // NOTE: memory leak when compiled without GC
};
}
if (isFunctor(v)) {
@@ -849,7 +819,7 @@ DebugTraceStacker::DebugTraceStacker(EvalState & evalState, DebugTrace t)
void Value::mkString(std::string_view s)
{
mkStringNoCopy(StringData::make(s));
mkStringNoCopy(makeImmutableString(s));
}
Value::StringWithContext::Context * Value::StringWithContext::Context::fromBuilder(const NixStringContext & context)
@@ -859,23 +829,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 &StringData::make(elt.to_string()); });
context, ctx->elems, [](const NixStringContextElem & elt) { return makeImmutableString(elt.to_string()); });
return ctx;
}
void Value::mkString(std::string_view s, const NixStringContext & context)
{
mkStringNoCopy(StringData::make(s), Value::StringWithContext::Context::fromBuilder(context));
mkStringNoCopy(makeImmutableString(s), Value::StringWithContext::Context::fromBuilder(context));
}
void Value::mkStringMove(const StringData & s, const NixStringContext & context)
void Value::mkStringMove(const char * s, const NixStringContext & context)
{
mkStringNoCopy(s, Value::StringWithContext::Context::fromBuilder(context));
}
void Value::mkPath(const SourcePath & path)
{
mkPath(&*path.accessor, StringData::make(path.path.abs()));
mkPath(&*path.accessor, makeImmutableString(path.path.abs()));
}
inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
@@ -2129,21 +2099,21 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
.atPos(pos)
.withFrame(env, *this)
.debugThrow();
std::string resultStr;
resultStr.reserve(sSize);
std::string result_str;
result_str.reserve(sSize);
for (const auto & part : strings) {
resultStr += *part;
result_str += *part;
}
v.mkPath(state.rootPath(CanonPath(resultStr)));
v.mkPath(state.rootPath(CanonPath(result_str)));
} else {
auto & resultStr = StringData::alloc(sSize);
auto * tmp = resultStr.data();
char * result_str = allocString(sSize + 1);
char * tmp = result_str;
for (const auto & part : strings) {
std::memcpy(tmp, part->data(), part->size());
memcpy(tmp, part->data(), part->size());
tmp += part->size();
}
*tmp = '\0';
v.mkStringMove(resultStr, context);
*tmp = 0;
v.mkStringMove(result_str, context);
}
}
@@ -2318,7 +2288,7 @@ void copyContext(const Value & v, NixStringContext & context, const Experimental
{
if (auto * ctx = v.context())
for (auto * elem : *ctx)
context.insert(NixStringContextElem::parse(elem->view(), xpSettings));
context.insert(NixStringContextElem::parse(elem, xpSettings));
}
std::string_view EvalState::forceString(

View File

@@ -31,7 +31,6 @@ 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',

View File

@@ -3,7 +3,6 @@
#include <map>
#include <span>
#include <memory>
#include <vector>
#include <memory_resource>
#include <algorithm>
@@ -12,7 +11,6 @@
#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"
@@ -188,18 +186,22 @@ 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 StringData & s)
ExprString(const char * s)
{
v.mkStringNoCopy(s);
};
ExprString(std::pmr::polymorphic_allocator<char> & alloc, std::string_view sv)
{
if (sv.size() == 0) {
v.mkStringNoCopy(""_sds);
auto len = sv.length();
if (len == 0) {
v.mkStringNoCopy("");
return;
}
v.mkStringNoCopy(StringData::make(*alloc.resource(), sv));
char * s = alloc.allocate(len + 1);
sv.copy(s, len);
s[len] = '\0';
v.mkStringNoCopy(s);
};
Value * maybeThunk(EvalState & state, Env & env) override;
@@ -214,7 +216,11 @@ struct ExprPath : Expr
ExprPath(std::pmr::polymorphic_allocator<char> & alloc, ref<SourceAccessor> accessor, std::string_view sv)
: accessor(accessor)
{
v.mkPath(&*accessor, StringData::make(*alloc.resource(), sv));
auto len = sv.length();
char * s = alloc.allocate(len + 1);
sv.copy(s, len);
s[len] = '\0';
v.mkPath(&*accessor, s);
}
Value * maybeThunk(EvalState & state, Env & env) override;

View File

@@ -4,8 +4,6 @@
#include <limits>
#include "nix/expr/eval.hh"
#include "nix/expr/value.hh"
#include "nix/expr/static-string-data.hh"
namespace nix {
@@ -242,7 +240,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>(""_sds);
return exprs.add<ExprString>("");
/* Figure out the minimum indentation. Note that by design
whitespace-only final lines are not taken into account. (So
@@ -334,7 +332,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>(""_sds);
auto * const result = exprs.add<ExprString>("");
return result;
}

View File

@@ -1,44 +0,0 @@
#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

View File

@@ -3,7 +3,6 @@
#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"
@@ -17,6 +16,7 @@ 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 string_view();
return {c_str(), size_};
}
};
@@ -96,13 +96,13 @@ class SymbolStr
SymbolValueStore & store;
std::string_view s;
std::size_t hash;
std::pmr::memory_resource & resource;
std::pmr::polymorphic_allocator<char> & alloc;
Key(SymbolValueStore & store, std::string_view s, std::pmr::memory_resource & stringMemory)
Key(SymbolValueStore & store, std::string_view s, std::pmr::polymorphic_allocator<char> & stringAlloc)
: store(store)
, s(s)
, hash(HashType{}(s))
, resource(stringMemory)
, alloc(stringAlloc)
{
}
};
@@ -122,10 +122,14 @@ public:
// for multi-threaded implementations: lock store and allocator here
const auto & [v, idx] = key.store.add(SymbolValue{});
if (size == 0) {
v.mkStringNoCopy(""_sds, nullptr);
v.mkStringNoCopy("", nullptr);
} else {
v.mkStringNoCopy(StringData::make(key.resource, key.s));
auto s = key.alloc.allocate(size + 1);
memcpy(s, key.s.data(), size);
s[size] = '\0';
v.mkStringNoCopy(s, nullptr);
}
v.size_ = size;
v.idx = idx;
this->s = &v;
}
@@ -135,12 +139,6 @@ 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
{
@@ -157,17 +155,13 @@ public:
[[gnu::always_inline]]
bool empty() const noexcept
{
auto * p = &s->string_data();
// Save a dereference in the sentinel value case
if (p == &""_sds)
return true;
return p->size() == 0;
return s->size_ == 0;
}
[[gnu::always_inline]]
size_t size() const noexcept
{
return s->string_data().size();
return s->size_;
}
[[gnu::always_inline]]
@@ -265,6 +259,7 @@ 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};
/**
@@ -287,7 +282,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, buffer}).first);
return Symbol(*symbols.insert(SymbolStr::Key{store, s, stringAlloc}).first);
}
std::vector<SymbolStr> resolve(const std::vector<Symbol> & symbols) const

View File

@@ -1,14 +1,8 @@
#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>
@@ -192,91 +186,6 @@ 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 {
/**
@@ -310,7 +219,7 @@ struct ValueBase
*/
struct StringWithContext
{
const StringData * str;
const char * c_str;
/**
* The type of the context itself.
@@ -325,7 +234,7 @@ struct ValueBase
*/
struct Context
{
using value_type = const StringData *;
using value_type = const char *;
using size_type = std::size_t;
using iterator = const value_type *;
@@ -376,7 +285,7 @@ struct ValueBase
struct Path
{
SourceAccessor * accessor;
const StringData * path;
const char * path;
};
struct Null
@@ -737,13 +646,13 @@ protected:
void getStorage(StringWithContext & string) const noexcept
{
string.context = untagPointer<decltype(string.context)>(payload[0]);
string.str = std::bit_cast<const StringData *>(payload[1]);
string.c_str = std::bit_cast<const char *>(payload[1]);
}
void getStorage(Path & path) const noexcept
{
path.accessor = untagPointer<decltype(path.accessor)>(payload[0]);
path.path = std::bit_cast<const StringData *>(payload[1]);
path.path = std::bit_cast<const char *>(payload[1]);
}
void setStorage(NixInt integer) noexcept
@@ -788,7 +697,7 @@ protected:
void setStorage(StringWithContext string) noexcept
{
setUntaggablePayload<pdString>(string.context, string.str);
setUntaggablePayload<pdString>(string.context, string.c_str);
}
void setStorage(Path path) noexcept
@@ -1141,22 +1050,22 @@ public:
setStorage(b);
}
void mkStringNoCopy(const StringData & s, const Value::StringWithContext::Context * context = nullptr) noexcept
void mkStringNoCopy(const char * s, const Value::StringWithContext::Context * context = nullptr) noexcept
{
setStorage(StringWithContext{.str = &s, .context = context});
setStorage(StringWithContext{.c_str = s, .context = context});
}
void mkString(std::string_view s);
void mkString(std::string_view s, const NixStringContext & context);
void mkStringMove(const StringData & s, const NixStringContext & context);
void mkStringMove(const char * s, const NixStringContext & context);
void mkPath(const SourcePath & path);
inline void mkPath(SourceAccessor * accessor, const StringData & path) noexcept
inline void mkPath(SourceAccessor * accessor, const char * path) noexcept
{
setStorage(Path{.accessor = accessor, .path = &path});
setStorage(Path{.accessor = accessor, .path = path});
}
inline void mkNull() noexcept
@@ -1254,23 +1163,17 @@ public:
SourcePath path() const
{
return SourcePath(
ref(pathAccessor()->shared_from_this()), CanonPath(CanonPath::unchecked_t(), std::string(pathStrView())));
}
const StringData & string_data() const noexcept
{
return *getStorage<StringWithContext>().str;
}
const char * c_str() const noexcept
{
return getStorage<StringWithContext>().str->data();
return SourcePath(ref(pathAccessor()->shared_from_this()), CanonPath(CanonPath::unchecked_t(), pathStr()));
}
std::string_view string_view() const noexcept
{
return string_data().view();
return std::string_view{getStorage<StringWithContext>().c_str};
}
const char * c_str() const noexcept
{
return getStorage<StringWithContext>().c_str;
}
const Value::StringWithContext::Context * context() const noexcept
@@ -1330,12 +1233,12 @@ public:
const char * pathStr() const noexcept
{
return getStorage<Path>().path->c_str();
return getStorage<Path>().path;
}
std::string_view pathStrView() const noexcept
{
return getStorage<Path>().path->view();
return std::string_view{getStorage<Path>().path};
}
SourceAccessor * pathAccessor() const noexcept

View File

@@ -5,7 +5,6 @@
#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"
@@ -488,34 +487,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"_sds);
v.mkStringNoCopy("int");
break;
case nBool:
v.mkStringNoCopy("bool"_sds);
v.mkStringNoCopy("bool");
break;
case nString:
v.mkStringNoCopy("string"_sds);
v.mkStringNoCopy("string");
break;
case nPath:
v.mkStringNoCopy("path"_sds);
v.mkStringNoCopy("path");
break;
case nNull:
v.mkStringNoCopy("null"_sds);
v.mkStringNoCopy("null");
break;
case nAttrs:
v.mkStringNoCopy("set"_sds);
v.mkStringNoCopy("set");
break;
case nList:
v.mkStringNoCopy("list"_sds);
v.mkStringNoCopy("list");
break;
case nFunction:
v.mkStringNoCopy("lambda"_sds);
v.mkStringNoCopy("lambda");
break;
case nExternal:
v.mkString(args[0]->external()->typeOf());
break;
case nFloat:
v.mkStringNoCopy("float"_sds);
v.mkStringNoCopy("float");
break;
case nThunk:
unreachable();
@@ -2025,9 +2024,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("."_sds, context);
v.mkStringMove(".", context);
else if (pos == 0)
v.mkStringMove("/"_sds, context);
v.mkStringMove("/", context);
else
v.mkString(path->substr(0, pos), context);
}
@@ -2310,10 +2309,10 @@ static const Value & fileTypeToString(EvalState & state, SourceAccessor::Type ty
static const Constants stringValues = []() {
Constants res;
res.regular.mkStringNoCopy("regular"_sds);
res.directory.mkStringNoCopy("directory"_sds);
res.symlink.mkStringNoCopy("symlink"_sds);
res.unknown.mkStringNoCopy("unknown"_sds);
res.regular.mkStringNoCopy("regular");
res.directory.mkStringNoCopy("directory");
res.symlink.mkStringNoCopy("symlink");
res.unknown.mkStringNoCopy("unknown");
return res;
}();
@@ -4464,7 +4463,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(""_sds, args[2]->context());
v.mkStringNoCopy("", args[2]->context());
return;
}
}

View File

@@ -1,6 +1,5 @@
#include "nix/expr/primops.hh"
#include "nix/expr/eval-inline.hh"
#include "nix/expr/static-string-data.hh"
#include "expr-config-private.hh"
@@ -137,7 +136,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"_sds);
attrs.alloc("_type").mkStringNoCopy("timestamp");
std::ostringstream s;
s << t;
auto str = s.view();

View File

@@ -1,19 +1,19 @@
#include "nix/fetchers/git-utils.hh"
#include "nix/util/file-system.hh"
#include <gmock/gmock.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 <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 <gtest/gtest.h>
#include "nix/util/fs-sink.hh"
#include "nix/util/serialise.hh"
#include "nix/fetchers/git-lfs-fetch.hh"
#include <git2/blob.h>
#include <git2/tree.h>
#include <git2-experimental/blob.h>
#include <git2-experimental/tree.h>
namespace nix {

View File

@@ -5,7 +5,7 @@
#include "nix/fetchers/fetchers.hh"
#include "nix/fetchers/git-utils.hh"
#include <git2.h>
#include <git2-experimental.h>
#include <gtest/gtest.h>
#include <filesystem>

View File

@@ -1,61 +0,0 @@
#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

View File

@@ -33,7 +33,7 @@ deps_private += rapidcheck
gtest = dependency('gtest', main : true)
deps_private += gtest
libgit2 = dependency('libgit2')
libgit2 = dependency('libgit2-experimental')
deps_private += libgit2
subdir('nix-meson-build-support/common')
@@ -42,7 +42,6 @@ sources = files(
'access-tokens.cc',
'git-utils.cc',
'git.cc',
'input.cc',
'nix_api_fetchers.cc',
'public-key.cc',
)

View File

@@ -1,5 +1,6 @@
#include "nix/fetchers/attrs.hh"
#include "nix/fetchers/fetchers.hh"
#include "nix/fetchers/git-utils.hh"
#include <nlohmann/json.hpp>
@@ -111,7 +112,7 @@ StringMap attrsToQuery(const Attrs & attrs)
Hash getRevAttr(const Attrs & attrs, const std::string & name)
{
return Hash::parseAny(getStrAttr(attrs, name), HashAlgorithm::SHA1);
return parseGitHash(getStrAttr(attrs, name));
}
} // namespace nix::fetchers

View File

@@ -447,6 +447,9 @@ 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);
}
}

View File

@@ -7,10 +7,10 @@
#include "nix/util/hash.hh"
#include "nix/store/ssh.hh"
#include <git2/attr.h>
#include <git2/config.h>
#include <git2/errors.h>
#include <git2/remote.h>
#include <git2-experimental/attr.h>
#include <git2-experimental/config.h>
#include <git2-experimental/errors.h>
#include <git2-experimental/remote.h>
#include <nlohmann/json.hpp>

View File

@@ -13,27 +13,27 @@
#include "nix/util/thread-pool.hh"
#include "nix/util/pool.hh"
#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 <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 <boost/unordered/concurrent_flat_set.hpp>
#include <boost/unordered/unordered_flat_map.hpp>
@@ -91,10 +91,21 @@ typedef std::unique_ptr<git_indexer, Deleter<git_indexer_free>> Indexer;
Hash toHash(const git_oid & oid)
{
#ifdef GIT_EXPERIMENTAL_SHA256
assert(oid.type == GIT_OID_SHA1);
#endif
Hash hash(HashAlgorithm::SHA1);
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);
memcpy(hash.hash, oid.id, hash.hashSize);
return hash;
}
@@ -111,7 +122,21 @@ static void initLibGit2()
git_oid hashToOID(const Hash & hash)
{
git_oid oid;
if (git_oid_fromstr(&oid, hash.gitRev().c_str()))
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))
throw Error("cannot convert '%s' to a Git OID", hash.gitRev());
return oid;
}
@@ -304,7 +329,8 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
// (synchronously on the git_packbuilder_write_buf thread)
Indexer indexer;
git_indexer_progress stats;
if (git_indexer_new(Setter(indexer), pack_dir_path.c_str(), 0, nullptr, nullptr))
git_indexer_options indexer_opts = GIT_INDEXER_OPTIONS_INIT;
if (git_indexer_new(Setter(indexer), pack_dir_path.c_str(), &indexer_opts))
throw Error("creating git packfile indexer: %s", git_error_last()->message);
// TODO: provide index callback for checkInterrupt() termination
@@ -1328,17 +1354,12 @@ std::vector<std::tuple<GitRepoImpl::Submodule, Hash>> GitRepoImpl::getSubmodules
return result;
}
namespace fetchers {
ref<GitRepo> Settings::getTarballCache() const
ref<GitRepo> getTarballCache()
{
auto tarballCache(_tarballCache.lock());
if (!*tarballCache)
*tarballCache = GitRepo::openRepo(std::filesystem::path(getCacheDir()) / "tarball-cache", true, true);
return ref<GitRepo>(*tarballCache);
}
static auto repoDir = std::filesystem::path(getCacheDir()) / "tarball-cache";
} // namespace fetchers
return GitRepo::openRepo(repoDir, true, true);
}
GitRepo::WorkdirInfo GitRepo::getCachedWorkdirInfo(const std::filesystem::path & path)
{
@@ -1383,4 +1404,21 @@ 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

View File

@@ -168,6 +168,8 @@ struct GitInputScheme : InputScheme
return {};
auto url2(url);
if (hasPrefix(url2.scheme, "git+"))
url2.scheme = std::string(url2.scheme, 4);
url2.query.clear();
Attrs attrs;

View File

@@ -48,7 +48,7 @@ struct GitArchiveInputScheme : InputScheme
auto size = path.size();
if (size == 3) {
if (std::regex_match(path[2], revRegex))
rev = Hash::parseAny(path[2], HashAlgorithm::SHA1);
rev = parseGitHash(path[2]);
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 = Hash::parseAny(value, HashAlgorithm::SHA1);
rev = parseGitHash(value);
} 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 (input.settings->getTarballCache()->hasObject(treeHash))
if (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 = input.settings->getTarballCache();
auto tarballCache = getTarballCache();
auto parseSink = tarballCache->getFileSystemObjectSink();
auto lastModified = unpackTarfileToSink(archive, *parseSink);
auto tree = parseSink->flush();
@@ -324,8 +324,7 @@ struct GitArchiveInputScheme : InputScheme
#endif
input.attrs.insert_or_assign("lastModified", uint64_t(tarballInfo.lastModified));
auto accessor =
input.settings->getTarballCache()->getAccessor(tarballInfo.treeHash, false, "«" + input.to_string() + "»");
auto accessor = getTarballCache()->getAccessor(tarballInfo.treeHash, false, "«" + input.to_string() + "»");
return {accessor, input};
}
@@ -404,8 +403,8 @@ struct GitHubInputScheme : GitArchiveInputScheme
store->requireStoreObjectAccessor(downloadResult.storePath)->readFile(CanonPath::root));
return RefInfo{
.rev = Hash::parseAny(std::string{json["sha"]}, HashAlgorithm::SHA1),
.treeHash = Hash::parseAny(std::string{json["commit"]["tree"]["sha"]}, HashAlgorithm::SHA1)};
.rev = parseGitHash(std::string{json["sha"]}),
.treeHash = parseGitHash(std::string{json["commit"]["tree"]["sha"]})};
}
DownloadUrl getDownloadUrl(const Input & input) const override
@@ -479,7 +478,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 = Hash::parseAny(std::string(json[0]["id"]), HashAlgorithm::SHA1)};
return RefInfo{.rev = parseGitHash(std::string(json[0]["id"]))};
}
if (json.is_array() && json.size() == 0) {
throw Error("No commits returned by GitLab API -- does the git ref really exist?");
@@ -580,7 +579,7 @@ struct SourceHutInputScheme : GitArchiveInputScheme
if (!id)
throw BadURL("in '%d', couldn't find ref '%d'", input.to_string(), ref);
return RefInfo{.rev = Hash::parseAny(*id, HashAlgorithm::SHA1)};
return RefInfo{.rev = parseGitHash(*id)};
}
DownloadUrl getDownloadUrl(const Input & input) const override

View File

@@ -11,12 +11,6 @@
#include <sys/types.h>
namespace nix {
struct GitRepo;
}
namespace nix::fetchers {
struct Cache;
@@ -131,12 +125,8 @@ 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

View File

@@ -5,7 +5,7 @@
#include "nix/util/serialise.hh"
#include "nix/util/url.hh"
#include <git2/repository.h>
#include <git2-experimental/repository.h>
#include <nlohmann/json_fwd.hpp>

View File

@@ -120,6 +120,8 @@ 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
@@ -165,4 +167,14 @@ 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

View File

@@ -23,7 +23,7 @@ struct IndirectInputScheme : InputScheme
if (path.size() == 1) {
} else if (path.size() == 2) {
if (std::regex_match(path[1], revRegex))
rev = Hash::parseAny(path[1], HashAlgorithm::SHA1);
rev = parseGitHash(path[1]);
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 = Hash::parseAny(path[2], HashAlgorithm::SHA1);
rev = parseGitHash(path[2]);
} else
throw BadURL("GitHub URL '%s' is invalid", url);

View File

@@ -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', version : '>= 1.9')
libgit2 = dependency('libgit2-experimental', version : '>= 1.9')
deps_private += libgit2
subdir('nix-meson-build-support/common')

View File

@@ -136,11 +136,11 @@ static DownloadTarballResult downloadTarball_(
.treeHash = treeHash,
.lastModified = (time_t) getIntAttr(infoAttrs, "lastModified"),
.immutableUrl = maybeGetStrAttr(infoAttrs, "immutableUrl"),
.accessor = settings.getTarballCache()->getAccessor(treeHash, false, displayPrefix),
.accessor = getTarballCache()->getAccessor(treeHash, false, displayPrefix),
};
};
if (cached && !settings.getTarballCache()->hasObject(getRevAttr(cached->value, "treeHash")))
if (cached && !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 = settings.getTarballCache();
auto tarballCache = getTarballCache();
auto parseSink = tarballCache->getFileSystemObjectSink();
auto lastModified = unpackTarfileToSink(archive, *parseSink);
auto tree = parseSink->flush();
@@ -398,9 +398,7 @@ struct TarballInputScheme : CurlInputScheme
input.attrs.insert_or_assign(
"narHash",
input.settings->getTarballCache()
->treeHashToNarHash(*input.settings, result.treeHash)
.to_string(HashFormat::SRI, true));
getTarballCache()->treeHashToNarHash(*input.settings, result.treeHash).to_string(HashFormat::SRI, true));
return {result.accessor, input};
}

View File

@@ -467,6 +467,8 @@ public:
std::string getStatus(State & state)
{
auto MiB = 1024.0 * 1024.0;
std::string res;
auto renderActivity =
@@ -514,65 +516,6 @@ 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);
@@ -586,7 +529,7 @@ public:
showActivity(actBuilds, "%s built");
auto s1 = renderActivity(actCopyPaths, "%s copied");
auto s2 = renderSizeActivity(actCopyPath);
auto s2 = renderActivity(actCopyPath, "%s MiB", "%.1f", MiB);
if (!s1.empty() || !s2.empty()) {
if (!res.empty())
@@ -602,12 +545,12 @@ public:
}
}
renderSizeActivity(actFileTransfer, "%s DL");
showActivity(actFileTransfer, "%s MiB DL", "%.1f", MiB);
{
auto s = renderActivity(actOptimiseStore, "%s paths optimised");
if (s != "") {
s += fmt(", %s / %d inodes freed", renderSize(state.bytesLinked), state.filesLinked);
s += fmt(", %.1f MiB / %d inodes freed", state.bytesLinked / MiB, state.filesLinked);
if (!res.empty())
res += ", ";
res += s;

View File

@@ -69,8 +69,7 @@
"outputChecks": {
"bin": {
"disallowedReferences": [
"/0nyw57wm2iicnm9rglvjmbci3ikmcp823czdqdzdcgsnnwqps71g",
"dev"
"/0nyw57wm2iicnm9rglvjmbci3ikmcp823czdqdzdcgsnnwqps71g"
],
"disallowedRequisites": [
"/07f301yqyz8c6wf6bbbavb2q39j4n8kmcly1s09xadyhgy6x2wr8"
@@ -85,8 +84,7 @@
"/164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9"
],
"allowedRequisites": [
"/0nr45p69vn6izw9446wsh9bng9nndhvn19kpsm4n96a5mycw0s4z",
"bin"
"/0nr45p69vn6izw9446wsh9bng9nndhvn19kpsm4n96a5mycw0s4z"
]
}
},

View File

@@ -11,9 +11,9 @@
"__sandboxProfile": "sandcastle",
"allowSubstitutes": "",
"allowedReferences": "/164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9",
"allowedRequisites": "/0nr45p69vn6izw9446wsh9bng9nndhvn19kpsm4n96a5mycw0s4z bin",
"allowedRequisites": "/0nr45p69vn6izw9446wsh9bng9nndhvn19kpsm4n96a5mycw0s4z",
"builder": "/bin/bash",
"disallowedReferences": "/0nyw57wm2iicnm9rglvjmbci3ikmcp823czdqdzdcgsnnwqps71g dev",
"disallowedReferences": "/0nyw57wm2iicnm9rglvjmbci3ikmcp823czdqdzdcgsnnwqps71g",
"disallowedRequisites": "/07f301yqyz8c6wf6bbbavb2q39j4n8kmcly1s09xadyhgy6x2wr8",
"exportReferencesGraph": "refs1 /164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9 refs2 /nix/store/qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv",
"impureEnvVars": "UNICORN",

View File

@@ -23,12 +23,10 @@
"/164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9"
],
"allowedRequisites": [
"/0nr45p69vn6izw9446wsh9bng9nndhvn19kpsm4n96a5mycw0s4z",
"bin"
"/0nr45p69vn6izw9446wsh9bng9nndhvn19kpsm4n96a5mycw0s4z"
],
"disallowedReferences": [
"/0nyw57wm2iicnm9rglvjmbci3ikmcp823czdqdzdcgsnnwqps71g",
"dev"
"/0nyw57wm2iicnm9rglvjmbci3ikmcp823czdqdzdcgsnnwqps71g"
],
"disallowedRequisites": [
"/07f301yqyz8c6wf6bbbavb2q39j4n8kmcly1s09xadyhgy6x2wr8"

View File

@@ -23,8 +23,7 @@
"allowedReferences": null,
"allowedRequisites": null,
"disallowedReferences": [
"/0nyw57wm2iicnm9rglvjmbci3ikmcp823czdqdzdcgsnnwqps71g",
"dev"
"/0nyw57wm2iicnm9rglvjmbci3ikmcp823czdqdzdcgsnnwqps71g"
],
"disallowedRequisites": [
"/07f301yqyz8c6wf6bbbavb2q39j4n8kmcly1s09xadyhgy6x2wr8"
@@ -47,8 +46,7 @@
"/164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9"
],
"allowedRequisites": [
"/0nr45p69vn6izw9446wsh9bng9nndhvn19kpsm4n96a5mycw0s4z",
"bin"
"/0nr45p69vn6izw9446wsh9bng9nndhvn19kpsm4n96a5mycw0s4z"
],
"disallowedReferences": [],
"disallowedRequisites": [],

View File

@@ -5,9 +5,9 @@
],
"builder": "/bin/bash",
"env": {
"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"
"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"
},
"inputs": {
"drvs": {
@@ -33,13 +33,13 @@
"name": "advanced-attributes-structured-attrs",
"outputs": {
"bin": {
"path": "cnpasdljgkhnwaf78cf3qygcp4qbki1c-advanced-attributes-structured-attrs-bin"
"path": "33qms3h55wlaspzba3brlzlrm8m2239g-advanced-attributes-structured-attrs-bin"
},
"dev": {
"path": "ijq6mwpa9jbnpnl33qldfqihrr38kprx-advanced-attributes-structured-attrs-dev"
"path": "wyfgwsdi8rs851wmy1xfzdxy7y5vrg5l-advanced-attributes-structured-attrs-dev"
},
"out": {
"path": "h1vh648d3p088kdimy0r8ngpfx7c3nzw-advanced-attributes-structured-attrs"
"path": "7cxy4zx1vqc885r4jl2l64pymqbdmhii-advanced-attributes-structured-attrs"
}
},
"structuredAttrs": {
@@ -66,8 +66,7 @@
"outputChecks": {
"bin": {
"disallowedReferences": [
"/nix/store/r5cff30838majxk5mp3ip2diffi8vpaj-bar",
"dev"
"/nix/store/r5cff30838majxk5mp3ip2diffi8vpaj-bar"
],
"disallowedRequisites": [
"/nix/store/9b61w26b4avv870dw0ymb6rw4r1hzpws-bar-dev"
@@ -82,8 +81,7 @@
"/nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo"
],
"allowedRequisites": [
"/nix/store/z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev",
"bin"
"/nix/store/z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev"
]
}
},

View File

@@ -11,14 +11,14 @@
"__sandboxProfile": "sandcastle",
"allowSubstitutes": "",
"allowedReferences": "/nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo",
"allowedRequisites": "/nix/store/z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev bin",
"allowedRequisites": "/nix/store/z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev",
"builder": "/bin/bash",
"disallowedReferences": "/nix/store/r5cff30838majxk5mp3ip2diffi8vpaj-bar dev",
"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/ymqmybkq5j4nd1xplw6ccdpbjnfi017v-advanced-attributes",
"out": "/nix/store/wyhpwd748pns4k7svh48wdrc8kvjk0ra-advanced-attributes",
"preferLocalBuild": "1",
"requiredSystemFeatures": "rainbow uid-range",
"system": "my-system"
@@ -47,7 +47,7 @@
"name": "advanced-attributes",
"outputs": {
"out": {
"path": "ymqmybkq5j4nd1xplw6ccdpbjnfi017v-advanced-attributes"
"path": "wyhpwd748pns4k7svh48wdrc8kvjk0ra-advanced-attributes"
}
},
"system": "my-system",

View File

@@ -23,12 +23,10 @@
"/nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo"
],
"allowedRequisites": [
"/nix/store/z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev",
"bin"
"/nix/store/z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev"
],
"disallowedReferences": [
"/nix/store/r5cff30838majxk5mp3ip2diffi8vpaj-bar",
"dev"
"/nix/store/r5cff30838majxk5mp3ip2diffi8vpaj-bar"
],
"disallowedRequisites": [
"/nix/store/9b61w26b4avv870dw0ymb6rw4r1hzpws-bar-dev"

View File

@@ -23,8 +23,7 @@
"allowedReferences": null,
"allowedRequisites": null,
"disallowedReferences": [
"/nix/store/r5cff30838majxk5mp3ip2diffi8vpaj-bar",
"dev"
"/nix/store/r5cff30838majxk5mp3ip2diffi8vpaj-bar"
],
"disallowedRequisites": [
"/nix/store/9b61w26b4avv870dw0ymb6rw4r1hzpws-bar-dev"
@@ -47,8 +46,7 @@
"/nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo"
],
"allowedRequisites": [
"/nix/store/z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev",
"bin"
"/nix/store/z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev"
],
"disallowedReferences": [],
"disallowedRequisites": [],

View File

@@ -9,17 +9,9 @@
},
"compression": "xz",
"deriver": "/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar.drv",
"downloadHash": {
"algorithm": "sha256",
"format": "base64",
"hash": "FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc="
},
"downloadHash": "sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=",
"downloadSize": 4029176,
"narHash": {
"algorithm": "sha256",
"format": "base64",
"hash": "FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc="
},
"narHash": "sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=",
"narSize": 34878,
"references": [
"/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar",

View File

@@ -7,11 +7,7 @@
},
"method": "nar"
},
"narHash": {
"algorithm": "sha256",
"format": "base64",
"hash": "FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc="
},
"narHash": "sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=",
"narSize": 34878,
"references": [
"/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar",

View File

@@ -1,11 +1,7 @@
{
"ca": null,
"deriver": null,
"narHash": {
"algorithm": "sha256",
"format": "base64",
"hash": "FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc="
},
"narHash": "sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=",
"narSize": 0,
"references": [],
"registrationTime": null,

View File

@@ -1,10 +1,6 @@
{
"ca": null,
"narHash": {
"algorithm": "sha256",
"format": "base64",
"hash": "FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc="
},
"narHash": "sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=",
"narSize": 0,
"references": [],
"version": 2

View File

@@ -8,11 +8,7 @@
"method": "nar"
},
"deriver": "/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar.drv",
"narHash": {
"algorithm": "sha256",
"format": "base64",
"hash": "FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc="
},
"narHash": "sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=",
"narSize": 34878,
"references": [
"/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar",

View File

@@ -7,11 +7,7 @@
},
"method": "nar"
},
"narHash": {
"algorithm": "sha256",
"format": "base64",
"hash": "FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc="
},
"narHash": "sha256-FePFYIlMuycIXPZbWi7LGEiMmZSX9FMbaQenWBzm1Sc=",
"narSize": 34878,
"references": [
"/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar",

View File

@@ -127,21 +127,6 @@ 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 = {
@@ -231,16 +216,16 @@ DerivationOptions advancedAttributes_ia = {
.outputChecks =
DerivationOptions::OutputChecks{
.ignoreSelfRefs = true,
.allowedReferences = StringSet{pathFoo},
.disallowedReferences = StringSet{pathBar, "dev"},
.allowedRequisites = StringSet{pathFooDev, "bin"},
.disallowedRequisites = StringSet{pathBarDev},
.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"},
},
.unsafeDiscardReferences = {},
.passAsFile = {},
.exportReferencesGraph{
{"refs1", {pathFoo}},
{"refs2", {pathBarDrvIA}},
{"refs1", {"/nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo"}},
{"refs2", {"/nix/store/vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv"}},
},
.additionalSandboxProfile = "sandcastle",
.noChroot = true,
@@ -261,16 +246,16 @@ DerivationOptions advancedAttributes_ca = {
.outputChecks =
DerivationOptions::OutputChecks{
.ignoreSelfRefs = true,
.allowedReferences = StringSet{placeholderFoo},
.disallowedReferences = StringSet{placeholderBar, "dev"},
.allowedRequisites = StringSet{placeholderFooDev, "bin"},
.disallowedRequisites = StringSet{placeholderBarDev},
.allowedReferences = StringSet{"/164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9"},
.disallowedReferences = StringSet{"/0nyw57wm2iicnm9rglvjmbci3ikmcp823czdqdzdcgsnnwqps71g"},
.allowedRequisites = StringSet{"/0nr45p69vn6izw9446wsh9bng9nndhvn19kpsm4n96a5mycw0s4z"},
.disallowedRequisites = StringSet{"/07f301yqyz8c6wf6bbbavb2q39j4n8kmcly1s09xadyhgy6x2wr8"},
},
.unsafeDiscardReferences = {},
.passAsFile = {},
.exportReferencesGraph{
{"refs1", {placeholderFoo}},
{"refs2", {pathBarDrvCA}},
{"refs1", {"/164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9"}},
{"refs2", {"/nix/store/qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv"}},
},
.additionalSandboxProfile = "sandcastle",
.noChroot = true,
@@ -390,13 +375,13 @@ DerivationOptions advancedAttributes_structuredAttrs_ia = {
std::map<std::string, DerivationOptions::OutputChecks>{
{"out",
DerivationOptions::OutputChecks{
.allowedReferences = StringSet{pathFoo},
.allowedRequisites = StringSet{pathFooDev, "bin"},
.allowedReferences = StringSet{"/nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo"},
.allowedRequisites = StringSet{"/nix/store/z0rjzy29v9k5qa4nqpykrbzirj7sd43v-foo-dev"},
}},
{"bin",
DerivationOptions::OutputChecks{
.disallowedReferences = StringSet{pathBar, "dev"},
.disallowedRequisites = StringSet{pathBarDev},
.disallowedReferences = StringSet{"/nix/store/r5cff30838majxk5mp3ip2diffi8vpaj-bar"},
.disallowedRequisites = StringSet{"/nix/store/9b61w26b4avv870dw0ymb6rw4r1hzpws-bar-dev"},
}},
{"dev",
DerivationOptions::OutputChecks{
@@ -408,8 +393,8 @@ DerivationOptions advancedAttributes_structuredAttrs_ia = {
.passAsFile = {},
.exportReferencesGraph =
{
{"refs1", {pathFoo}},
{"refs2", {pathBarDrvIA}},
{"refs1", {"/nix/store/p0hax2lzvjpfc2gwkk62xdglz0fcqfzn-foo"}},
{"refs2", {"/nix/store/vj2i49jm2868j2fmqvxm70vlzmzvgv14-bar.drv"}},
},
.additionalSandboxProfile = "sandcastle",
.noChroot = true,
@@ -432,13 +417,13 @@ DerivationOptions advancedAttributes_structuredAttrs_ca = {
std::map<std::string, DerivationOptions::OutputChecks>{
{"out",
DerivationOptions::OutputChecks{
.allowedReferences = StringSet{placeholderFoo},
.allowedRequisites = StringSet{placeholderFooDev, "bin"},
.allowedReferences = StringSet{"/164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9"},
.allowedRequisites = StringSet{"/0nr45p69vn6izw9446wsh9bng9nndhvn19kpsm4n96a5mycw0s4z"},
}},
{"bin",
DerivationOptions::OutputChecks{
.disallowedReferences = StringSet{placeholderBar, "dev"},
.disallowedRequisites = StringSet{placeholderBarDev},
.disallowedReferences = StringSet{"/0nyw57wm2iicnm9rglvjmbci3ikmcp823czdqdzdcgsnnwqps71g"},
.disallowedRequisites = StringSet{"/07f301yqyz8c6wf6bbbavb2q39j4n8kmcly1s09xadyhgy6x2wr8"},
}},
{"dev",
DerivationOptions::OutputChecks{
@@ -450,8 +435,8 @@ DerivationOptions advancedAttributes_structuredAttrs_ca = {
.passAsFile = {},
.exportReferencesGraph =
{
{"refs1", {placeholderFoo}},
{"refs2", {pathBarDrvCA}},
{"refs1", {"/164j69y6zir9z0339n8pjigg3rckinlr77bxsavzizdaaljb7nh9"}},
{"refs2", {"/nix/store/qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv"}},
},
.additionalSandboxProfile = "sandcastle",
.noChroot = true,

View File

@@ -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); }, \
[](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, HashFormat::SRI); }, \
[](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)

View File

@@ -80,7 +80,7 @@ static UnkeyedValidPathInfo makeFull(const Store & store, bool includeImpureInfo
{ \
writeTest( \
#STEM, \
[&]() -> json { return OBJ.toJSON(*store, PURE); }, \
[&]() -> json { return OBJ.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"); }); \
}

View File

@@ -423,6 +423,15 @@ 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_);

View File

@@ -1381,15 +1381,13 @@ 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"] = expectedJsonVersionDerivation;
res["version"] = 4;
{
nlohmann::json & outputsObj = res["outputs"];
@@ -1448,14 +1446,8 @@ Derivation adl_serializer<Derivation>::from_json(const json & _json, const Exper
res.name = getString(valueAt(json, "name"));
{
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);
}
if (valueAt(json, "version") != 4)
throw Error("Only derivation format version 4 is currently supported.");
try {
auto outputs = getObject(valueAt(json, "outputs"));

View File

@@ -42,7 +42,7 @@ struct NarInfo : ValidPathInfo
std::string to_string(const StoreDirConfig & store) const;
nlohmann::json toJSON(const StoreDirConfig & store, bool includeImpureInfo) const override;
nlohmann::json toJSON(const StoreDirConfig & store, bool includeImpureInfo, HashFormat hashFormat) const override;
static NarInfo fromJSON(const StoreDirConfig & store, const StorePath & path, const nlohmann::json & json);
};

View File

@@ -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) const;
virtual nlohmann::json toJSON(const StoreDirConfig & store, bool includeImpureInfo, HashFormat hashFormat) const;
static UnkeyedValidPathInfo fromJSON(const StoreDirConfig & store, const nlohmann::json & json);
};

View File

@@ -52,21 +52,7 @@ struct RestrictionContext
* Add 'path' to the set of paths that may be referenced by the
* outputs, and make it appear in the sandbox.
*/
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;
virtual void addDependency(const StorePath & path) = 0;
};
/**

View File

@@ -989,22 +989,19 @@ 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. */
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);
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));
}});
txn.commit();
});

View File

@@ -311,25 +311,22 @@ MissingPaths Store::queryMissing(const std::vector<DerivedPath> & targets)
StorePaths Store::topoSortPaths(const StorePathSet & paths)
{
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);
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));
}});
}
std::map<DrvOutput, StorePath>

View File

@@ -130,11 +130,11 @@ std::string NarInfo::to_string(const StoreDirConfig & store) const
return res;
}
nlohmann::json NarInfo::toJSON(const StoreDirConfig & store, bool includeImpureInfo) const
nlohmann::json NarInfo::toJSON(const StoreDirConfig & store, bool includeImpureInfo, HashFormat hashFormat) const
{
using nlohmann::json;
auto jsonObject = ValidPathInfo::toJSON(store, includeImpureInfo);
auto jsonObject = ValidPathInfo::toJSON(store, includeImpureInfo, hashFormat);
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;
jsonObject["downloadHash"] = fileHash->to_string(hashFormat, true);
if (fileSize)
jsonObject["downloadSize"] = fileSize;
}
@@ -161,17 +161,17 @@ NarInfo NarInfo::fromJSON(const StoreDirConfig & store, const StorePath & path,
auto & obj = getObject(json);
if (auto * url = get(obj, "url"))
res.url = getString(*url);
if (json.contains("url"))
res.url = getString(valueAt(obj, "url"));
if (auto * compression = get(obj, "compression"))
res.compression = getString(*compression);
if (json.contains("compression"))
res.compression = getString(valueAt(obj, "compression"));
if (auto * downloadHash = get(obj, "downloadHash"))
res.fileHash = *downloadHash;
if (json.contains("downloadHash"))
res.fileHash = Hash::parseAny(getString(valueAt(obj, "downloadHash")), std::nullopt);
if (auto * downloadSize = get(obj, "downloadSize"))
res.fileSize = getUnsigned(*downloadSize);
if (json.contains("downloadSize"))
res.fileSize = getUnsigned(valueAt(obj, "downloadSize"));
return res;
}

View File

@@ -149,7 +149,8 @@ ValidPathInfo ValidPathInfo::makeFromCA(
return res;
}
nlohmann::json UnkeyedValidPathInfo::toJSON(const StoreDirConfig & store, bool includeImpureInfo) const
nlohmann::json
UnkeyedValidPathInfo::toJSON(const StoreDirConfig & store, bool includeImpureInfo, HashFormat hashFormat) const
{
using nlohmann::json;
@@ -157,7 +158,7 @@ nlohmann::json UnkeyedValidPathInfo::toJSON(const StoreDirConfig & store, bool i
jsonObject["version"] = 2;
jsonObject["narHash"] = narHash;
jsonObject["narHash"] = narHash.to_string(hashFormat, true);
jsonObject["narSize"] = narSize;
{
@@ -191,13 +192,16 @@ UnkeyedValidPathInfo UnkeyedValidPathInfo::fromJSON(const StoreDirConfig & store
auto & json = getObject(_json);
{
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);
// 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);
}
}
res.narHash = valueAt(json, "narHash");
res.narHash = Hash::parseAny(getString(valueAt(json, "narHash")), std::nullopt);
res.narSize = getUnsigned(valueAt(json, "narSize"));
try {
@@ -209,12 +213,19 @@ UnkeyedValidPathInfo UnkeyedValidPathInfo::fromJSON(const StoreDirConfig & store
throw;
}
try {
res.ca = ptrToOwned<ContentAddress>(getNullable(valueAt(json, "ca")));
} catch (Error & e) {
e.addTrace({}, "while reading key 'ca'");
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;
}
if (auto * rawDeriver0 = optionalValueAt(json, "deriver"))
if (auto * rawDeriver = getNullable(*rawDeriver0))

View File

@@ -334,7 +334,7 @@ private:
protected:
void addDependencyImpl(const StorePath & path) override;
void addDependency(const StorePath & path) override;
/**
* Make a file owned by the builder.
@@ -1203,8 +1203,11 @@ void DerivationBuilderImpl::stopDaemon()
daemonSocket.close();
}
void DerivationBuilderImpl::addDependencyImpl(const StorePath & path)
void DerivationBuilderImpl::addDependency(const StorePath & path)
{
if (isAllowed(path))
return;
addedPaths.insert(path);
}
@@ -1470,46 +1473,43 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs()
outputStats.insert_or_assign(outputName, std::move(st));
}
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.
auto sortedOutputNames = topoSort(
outputsToSort,
{[&](const std::string & name) {
auto orifu = get(outputReferencesIfUnregistered, name);
if (!orifu)
throw BuildError(
BuildResult::Failure::OutputRejected,
"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);
"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);
}});
std::reverse(sortedOutputNames.begin(), sortedOutputNames.end());

View File

@@ -709,11 +709,8 @@ struct ChrootLinuxDerivationBuilder : ChrootDerivationBuilder, LinuxDerivationBu
DerivationBuilderImpl::killSandbox(getStats);
}
void addDependencyImpl(const StorePath & path) override
void addDependency(const StorePath & path) override
{
if (isAllowed(path))
return;
auto [source, target] = ChrootDerivationBuilder::addDependencyPrep(path);
/* Bind-mount the path into the sandbox. This requires

View File

@@ -74,7 +74,6 @@ sources = files(
'strings.cc',
'suggestions.cc',
'terminal.cc',
'topo-sort.cc',
'url.cc',
'util.cc',
'xml-writer.cc',

View File

@@ -1,318 +0,0 @@
#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

View File

@@ -35,10 +35,10 @@ INSTANTIATE_TEST_SUITE_P(
// Already proper URL with git+ssh
FixGitURLParam{
.input = "git+ssh://user@domain:1234/path",
.expected = "ssh://user@domain:1234/path",
.expected = "git+ssh://user@domain:1234/path",
.parsed =
ParsedURL{
.scheme = "ssh",
.scheme = "git+ssh",
.authority =
ParsedURL::Authority{
.host = "domain",

View File

@@ -146,59 +146,6 @@ 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
* --------------------------------------------------------------------------*/

View File

@@ -114,13 +114,4 @@ 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

View File

@@ -2,61 +2,39 @@
///@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>
TopoSortResult<T> topoSort(std::set<T, Compare> items, std::function<std::set<T, Compare>(const T &)> getChildren)
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)
{
std::vector<T> sorted;
decltype(items) visited, parents;
std::function<std::optional<Cycle<T>>(const T & path, const T * parent)> dfsVisit;
auto dfsVisit = [&](this auto & dfsVisit, const T & path, const T * parent) {
if (parents.count(path))
throw makeCycleError(path, *parent);
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;
}
if (!visited.insert(path).second)
return;
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)) {
auto result = dfsVisit(i, &path);
if (result.has_value()) {
return result;
}
}
if (i != path && items.count(i))
dfsVisit(i, &path);
sorted.push_back(path);
parents.erase(path);
return std::nullopt;
};
for (auto & i : items) {
auto cycle = dfsVisit(i, nullptr);
if (cycle.has_value()) {
return *cycle;
}
}
for (auto & i : items)
dfsVisit(i, nullptr);
std::reverse(sorted.begin(), sorted.end());

View File

@@ -330,13 +330,10 @@ 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
* drops `git+` from the scheme (e.g. `git+https://` to `https://`)
* and changes absolute paths into `file://` URLs.
*/
ParsedURL fixGitURL(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
changes absolute paths into file:// URLs. */
ParsedURL fixGitURL(const std::string & url);
/**
* Whether a string is valid as RFC 3986 scheme name.

View File

@@ -99,42 +99,6 @@ 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

View File

@@ -409,23 +409,21 @@ ParsedUrlScheme parseUrlScheme(std::string_view scheme)
};
}
ParsedURL fixGitURL(std::string url)
ParsedURL fixGitURL(const std::string & url)
{
std::regex scpRegex("([^/]*)@(.*):(.*)");
if (!hasPrefix(url, "/") && std::regex_match(url, scpRegex))
url = std::regex_replace(url, scpRegex, "ssh://$1@$2/$3");
if (!hasPrefix(url, "file:") && !hasPrefix(url, "git+file:") && url.find("://") == std::string::npos)
return parseURL(std::regex_replace(url, scpRegex, "ssh://$1@$2/$3"));
if (hasPrefix(url, "file:"))
return parseURL(url);
if (url.find("://") == std::string::npos) {
return ParsedURL{
.scheme = "file",
.authority = ParsedURL::Authority{},
.path = splitString<std::vector<std::string>>(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;
}
return parseURL(url);
}
// https://www.rfc-editor.org/rfc/rfc3986#section-3.1

View File

@@ -132,62 +132,17 @@ 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)
{
SizeUnit unit = getSizeUnit(value);
return fmt("%s %ciB", renderSizeWithoutUnit(value, unit, align), getSizeUnitSuffix(unit));
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));
}
bool hasPrefix(std::string_view s, std::string_view prefix)

View File

@@ -227,13 +227,11 @@ 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);
@@ -304,8 +302,6 @@ 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);
@@ -499,18 +495,19 @@ struct Common : InstallableCommand, MixProfile
}
}
std::pair<BuildEnvironment, StorePath> getBuildEnvironment(ref<Store> store, ref<Installable> installable)
std::pair<BuildEnvironment, std::string> getBuildEnvironment(ref<Store> store, ref<Installable> installable)
{
auto shellOutPath = getShellOutPath(store, installable);
auto strPath = store->printStorePath(shellOutPath);
updateProfile(shellOutPath);
debug("reading environment file '%s'", store->printStorePath(shellOutPath));
debug("reading environment file '%s'", strPath);
return {
BuildEnvironment::parseJSON(store->requireStoreObjectAccessor(shellOutPath)->readFile(CanonPath::root)),
shellOutPath,
};
strPath};
}
};
@@ -637,7 +634,7 @@ struct CmdDevelop : Common, MixEnvironment
setEnviron();
// prevent garbage collection until shell exits
setEnv("NIX_GCROOT", store->printStorePath(gcroot).c_str());
setEnv("NIX_GCROOT", gcroot.c_str());
Path shell = "bash";
bool foundInteractive = false;

View File

@@ -362,7 +362,7 @@ struct CmdFlakeCheck : FlakeCommand
throw;
} catch (Error & e) {
if (settings.keepGoing) {
logError(e.info());
ignoreExceptionExceptInterrupt();
hasErrors = true;
} else
throw;
@@ -418,7 +418,7 @@ struct CmdFlakeCheck : FlakeCommand
return std::nullopt;
};
std::map<DerivedPath, std::vector<AttrPath>> attrPathsByDrv;
std::vector<DerivedPath> drvPaths;
auto checkApp = [&](const std::string & attrPath, Value & v, const PosIdx pos) {
try {
@@ -616,13 +616,7 @@ struct CmdFlakeCheck : FlakeCommand
.drvPath = makeConstantStorePathRef(*drvPath),
.outputs = OutputsSpec::All{},
};
// 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));
drvPaths.push_back(std::move(path));
}
}
}
@@ -786,9 +780,7 @@ struct CmdFlakeCheck : FlakeCommand
});
}
if (build && !attrPathsByDrv.empty()) {
auto keys = std::views::keys(attrPathsByDrv);
std::vector<DerivedPath> drvPaths(keys.begin(), keys.end());
if (build && !drvPaths.empty()) {
// TODO: This filtering of substitutable paths is a temporary workaround until
// https://github.com/NixOS/nix/issues/5025 (union stores) is implemented.
//
@@ -812,28 +804,7 @@ struct CmdFlakeCheck : FlakeCommand
}
Activity act(*logger, lvlInfo, actUnknown, fmt("running %d flake checks", toBuild.size()));
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));
}
}
}
store->buildPaths(toBuild);
}
if (hasErrors)
throw Error("some errors were encountered during the evaluation");

View File

@@ -17,7 +17,6 @@ __functions="$(declare -F)"
__dumpEnv() {
printf '{\n'
printf ' "version": 1,\n'
printf ' "bashFunctions": {\n'
local __first=1

View File

@@ -9,7 +9,6 @@
#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>
@@ -57,7 +56,7 @@ bool createUserEnv(
auto attrs = state.buildBindings(7 + outputs.size());
attrs.alloc(state.s.type).mkStringNoCopy("derivation"_sds);
attrs.alloc(state.s.type).mkStringNoCopy("derivation");
attrs.alloc(state.s.name).mkString(i.queryName());
auto system = i.querySystem();
if (!system.empty())

View File

@@ -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);
jsonObject = info->toJSON(store, true, HashFormat::SRI);
if (showClosureSize) {
StorePathSet closure;

View File

@@ -66,16 +66,10 @@ derivation' {
outputChecks = {
out = {
allowedReferences = [ foo ];
allowedRequisites = [
foo.dev
"bin"
];
allowedRequisites = [ foo.dev ];
};
bin = {
disallowedReferences = [
bar
"dev"
];
disallowedReferences = [ bar ];
disallowedRequisites = [ bar.dev ];
};
dev = {

View File

@@ -58,14 +58,8 @@ derivation' {
impureEnvVars = [ "UNICORN" ];
__darwinAllowLocalNetworking = true;
allowedReferences = [ foo ];
allowedRequisites = [
foo.dev
"bin"
];
disallowedReferences = [
bar
"dev"
];
allowedRequisites = [ foo.dev ];
disallowedReferences = [ bar ];
disallowedRequisites = [ bar.dev ];
requiredSystemFeatures = [
"rainbow"

View File

@@ -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\",\"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")])
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")])

View File

@@ -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 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")])
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")])

View File

@@ -1 +1 @@
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")])
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")])

View File

@@ -1 +1 @@
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")])
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")])

View File

@@ -192,24 +192,3 @@ 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"

View File

@@ -17,16 +17,8 @@ diff --unified --color=always \
jq --sort-keys 'map_values(.narHash)') \
<(jq --sort-keys <<-EOF
{
"$foo": {
"algorithm": "sha256",
"format": "base64",
"hash": "QvtAMbUl/uvi+LCObmqOhvNOapHdA2raiI4xG5zI5pA="
},
"$bar": {
"algorithm": "sha256",
"format": "base64",
"hash": "9fhYGu9fqxcQC2Kc81qh2RMo1QcLBUBo8U+pPn+jthQ="
},
"$foo": "sha256-QvtAMbUl/uvi+LCObmqOhvNOapHdA2raiI4xG5zI5pA=",
"$bar": "sha256-9fhYGu9fqxcQC2Kc81qh2RMo1QcLBUBo8U+pPn+jthQ=",
"$baz": null
}
EOF