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:
commit
ab7feb3898
9 changed files with 153 additions and 38 deletions
|
|
@ -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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
},
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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'")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue