Skip to content

Commit

Permalink
fetchTree: Support applying patches
Browse files Browse the repository at this point in the history
You can now write

  fetchTree {
    type = "github";
    owner = "NixOS";
    repo = "nixpkgs";
    rev = "0f316e4d72daed659233817ffe52bf08e081b5de";
    patches = [ ./thunderbird-1.patch ./thunderbird-2.patch ];
  };

to apply a list of patches to a tree. These are applied lazily - the
patched tree is not materialized unless you do something that causes
the entire tree to be copied to the store (like 'src = fetchTree {
... }'). The equivalent of '-p1' is implied.

File additions/deletions/renames are not yet handled.

Issue NixOS#3920.
  • Loading branch information
edolstra committed Mar 3, 2022
1 parent 5f51154 commit 77dc9b0
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 0 deletions.
19 changes: 19 additions & 0 deletions src/libexpr/primops/fetchTree.cc
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ static void fetchTree(
) {
fetchers::Input input;
PathSet context;
std::vector<std::string> patches;

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

Expand All @@ -130,7 +131,22 @@ static void fetchTree(

for (auto & attr : *args[0]->attrs) {
if (attr.name == state.sType) continue;

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

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

continue;
}

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

if (attr.value->type() == nPath || attr.value->type() == nString) {
auto s = state.coerceToString(*attr.pos, *attr.value, context, false, false).toOwned();
attrs.emplace(attr.name,
Expand Down Expand Up @@ -178,6 +194,9 @@ static void fetchTree(

auto [accessor, input2] = input.lazyFetch(state.store);

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

//state.allowPath(tree.storePath);

emitTreeAttrs(
Expand Down
4 changes: 4 additions & 0 deletions src/libfetchers/input-accessor.hh
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ ref<MemoryInputAccessor> makeMemoryInputAccessor();

ref<InputAccessor> makeZipInputAccessor(PathView path);

ref<InputAccessor> makePatchingInputAccessor(
ref<InputAccessor> next,
const std::vector<std::string> & patches);

struct SourcePath
{
ref<InputAccessor> accessor;
Expand Down
115 changes: 115 additions & 0 deletions src/libfetchers/patching-input-accessor.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#include "input-accessor.hh"

namespace nix {

// TODO: handle file creation / deletion.
struct PatchingInputAccessor : InputAccessor
{
ref<InputAccessor> next;

std::map<Path, std::vector<std::string>> patchesPerFile;

PatchingInputAccessor(
ref<InputAccessor> next,
const std::vector<std::string> & patches)
: next(next)
{
/* Extract the patches for each file. */
for (auto & patch : patches) {
std::string_view p = patch;
std::string_view start;
std::string_view fileName;

auto flush = [&]()
{
if (start.empty()) return;
auto contents = start.substr(0, p.data() - start.data());
start = "";
auto slash = fileName.find('/');
if (slash == fileName.npos) return;
fileName = fileName.substr(slash);
debug("found patch for '%s'", fileName);
patchesPerFile.emplace(Path(fileName), std::vector<std::string>())
.first->second.push_back(std::string(contents));
};

while (!p.empty()) {
auto [line, rest] = getLine(p);

if (hasPrefix(line, "--- ")) {
flush();
start = p;
fileName = line.substr(4);
}

if (!start.empty()) {
if (!(hasPrefix(line, "+++ ")
|| hasPrefix(line, "@@")
|| hasPrefix(line, "+")
|| hasPrefix(line, "-")
|| hasPrefix(line, " ")))
{
flush();
}
}

p = rest;
}

flush();
}
}

std::string readFile(PathView path) override
{
auto contents = next->readFile(path);

auto i = patchesPerFile.find((Path) path);
if (i != patchesPerFile.end()) {
for (auto & patch : i->second) {
auto tempDir = createTempDir();
AutoDelete del(tempDir);
auto sourceFile = tempDir + "/source";
auto rejFile = tempDir + "/source.rej";
writeFile(sourceFile, contents);
try {
contents = runProgram("patch", true, {"--quiet", sourceFile, "--output=-", "-r", rejFile}, patch);
} catch (ExecError & e) {
del.cancel();
throw;
}
}
}

return contents;
}

bool pathExists(PathView path) override
{
return next->pathExists(path);
}

Stat lstat(PathView path) override
{
return next->lstat(path);
}

DirEntries readDirectory(PathView path) override
{
return next->readDirectory(path);
}

std::string readLink(PathView path) override
{
return next->readLink(path);
}
};

ref<InputAccessor> makePatchingInputAccessor(
ref<InputAccessor> next,
const std::vector<std::string> & patches)
{
return make_ref<PatchingInputAccessor>(next, std::move(patches));
}

}
36 changes: 36 additions & 0 deletions src/libutil/tests/tests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,42 @@ namespace nix {
ASSERT_THROW(base64Decode("cXVvZCBlcm_0IGRlbW9uc3RyYW5kdW0="), Error);
}

/* ----------------------------------------------------------------------------
* getLine
* --------------------------------------------------------------------------*/

TEST(getLine, all) {
{
auto [line, rest] = getLine("foo\nbar\nxyzzy");
ASSERT_EQ(line, "foo");
ASSERT_EQ(rest, "bar\nxyzzy");
}

{
auto [line, rest] = getLine("foo\r\nbar\r\nxyzzy");
ASSERT_EQ(line, "foo");
ASSERT_EQ(rest, "bar\r\nxyzzy");
}

{
auto [line, rest] = getLine("foo\n");
ASSERT_EQ(line, "foo");
ASSERT_EQ(rest, "");
}

{
auto [line, rest] = getLine("foo");
ASSERT_EQ(line, "foo");
ASSERT_EQ(rest, "");
}

{
auto [line, rest] = getLine("");
ASSERT_EQ(line, "");
ASSERT_EQ(rest, "");
}
}

/* ----------------------------------------------------------------------------
* toLower
* --------------------------------------------------------------------------*/
Expand Down
15 changes: 15 additions & 0 deletions src/libutil/util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1542,6 +1542,21 @@ std::string stripIndentation(std::string_view s)
}


std::pair<std::string_view, std::string_view> getLine(std::string_view s)
{
auto newline = s.find('\n');

if (newline == s.npos) {
return {s, ""};
} else {
auto line = s.substr(0, newline);
if (!line.empty() && line[line.size() - 1] == '\r')
line = line.substr(0, line.size() - 1);
return {line, s.substr(newline + 1)};
}
}


//////////////////////////////////////////////////////////////////////


Expand Down
6 changes: 6 additions & 0 deletions src/libutil/util.hh
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,12 @@ std::string base64Decode(std::string_view s);
std::string stripIndentation(std::string_view s);


/* Get the prefix of 's' up to and excluding the next line break (LF
optionally preceded by CR), and the remainder following the line
break. */
std::pair<std::string_view, std::string_view> getLine(std::string_view s);


/* Get a value for the specified key from an associate container. */
template <class T>
std::optional<typename T::mapped_type> get(const T & map, const typename T::key_type & key)
Expand Down

0 comments on commit 77dc9b0

Please sign in to comment.