Patches, and what a patch is relative to¶
What you will be able to do
Read a
patchesentry, in both its forms, and say where it will be applied from.Say what a strip level is and why a patch carries one.
Decide whether a change belongs in a patch or upstream.
A patch is the only place an easyconfig changes the software rather than describing it. That makes it the parameter with the most power and the one most likely to be wrong in a way nothing notices.
Two forms¶
The first form represents a filename.
EasyBuild locates it next to the easyconfig, checks its checksum together with the sources, and applies it using patch within the unpacked source tree.
The second form is a tuple and performs a different action: it copies a file into the source tree at the specified path.
No patch, no diff, only a file placed where the build expects one.
This is how a recipe provides an entire makefile fragment instead of a diff against one.
Some packages require their build configuration as a file, and this method delivers it.
The second element does not imply that a directory means copy.
An integer denotes a strip level.
A string combined with a name ending in .patch constitutes a sourcepath: apply the diff in that subdirectory.
A string combined with a name that is not .patch indicates a copy.
('extra-makefile.include', '.') represents a copy due to the name, not the directory.
('foo.patch', 'src') does not represent a copy.
In either case the file requires a checksum.org][A checksum is a claim about bytes]] applies to patches in the same manner as to sources.
The strip level¶
A diff includes paths, and those paths contain a non‑useful prefix.
--- vasp.6.5.1/src/main.F 2025-03-01 10:00:00
+++ vasp.6.5.1-fixed/src/main.F 2025-03-02 11:00:00
patch -p1 removes a single leading directory component, causing it to search for src/main.F.
patch -p0 searches for vasp.6.5.1/src/main.F.
EasyBuild does not automatically use -p1 as the default.
When no integer is supplied, apply_patch infers the strip level from the +++ paths relative to the unpacked tree.
git diff output is typically applied with -p1, while specifying an integer sets the strip level explicitly:
patches = [('odd-layout.patch', 2)]
That repeats the failure documented in An easyconfig is Python.
The strip level is relative to the unpacked source tree.
It is precisely the type of parameter that describes the shape of the source rather than the software.
Switching from a git checkout to a release tarball causes the number of leading components to change silently, in the same way start_dir did.
Applying cleanly is not the same as applying correctly¶
patch is more helpful than useful.
patch matches on context, not on line numbers.
It will apply a hunk that has moved, and report the offset.
It will apply a hunk with fuzz, matching approximately, and report that too.
Both are usually right and occasionally catastrophic.
A hunk can apply to the wrong function, if that function happens to have similar surrounding lines.
So a patch that applies with offsets against a new version is not evidence that it still does the right thing.
It is evidence that patch found somewhere plausible to put it.
After a version bump, a patch that still applies deserves the same suspicion as a start_dir that still parses.
The check is to read the patched file, not the patch output.
When it should not be a patch¶
A patch represents a maintained fork.
Consider three questions before creating a patch.
Is it a bug? If so, it belongs upstream, and the patch remains while the pull request is open.
Indicate this in the patch’s header, include a link, and inform the next maintainer whether to drop it.
Is it a build-system fact about the site? It likely belongs in configopts or preconfigopts instead of a diff.
A patch that only changes a path will conflict on each version bump without justification.
Is it a configuration file the package expects to be written? It is the copy form above and not truly a patch.
A long‑lived patch is appropriate for a package whose upstream is unresponsive or absent, serving as its maintenance.
In that situation, the patch should include a comment explaining this, as an undocumented patch is indistinguishable from one unintentionally retained.
EB-Patches-1 — It still applies, and it is still wrong
A recipe carries patches = ['fix-alignment.patch'] and has done for
three versions. You bump to a fourth. The build succeeds and the patch
output says:
patching file src/kernel.c
Hunk #1 succeeded at 412 (offset 87 lines).
What has
patchtold you, and what has it not?What would you read to find out whether the patch did the right thing?
The patch has no comment and no upstream link. What do you do about that, and what do you write down either way?
Solution
It has told you that it found a place where the context matched, 87 lines from where the patch expected it. It has not told you that the place it found is the place you meant. Context matching does not know what a function is for.
Read the patched file. Find the hunk in src/kernel.c at its new
location and check that the surrounding code is the code the patch was
written against. If the function was refactored, “applied with offset”
and “applied to the right thing” have come apart.
For the missing comment: find out whether the fix is upstream now. If it is, delete the patch and note the version it landed in. If it is not, add a header to the patch saying what it fixes and linking the issue or pull request, so the next person can answer this question without archaeology.
Either way, write the answer in the commit message. A patch that survives a bump looked, are indistinguishable a year later, and only one of them is safe.