Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions doc/manual/source/protocols/json/schema/store-object-info-v2.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,15 @@ $defs:
The total size of this store object and every other object in its [closure](@docroot@/glossary.md#gloss-closure).

> This field is not stored at all, but computed by traversing the other fields across all the store objects in a closure.

provenance:
oneOf:
- type: "null"
- type: object # FIXME
title: Provenance
description: |
An arbitrary JSON object containing provenance information about the store object, or `null` if not available.

additionalProperties: false

narInfo:
Expand Down Expand Up @@ -262,4 +271,13 @@ $defs:
> This is an impure "`.narinfo`" field that may not be included in certain contexts.

> This field is not stored at all, but computed by traversing the other fields across all the store objects in a closure.

provenance:
oneOf:
- type: "null"
- type: object # FIXME
title: Provenance
description: |
An arbitrary JSON object containing provenance information about the store object, or `null` if not available.

additionalProperties: false
2 changes: 2 additions & 0 deletions src/libcmd/include/nix/cmd/installable-flake.hh
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ struct InstallableFlake : InstallableValue
ref<flake::LockedFlake> getLockedFlake() const;

FlakeRef nixpkgsFlakeRef() const;

std::shared_ptr<const Provenance> makeProvenance(std::string_view attrPath) const;
};

/**
Expand Down
13 changes: 13 additions & 0 deletions src/libcmd/installable-flake.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "nix/util/url.hh"
#include "nix/fetchers/registry.hh"
#include "nix/store/build-result.hh"
#include "nix/flake/provenance.hh"

#include <regex>
#include <queue>
Expand Down Expand Up @@ -84,6 +85,8 @@ DerivedPathsWithInfo InstallableFlake::toDerivedPaths()

auto attrPath = attr->getAttrPathStr();

PushProvenance pushedProvenance(*state, makeProvenance(attrPath));

if (!attr->isDerivation()) {

// FIXME: use eval cache?
Expand Down Expand Up @@ -172,6 +175,8 @@ std::vector<ref<eval_cache::AttrCursor>> InstallableFlake::getCursors(EvalState
for (auto & attrPath : attrPaths) {
debug("trying flake output attribute '%s'", attrPath);

PushProvenance pushedProvenance(state, makeProvenance(attrPath));

auto attr = root->findAlongAttrPath(AttrPath::parse(state, attrPath));
if (attr) {
res.push_back(ref(*attr));
Expand Down Expand Up @@ -212,4 +217,12 @@ FlakeRef InstallableFlake::nixpkgsFlakeRef() const
return defaultNixpkgsFlakeRef();
}

std::shared_ptr<const Provenance> InstallableFlake::makeProvenance(std::string_view attrPath) const
{
auto provenance = getLockedFlake()->flake.provenance;
if (!provenance)
return nullptr;
return std::make_shared<const FlakeProvenance>(provenance, std::string(attrPath));
}

} // namespace nix
2 changes: 2 additions & 0 deletions src/libexpr/eval.cc
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,8 @@ EvalMemory::EvalMemory()
assertGCInitialized();
}

thread_local EvalState::EvalContext EvalState::evalContext;

EvalState::EvalState(
const LookupPath & lookupPathFromArguments,
ref<Store> store,
Expand Down
33 changes: 33 additions & 0 deletions src/libexpr/include/nix/expr/eval.hh
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ enum RepairFlag : bool;
struct MemorySourceAccessor;
struct MountedSourceAccessor;
struct AsyncPathWriter;
struct Provenance;

namespace eval_cache {
class EvalCache;
Expand Down Expand Up @@ -1128,6 +1129,20 @@ private:
friend class ListBuilder;

public:

/**
* Per-thread evaluation context. This context is propagated to worker threads when a value is evaluated
* asynchronously.
*/
struct EvalContext
{
std::shared_ptr<const Provenance> provenance;

// FIXME: move callDepth here.
};

thread_local static EvalContext evalContext;

/**
* Worker threads manager.
*
Expand Down Expand Up @@ -1173,6 +1188,24 @@ SourcePath resolveExprPath(SourcePath path, bool addDefaultNix = true);
*/
bool isAllowedURI(std::string_view uri, const Strings & allowedPaths);

struct PushProvenance
{
EvalState & state;
std::shared_ptr<const Provenance> prev;

PushProvenance(EvalState & state, std::shared_ptr<const Provenance> prov)
: state(state)
{
state.evalContext.provenance.swap(prev);
state.evalContext.provenance.swap(prov);
}

~PushProvenance()
{
state.evalContext.provenance.swap(prev);
}
};

} // namespace nix

