1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-12-10 02:51:02 +01:00

Merge pull request #14023 from NixOS/dummy-store-path-info

Make dummy store also store path info
This commit is contained in:
Robert Hensing 2025-09-21 16:58:18 +02:00 committed by GitHub
commit ab7feb3898
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 153 additions and 38 deletions

View file

@ -19,20 +19,43 @@ struct DummyStore : virtual Store
ref<const Config> config; ref<const Config> config;
ref<MemorySourceAccessor> contents; struct PathInfoAndContents
{
UnkeyedValidPathInfo info;
ref<MemorySourceAccessor> contents;
};
/**
* This is map conceptually owns the file system objects for each
* store object.
*/
std::map<StorePath, PathInfoAndContents> contents;
/**
* This view conceptually just borrows the file systems objects of
* each store object from `contents`, and combines them together
* into one store-wide source accessor.
*
* This is needed just in order to implement `Store::getFSAccessor`.
*/
ref<MemorySourceAccessor> wholeStoreView = make_ref<MemorySourceAccessor>();
DummyStore(ref<const Config> config) DummyStore(ref<const Config> config)
: Store{*config} : Store{*config}
, config(config) , config(config)
, contents(make_ref<MemorySourceAccessor>())
{ {
contents->setPathDisplay(config->storeDir); wholeStoreView->setPathDisplay(config->storeDir);
MemorySink sink{*wholeStoreView};
sink.createDirectory(CanonPath::root);
} }
void queryPathInfoUncached( void queryPathInfoUncached(
const StorePath & path, Callback<std::shared_ptr<const ValidPathInfo>> callback) noexcept override const StorePath & path, Callback<std::shared_ptr<const ValidPathInfo>> callback) noexcept override
{ {
callback(nullptr); if (auto it = contents.find(path); it != contents.end())
callback(std::make_shared<ValidPathInfo>(StorePath{path}, it->second.info));
else
callback(nullptr);
} }
/** /**
@ -50,7 +73,33 @@ struct DummyStore : virtual Store
void addToStore(const ValidPathInfo & info, Source & source, RepairFlag repair, CheckSigsFlag checkSigs) override void addToStore(const ValidPathInfo & info, Source & source, RepairFlag repair, CheckSigsFlag checkSigs) override
{ {
unsupported("addToStore"); if (config->readOnly)
unsupported("addToStore");
if (repair)
throw Error("repairing is not supported for '%s' store", config->getHumanReadableURI());
if (checkSigs)
throw Error("checking signatures is not supported for '%s' store", config->getHumanReadableURI());
auto temp = make_ref<MemorySourceAccessor>();
MemorySink tempSink{*temp};
parseDump(tempSink, source);
auto path = info.path;
auto [it, _] = contents.insert({
path,
{
std::move(info),
make_ref<MemorySourceAccessor>(std::move(*temp)),
},
});
auto & pathAndContents = it->second;
bool inserted = wholeStoreView->open(CanonPath(path.to_string()), pathAndContents.contents->root);
if (!inserted)
unreachable();
} }
StorePath addToStoreFromDump( StorePath addToStoreFromDump(
@ -65,6 +114,9 @@ struct DummyStore : virtual Store
if (config->readOnly) if (config->readOnly)
unsupported("addToStoreFromDump"); unsupported("addToStoreFromDump");
if (repair)
throw Error("repairing is not supported for '%s' store", config->getHumanReadableURI());
auto temp = make_ref<MemorySourceAccessor>(); auto temp = make_ref<MemorySourceAccessor>();
{ {
@ -85,27 +137,52 @@ struct DummyStore : virtual Store
} }
auto hash = hashPath({temp, CanonPath::root}, hashMethod.getFileIngestionMethod(), hashAlgo).first; auto hash = hashPath({temp, CanonPath::root}, hashMethod.getFileIngestionMethod(), hashAlgo).first;
auto narHash = hashPath({temp, CanonPath::root}, FileIngestionMethod::NixArchive, HashAlgorithm::SHA256);
auto desc = ContentAddressWithReferences::fromParts( auto info = ValidPathInfo::makeFromCA(
hashMethod, *this,
hash, name,
ContentAddressWithReferences::fromParts(
hashMethod,
std::move(hash),
{
.others = references,
// caller is not capable of creating a self-reference, because
// this is content-addressed without modulus
.self = false,
}),
std::move(narHash.first));
info.narSize = narHash.second.value();
auto path = info.path;
auto [it, _] = contents.insert({
path,
{ {
.others = references, std::move(info),
// caller is not capable of creating a self-reference, because make_ref<MemorySourceAccessor>(std::move(*temp)),
// this is content-addressed without modulus },
.self = false, });
});
auto dstPath = makeFixedOutputPathFromCA(name, desc); auto & pathAndContents = it->second;
contents->open(CanonPath(printStorePath(dstPath)), std::move(temp->root)); bool inserted = wholeStoreView->open(CanonPath(path.to_string()), pathAndContents.contents->root);
if (!inserted)
unreachable();
return dstPath; return path;
} }
void narFromPath(const StorePath & path, Sink & sink) override void narFromPath(const StorePath & path, Sink & sink) override
{ {
unsupported("narFromPath"); auto object = contents.find(path);
if (object == contents.end())
throw Error("path '%s' is not valid", printStorePath(path));
const auto & [info, accessor] = object->second;
SourcePath sourcePath(accessor);
dumpPath(sourcePath, sink, FileSerialisationMethod::NixArchive);
} }
void void
@ -116,7 +193,7 @@ struct DummyStore : virtual Store
virtual ref<SourceAccessor> getFSAccessor(bool requireValidPath) override virtual ref<SourceAccessor> getFSAccessor(bool requireValidPath) override
{ {
return this->contents; return wholeStoreView;
} }
}; };

View file

@ -4,7 +4,7 @@ R"(
This store type represents a store in memory. This store type represents a store in memory.
Store objects can be read and written, but only so long as the store is open. Store objects can be read and written, but only so long as the store is open.
Once the store is closed, all data will be forgoton. Once the store is closed, all data will be discarded.
It's useful when you want to use the Nix evaluator when no actual Nix store exists, e.g. It's useful when you want to use the Nix evaluator when no actual Nix store exists, e.g.

View file

@ -4,10 +4,15 @@ namespace nix {
struct DummyStoreConfig : public std::enable_shared_from_this<DummyStoreConfig>, virtual StoreConfig struct DummyStoreConfig : public std::enable_shared_from_this<DummyStoreConfig>, virtual StoreConfig
{ {
using StoreConfig::StoreConfig; DummyStoreConfig(const Params & params)
: StoreConfig(params)
{
// Disable caching since this a temporary in-memory store.
pathInfoCacheSize = 0;
}
DummyStoreConfig(std::string_view scheme, std::string_view authority, const Params & params) DummyStoreConfig(std::string_view scheme, std::string_view authority, const Params & params)
: StoreConfig(params) : DummyStoreConfig(params)
{ {
if (!authority.empty()) if (!authority.empty())
throw UsageError("`%s` store URIs must not contain an authority part %s", scheme, authority); throw UsageError("`%s` store URIs must not contain an authority part %s", scheme, authority);

View file

@ -233,30 +233,30 @@ TEST_F(GitTest, both_roundrip)
.contents{ .contents{
{ {
"foo", "foo",
File::Regular{ make_ref<File>(File::Regular{
.contents = "hello\n\0\n\tworld!", .contents = "hello\n\0\n\tworld!",
}, }),
}, },
{ {
"bar", "bar",
File::Directory{ make_ref<File>(File::Directory{
.contents = .contents =
{ {
{ {
"baz", "baz",
File::Regular{ make_ref<File>(File::Regular{
.executable = true, .executable = true,
.contents = "good day,\n\0\n\tworld!", .contents = "good day,\n\0\n\tworld!",
}, }),
}, },
{ {
"quux", "quux",
File::Symlink{ make_ref<File>(File::Symlink{
.target = "/over/there", .target = "/over/there",
}, }),
}, },
}, },
}, }),
}, },
}, },
}; };

View file

@ -35,7 +35,7 @@ struct MemorySourceAccessor : virtual SourceAccessor
{ {
using Name = std::string; using Name = std::string;
std::map<Name, File, std::less<>> contents; std::map<Name, ref<File>, std::less<>> contents;
bool operator==(const Directory &) const noexcept; bool operator==(const Directory &) const noexcept;
// TODO libc++ 16 (used by darwin) missing `std::map::operator <=>`, can't do yet. // TODO libc++ 16 (used by darwin) missing `std::map::operator <=>`, can't do yet.
@ -58,7 +58,7 @@ struct MemorySourceAccessor : virtual SourceAccessor
Stat lstat() const; Stat lstat() const;
}; };
File root{File::Directory{}}; std::optional<File> root;
bool operator==(const MemorySourceAccessor &) const noexcept = default; bool operator==(const MemorySourceAccessor &) const noexcept = default;
@ -89,13 +89,21 @@ struct MemorySourceAccessor : virtual SourceAccessor
SourcePath addFile(CanonPath path, std::string && contents); SourcePath addFile(CanonPath path, std::string && contents);
}; };
inline bool MemorySourceAccessor::File::Directory::operator==( inline bool
const MemorySourceAccessor::File::Directory &) const noexcept = default; MemorySourceAccessor::File::Directory::operator==(const MemorySourceAccessor::File::Directory & other) const noexcept
{
return std::ranges::equal(contents, other.contents, [](const auto & lhs, const auto & rhs) -> bool {
return lhs.first == rhs.first && *lhs.second == *rhs.second;
});
};
inline bool inline bool
MemorySourceAccessor::File::Directory::operator<(const MemorySourceAccessor::File::Directory & other) const noexcept MemorySourceAccessor::File::Directory::operator<(const MemorySourceAccessor::File::Directory & other) const noexcept
{ {
return contents < other.contents; return std::ranges::lexicographical_compare(
contents, other.contents, [](const auto & lhs, const auto & rhs) -> bool {
return lhs.first < rhs.first && *lhs.second < *rhs.second;
});
} }
inline bool MemorySourceAccessor::File::operator==(const MemorySourceAccessor::File &) const noexcept = default; inline bool MemorySourceAccessor::File::operator==(const MemorySourceAccessor::File &) const noexcept = default;

View file

@ -4,7 +4,22 @@ namespace nix {
MemorySourceAccessor::File * MemorySourceAccessor::open(const CanonPath & path, std::optional<File> create) MemorySourceAccessor::File * MemorySourceAccessor::open(const CanonPath & path, std::optional<File> create)
{ {
File * cur = &root; bool hasRoot = root.has_value();
// Special handling of root directory.
if (path.isRoot() && !hasRoot) {
if (create) {
root = std::move(*create);
return &root.value();
}
return nullptr;
}
// Root does not exist.
if (!hasRoot)
return nullptr;
File * cur = &root.value();
bool newF = false; bool newF = false;
@ -24,11 +39,11 @@ MemorySourceAccessor::File * MemorySourceAccessor::open(const CanonPath & path,
i, i,
{ {
std::string{name}, std::string{name},
File::Directory{}, make_ref<File>(File::Directory{}),
}); });
} }
} }
cur = &i->second; cur = &*i->second;
} }
if (newF && create) if (newF && create)
@ -92,7 +107,7 @@ MemorySourceAccessor::DirEntries MemorySourceAccessor::readDirectory(const Canon
if (auto * d = std::get_if<File::Directory>(&f->raw)) { if (auto * d = std::get_if<File::Directory>(&f->raw)) {
DirEntries res; DirEntries res;
for (auto & [name, file] : d->contents) for (auto & [name, file] : d->contents)
res.insert_or_assign(name, file.lstat().type); res.insert_or_assign(name, file->lstat().type);
return res; return res;
} else } else
throw Error("file '%s' is not a directory", path); throw Error("file '%s' is not a directory", path);
@ -112,6 +127,10 @@ std::string MemorySourceAccessor::readLink(const CanonPath & path)
SourcePath MemorySourceAccessor::addFile(CanonPath path, std::string && contents) SourcePath MemorySourceAccessor::addFile(CanonPath path, std::string && contents)
{ {
// Create root directory automatically if necessary as a convenience.
if (!root && !path.isRoot())
open(CanonPath::root, File::Directory{});
auto * f = open(path, File{File::Regular{}}); auto * f = open(path, File{File::Regular{}});
if (!f) if (!f)
throw Error("file '%s' cannot be made because some parent file is not a directory", path); throw Error("file '%s' cannot be made because some parent file is not a directory", path);

View file

@ -52,3 +52,7 @@ rm -rf "$eval_store"
[[ $(nix eval --eval-store "$eval_store?require-sigs=false" --impure --raw --file ./ifd.nix) = hi ]] [[ $(nix eval --eval-store "$eval_store?require-sigs=false" --impure --raw --file ./ifd.nix) = hi ]]
ls $NIX_STORE_DIR/*dependencies-top/foobar ls $NIX_STORE_DIR/*dependencies-top/foobar
(! ls $eval_store/nix/store/*dependencies-top/foobar) (! ls $eval_store/nix/store/*dependencies-top/foobar)
# Can't write .drv by default
(! nix-instantiate dependencies.nix --eval-store "dummy://")
nix-instantiate dependencies.nix --eval-store "dummy://?read-only=false"

View file

@ -406,6 +406,7 @@ nix flake update flake1 flake2/flake1 --flake "$flake3Dir"
# Test 'nix flake metadata --json'. # Test 'nix flake metadata --json'.
nix flake metadata "$flake3Dir" --json | jq . nix flake metadata "$flake3Dir" --json | jq .
nix flake metadata "$flake3Dir" --json --eval-store "dummy://?read-only=false" | jq .
# Test flake in store does not evaluate. # Test flake in store does not evaluate.
rm -rf $badFlakeDir rm -rf $badFlakeDir

View file

@ -219,6 +219,7 @@ in
client.succeed("nix registry pin nixpkgs") client.succeed("nix registry pin nixpkgs")
client.succeed("nix flake metadata nixpkgs --tarball-ttl 0 >&2") client.succeed("nix flake metadata nixpkgs --tarball-ttl 0 >&2")
client.succeed("nix eval nixpkgs#hello --eval-store dummy://?read-only=false >&2")
# Test fetchTree on a github URL. # Test fetchTree on a github URL.
hash = client.succeed(f"nix eval --no-trust-tarballs-from-git-forges --raw --expr '(fetchTree {info['url']}).narHash'") hash = client.succeed(f"nix eval --no-trust-tarballs-from-git-forges --raw --expr '(fetchTree {info['url']}).narHash'")