Skip to content

Commit

Permalink
Add builtins.patch
Browse files Browse the repository at this point in the history
This replaces the 'patches' argument to builtins.fetchTree with
something more generic. So instead of 'builtins.fetchTree { patches =
... }' you can do 'builtins.patch { src = builtins.fetchTree { ... };
patchFiles = ... }'.
  • Loading branch information
edolstra committed Jun 10, 2022
1 parent 2b30df7 commit 5f13402
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 20 deletions.
19 changes: 0 additions & 19 deletions src/libexpr/primops/fetchTree.cc
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ static void fetchTree(
) {
fetchers::Input input;
PathSet context;
std::vector<std::string> patches;

state.forceValue(*args[0], pos);

Expand All @@ -137,19 +136,6 @@ static void fetchTree(
for (auto & attr : *args[0]->attrs) {
if (attr.name == state.sType) continue;

if (state.symbols[attr.name] == "patches") {
state.forceList(*attr.value, attr.pos);

for (auto elem : attr.value->listItems()) {
// FIXME: use realisePath
PathSet context;
auto patchFile = state.coerceToPath(pos, *elem, context);
patches.push_back(patchFile.readFile());
}

continue;
}

state.forceValue(*attr.value, attr.pos);

if (attr.value->type() == nPath || attr.value->type() == nString) {
Expand Down Expand Up @@ -200,9 +186,6 @@ static void fetchTree(
if (params.returnPath) {
auto [accessor, input2] = input.lazyFetch(state.store);

if (!patches.empty())
accessor = makePatchingInputAccessor(accessor, patches);

emitTreeAttrs(
state,
{ state.registerAccessor(accessor), CanonPath::root },
Expand All @@ -211,8 +194,6 @@ static void fetchTree(
params.emptyRevFallback,
false);
} else {
assert(patches.empty());

auto [tree, input2] = input.fetch(state.store);

auto storePath = state.store->printStorePath(tree.storePath);
Expand Down
124 changes: 124 additions & 0 deletions src/libexpr/primops/patch.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#include "primops.hh"

namespace nix {

static void prim_patch(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
std::vector<std::string> patches;
std::optional<SourcePath> src;

state.forceAttrs(*args[0], pos);

for (auto & attr : *args[0]->attrs) {
std::string_view n(state.symbols[attr.name]);

auto check = [&]()
{
if (!patches.empty())
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("'builtins.patch' does not support both 'patches' and 'patchFiles'"),
.errPos = state.positions[attr.pos]
}));
};

if (n == "src") {
PathSet context;
src.emplace(state.coerceToPath(pos, *attr.value, context));
}

else if (n == "patchFiles") {
check();
state.forceList(*attr.value, attr.pos);
for (auto elem : attr.value->listItems()) {
// FIXME: use realisePath
PathSet context;
auto patchFile = state.coerceToPath(attr.pos, *elem, context);
patches.push_back(patchFile.readFile());
}
}

else if (n == "patches") {
check();
state.forceList(*attr.value, attr.pos);
for (auto elem : attr.value->listItems())
patches.push_back(std::string(state.forceStringNoCtx(*elem, attr.pos)));
}

else
throw Error({
.msg = hintfmt("attribute '%s' isn't supported in call to 'builtins.patch'", n),
.errPos = state.positions[pos]
});
}

if (!src)
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("attribute 'src' is missing in call to 'builtins.patch'"),
.errPos = state.positions[pos]
}));

if (!src->path.isRoot())
throw UnimplementedError("applying patches to a non-root path ('%s') is not yet supported", src->path);

auto accessor = makePatchingInputAccessor(ref(src->accessor.shared_from_this()), patches);

v.mkPath(SourcePath { state.registerAccessor(accessor), src->path });
}

static RegisterPrimOp primop_patch({
.name = "__patch",
.args = {"args"},
.doc = R"(
Apply patches to a source tree. This function has the following required argument:
- src\
The input source tree.
It also takes one of the following:
- patchFiles\
A list of patch files to be applied to `src`.
- patches\
A list of patches (i.e. strings) to be applied to `src`.
It returns a source tree that lazily and non-destructively
applies the specified patches to `src`.
Example:
```nix
let
tree = builtins.patch {
src = fetchTree {
type = "github";
owner = "NixOS";
repo = "patchelf";
rev = "be0cc30a59b2755844bcd48823f6fbc8d97b93a7";
};
patches = [
''
diff --git a/src/patchelf.cc b/src/patchelf.cc
index 6882b28..28f511c 100644
--- a/src/patchelf.cc
+++ b/src/patchelf.cc
@@ -1844,6 +1844,8 @@ void showHelp(const std::string & progName)
int mainWrapped(int argc, char * * argv)
{
+ printf("Hello!");
+
if (argc <= 1) {
showHelp(argv[0]);
return 1;
''
];
};
in builtins.readFile (tree + "/src/patchelf.cc")
```
)",
.fun = prim_patch,
});

}
2 changes: 1 addition & 1 deletion src/libfetchers/input-accessor.hh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace nix {

MakeError(RestrictedPathError, Error);

struct InputAccessor
struct InputAccessor : public std::enable_shared_from_this<InputAccessor>
{
const size_t number;

Expand Down

0 comments on commit 5f13402

Please sign in to comment.