#include "nix/expr/eval-inline.hh"
7 changes: 6 additions & 1 deletion src/libexpr/parallel-eval.cc
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,12 @@ static void prim_parallel(EvalState & state, const PosIdx pos, Value ** args, Va
std::vector<std::pair<Executor::work_t, uint8_t>> work;
for (auto value : args[0]->listView())
if (!value->isFinished())
work.emplace_back([value(allocRootValue(value)), &state, pos]() { state.forceValue(**value, pos); }, 0);
work.emplace_back(
[value(allocRootValue(value)), &state, pos, evalContext(state.evalContext)]() {
state.evalContext = evalContext;
state.forceValue(**value, pos);
},
0);
state.executor->spawn(std::move(work));
}

Expand Down
6 changes: 4 additions & 2 deletions src/libexpr/primops.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1847,7 +1847,8 @@ static void derivationStrictInternal(EvalState & state, std::string_view drvName
}

/* Write the resulting term into the Nix store directory. */
auto drvPath = writeDerivation(*state.store, *state.asyncPathWriter, drv, state.repair);
auto drvPath =
writeDerivation(*state.store, *state.asyncPathWriter, drv, state.repair, false, state.evalContext.provenance);
auto drvPathS = state.store->printStorePath(drvPath);

printMsg(lvlChatty, "instantiated '%1%' -> '%2%'", drvName, drvPathS);
Expand Down Expand Up @@ -2733,7 +2734,8 @@ static void prim_toFile(EvalState & state, const PosIdx pos, Value ** args, Valu
ContentAddressMethod::Raw::Text,
HashAlgorithm::SHA256,
refs,
state.repair);
state.repair,
state.evalContext.provenance);
});

/* Note: we don't need to add `context' to the context of the
Expand Down
6 changes: 5 additions & 1 deletion src/libexpr/value-to-json.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ static void parallelForceDeep(EvalState & state, Value & v, PosIdx pos)
return;
for (auto & a : *v.attrs())
work.emplace_back(
[value(allocRootValue(a.value)), pos(a.pos), &state]() { parallelForceDeep(state, **value, pos); }, 0);
[value(allocRootValue(a.value)), pos(a.pos), &state, evalContext(state.evalContext)]() {
state.evalContext = evalContext;
parallelForceDeep(state, **value, pos);
},
0);
break;
}

Expand Down
3 changes: 3 additions & 0 deletions src/libfetchers/attrs.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ nlohmann::json attrsToJSON(const Attrs & attrs)
{
nlohmann::json json;
for (auto & attr : attrs) {
/* The __final attribute is purely internal, so never serialize it. */
if (attr.first == "__final")
continue;
if (auto v = std::get_if<uint64_t>(&attr.second)) {
json[attr.first] = *v;
} else if (auto v = std::get_if<std::string>(&attr.second)) {
Expand Down
7 changes: 5 additions & 2 deletions src/libfetchers/fetchers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#include "nix/fetchers/fetch-to-store.hh"
#include "nix/util/json-utils.hh"
#include "nix/fetchers/fetch-settings.hh"
#include "nix/fetchers/fetch-to-store.hh"
#include "nix/fetchers/provenance.hh"
#include "nix/util/url.hh"
#include "nix/util/forwarding-source-accessor.hh"
#include "nix/util/archive.hh"
Expand Down Expand Up @@ -196,7 +196,6 @@ bool Input::contains(const Input & other) const
return false;
}

// FIXME: remove
std::tuple<StorePath, ref<SourceAccessor>, Input> Input::fetchToStore(const Settings & settings, Store & store) const
{
if (!scheme)
Expand Down Expand Up @@ -341,6 +340,8 @@ std::pair<ref<SourceAccessor>, Input> Input::getAccessorUnchecked(const Settings
{{"hash", store.queryPathInfo(*storePath)->narHash.to_string(HashFormat::SRI, true)}});
}

accessor->provenance = std::make_shared<TreeProvenance>(*this);

