Patches, and what a patch is relative to

a patch is relative to one extracted tree

A patch is a claim about one tree. Applied against another version it is a different object.

a patch applied to extracted source

A patch is relative to one extracted tree.

a patch applies to the extracted tree, not the URL

A patch is relative to the unpacked tree and a -pN strip, not to the source URL.

What you will be able to do

  1. Read a patches entry, in both its forms, and say where it will be applied from.

  2. Say what a strip level is and why a patch carries one.

  3. Decide whether a change belongs in a patch or upstream.

A patch is the sole place where an easyconfig changes the software rather than describing it. The patch is the parameter with the greatest power and the highest risk of being incorrect without detection.

Two forms

name = 'VASP6'
version = '6.5.1'
toolchain = {'name': 'foss', 'version': '2025a'}

patches = [
 'VASP6-6.5.1_fix-hdf5-detection.patch',
 ('extra-makefile.include', '.'),
 ('vendored-config.h', 'src/config'),
]

The first form is a filename. EasyBuild locates it next to the easyconfig. EasyBuild checks its checksum together with the sources. EasyBuild applies it using patch within the unpacked source tree. The second form is a tuple. The tuple performs a different action. It copies a file into the source tree at the specified path. No patch is used. No diff is used. Only a file is placed where the build expects one. A recipe can provide an entire makefile fragment instead of a diff against one. Some packages require their build configuration as a file. The extra source provides the required file. 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 is a sourcepath. The diff is applied in that subdirectory. A string combined with a name that is not .patch indicates a copy. ('extra-makefile.include', '.') is a copy because of its name; the directory plays no part. ('foo.patch', 'src') does not represent a copy. In either case the file requires a checksum. A checksum is a claim about bytes applies to patches in the same manner as to sources.

The strip level

A diff includes paths. 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 one leading directory component. It searches 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. Specifying an integer sets the strip level explicitly.

patches = [('odd-layout.patch', 2)]

The failure documented in An easyconfig is Python repeats. The strip level is relative to the unpacked source tree. The parameter precisely describes the shape of the source rather than the software. Switching from a git checkout to a release tarball changes the number of leading components without warning, as start_dir did.

A clean apply can still be a wrong apply

patch provides more help than utility. patch matches based on context, not line numbers. patch applies a moved hunk and reports the offset. patch applies a fuzzy hunk, matches approximately, and reports that too. Both are usually correct and occasionally catastrophic. A hunk can apply to the wrong function when surrounding lines are similar. A patch that applies with offsets against a new version says nothing about correctness. This indicates that patch found a plausible location. After a version bump, a patch that still applies raises the same suspicion as a start_dir that still parses. The check reads the patched file; the output of patch is no evidence.

When it should not be a patch

A patch is 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, which is a copy rather than 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).
  1. What has patch told you, and what has it not?

  2. What would you read to find out whether the patch did the right thing?

  3. 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 that states 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.

What to remember

  • A patch is a claim about a file at a path inside the unpacked archive.

  • The search is for a name, not for a hunk. A moved file is a missed patch.

  • The level is how many path components to strip, not a quality score.