// FIXME: ideally we would use the `showPath()` of the
// "real" accessor for this fetcher type.
accessor->setPathDisplay("«" + to_string(true) + "»");
Expand All @@ -365,6 +366,8 @@ std::pair<ref<SourceAccessor>, Input> Input::getAccessorUnchecked(const Settings
else
accessor->fingerprint = result.getFingerprint(store);

accessor->provenance = std::make_shared<TreeProvenance>(result);

return {accessor, std::move(result)};
} catch (Error & e) {
if (storePath) {
Expand Down
7 changes: 7 additions & 0 deletions src/libfetchers/filtering-source-accessor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ std::pair<CanonPath, std::optional<std::string>> FilteringSourceAccessor::getFin
return next->getFingerprint(prefix / path);
}

std::shared_ptr<const Provenance> FilteringSourceAccessor::getProvenance(const CanonPath & path)
{
if (provenance)
return SourceAccessor::getProvenance(path);
return next->getProvenance(prefix / path);
}

void FilteringSourceAccessor::invalidateCache(const CanonPath & path)
{
next->invalidateCache(prefix / path);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ struct FilteringSourceAccessor : SourceAccessor

std::pair<CanonPath, std::optional<std::string>> getFingerprint(const CanonPath & path) override;

std::shared_ptr<const Provenance> getProvenance(const CanonPath & path) override;

void invalidateCache(const CanonPath & path) override;

/**
Expand Down
1 change: 1 addition & 0 deletions src/libfetchers/include/nix/fetchers/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ headers = files(
'git-lfs-fetch.hh',
'git-utils.hh',
'input-cache.hh',
'provenance.hh',
'registry.hh',
'tarball.hh',
)
17 changes: 17 additions & 0 deletions src/libfetchers/include/nix/fetchers/provenance.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#pragma once

#include "nix/util/provenance.hh"
#include "nix/fetchers/fetchers.hh"

namespace nix {

struct TreeProvenance : Provenance
{
ref<nlohmann::json> attrs;

TreeProvenance(const fetchers::Input & input);

nlohmann::json to_json() const override;
};

} // namespace nix
1 change: 1 addition & 0 deletions src/libfetchers/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ sources = files(
'input-cache.cc',
'mercurial.cc',
'path.cc',
'provenance.cc',
'registry.cc',
'tarball.cc',
)
Expand Down
27 changes: 27 additions & 0 deletions src/libfetchers/provenance.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#include "nix/fetchers/provenance.hh"
#include "nix/fetchers/attrs.hh"

#include <nlohmann/json.hpp>

namespace nix {

TreeProvenance::TreeProvenance(const fetchers::Input & input)
: attrs(make_ref<nlohmann::json>([&]() {
// Remove the narHash attribute from the provenance info, as it's redundant (it's already recorded in the store
// path info).
auto attrs2 = input.attrs;
attrs2.erase("narHash");
return fetchers::attrsToJSON(attrs2);
}()))
{
}

nlohmann::json TreeProvenance::to_json() const
{
return nlohmann::json{
{"type", "tree"},
{"attrs", *attrs},
};
}

} // namespace nix
28 changes: 28 additions & 0 deletions src/libfetchers/tarball.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,30 @@
#include "nix/store/store-api.hh"
#include "nix/fetchers/git-utils.hh"
#include "nix/fetchers/fetch-settings.hh"
#include "nix/util/provenance.hh"

#include <nlohmann/json.hpp>

namespace nix::fetchers {

struct FetchurlProvenance : Provenance
{
std::string url;

FetchurlProvenance(const std::string & url)
: url(url)
{
}

nlohmann::json to_json() const override
{
return nlohmann::json{
{"type", "fetchurl"},
{"url", url},
};
}
};

DownloadFileResult downloadFile(
Store & store,
const Settings & settings,
Expand Down Expand Up @@ -83,6 +104,13 @@ DownloadFileResult downloadFile(
},
hashString(HashAlgorithm::SHA256, sink.s));
info.narSize = sink.s.size();
if (experimentalFeatureSettings.isEnabled(Xp::Provenance)) {
auto sanitizedUrl = request.uri.parsed();
if (sanitizedUrl.authority)
sanitizedUrl.authority->password.reset();
sanitizedUrl.query.clear();
info.provenance = std::make_shared<FetchurlProvenance>(sanitizedUrl.to_string());
}
auto source = StringSource{sink.s};
store.addToStore(info, source, NoRepair, NoCheckSigs);
storePath = std::move(info.path);
Expand Down
1 change: 1 addition & 0 deletions src/libflake/flake.cc
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ static Flake readFlake(
.resolvedRef = resolvedRef,
.lockedRef = lockedRef,
.path = flakePath,
.provenance = flakePath.getProvenance(),
};

if (auto description = vInfo.attrs()->get(state.s.description)) {
Expand Down
6 changes: 6 additions & 0 deletions src/libflake/include/nix/flake/flake.hh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
namespace nix {

class EvalState;
struct Provenance;

namespace flake {

Expand Down Expand Up @@ -94,6 +95,11 @@ struct Flake
*/
SourcePath path;

/**
* Cached provenance of `flake.nix` (equivalent to `path.getProvenance()`).
*/
std::shared_ptr<const Provenance> provenance;

/**
* Pretend that `lockedRef` is dirty.
*/
Expand Down
1 change: 1 addition & 0 deletions src/libflake/include/nix/flake/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ headers = files(
'flake.hh',
'flakeref.hh',
'lockfile.hh',
'provenance.hh',
'settings.hh',
'url-name.hh',
)
Loading