问题
rpm spec 中的 Patch
(或者 Source
)能否指定目录前缀?
如能否以如下的方式指定文件路径:
Source0: a/b/c
Patch0: a/b/c
代码分析
%autopatch
宏实现如下,通过 lua 内置函数 ipairs
遍历 patches
table 遍历,得到每个通过 Patch
定义的补丁路径,并最终应用补丁:
## /lib/rpm/macros
# Single patch application
%apply_patch(qp:m:)\
%{lua:\
local file = rpm.expand("%{1}")\
local num = rpm.expand("%{2}")\
if posix.access(file, "r") then\
local options = rpm.expand("%{-q} %{-p:-p%{-p*}} %{-m:-m%{-m*}}")\
local scm_apply = rpm.expand("%__scm_apply_%{__scm}")\
print(rpm.expand("%{uncompress:"..file.."} | "..scm_apply.." "..options.." "..file.." "..num.."\\n"))\
else\
print("echo 'Cannot read "..file.."'; exit 1;".."\\n")\
end}
# Automatically apply all patches
# -m<min> Apply patches with number >= min only
# -M<max> Apply patches with number <= max only
%autopatch(vp:m:M:)\
%{lua:\
local options = rpm.expand("%{!-v:-q} %{-p:-p%{-p*}} ")\
local low_limit = tonumber(rpm.expand("%{-m:%{-m*}}"))\
local high_limit = tonumber(rpm.expand("%{-M:%{-M*}}"))\
for i, p in ipairs(patches) do\
local inum = patch_nums[i]\
if ((not low_limit or inum>=low_limit) and (not high_limit or inum<=high_limit)) \
then\
print(rpm.expand("%apply_patch -m %{basename:"..p.."} "..options..p.." "..i.."\\n")) \
end\
end}
其中 patches
(sources
类似),是通过解析 spec 文件得到的 lua table 类型变量,来自于如下代码:
// https://github.com/rpm-software-management/rpm/blob/rpm-4.17.0-beta1/build/speclua.c
static const char * luavars[] = { "patches", "sources",
"patch_nums", "source_nums", NULL, };
void addLuaSource(rpmlua lua, const struct Source *p)
{
lua_State *L = rpmluaGetLua(lua);
const char * what = (p->flags & RPMBUILD_ISPATCH) ? "patches" : "sources";
lua_getglobal(L, what);
lua_pushstring(L, p->path);
lua_rawseti(L, -2, lua_rawlen(L, -2) + 1);
lua_pop(L, 1);
what = (p->flags & RPMBUILD_ISPATCH) ? "patch_nums" : "source_nums";
lua_getglobal(L, what);
lua_pushinteger(L, p->num);
lua_rawseti(L, -2, lua_rawlen(L, -2) + 1);
lua_pop(L, 1);
}
然而,在解析 Patch
(或者 Source
)定义的路径时会自动忽略文件名前面的文件夹(即最终的文件路径 p->path
为 %{_sourcedir}/filename
):
// https://github.com/rpm-software-management/rpm/blob/rpm-4.17.0-beta1/build/parsePreamble.c#L125
static struct Source *newSource(uint32_t num, const char *path, int flags)
{
struct Source *p = xmalloc(sizeof(*p));
p->num = num;
p->fullSource = xstrdup(path);
p->flags = flags;
p->source = strrchr(p->fullSource, '/');
if (p->source) {
const char *buf = strrchr(p->source,'=');
if (buf)
p->source = buf;
p->source++;
} else {
p->source = p->fullSource;
}
p->path = rpmGetPath("%{_sourcedir}/", p->source, NULL);
return p;
}
揭晓答案
以如下的 Patch
定义为例:
Patch0: a/b/c
通过上面对代码的分析可知,在 ipairs(patches)
遍历时,临时变量 p
是 %{_sourcedir}/c
,即传递给 apply_patch
宏进行应用补丁操作时得到的路径就是 %{_sourcedir}/c
。
因此,答案是补丁的路径里带上了文件夹路径不会报错,但是文件名前的目录会自动忽略。
此外,需要注意 %apply_patch
宏定义中 %{basename:"..p.."}
是 -m
选项的参数(裸的 patch
命令会直接忽略该参数),patch
命令的参数是后面的 p
:
%apply_patch -m %{basename:"..p.."} "..options..p.." "..i.."\\n
扩展阅读
从 debian quilt 补丁中生成 rpm spec 补丁
首先生成 Patch
定义:
%{lua:
local i = 1
for l in io.lines('debian/patches/series') do
print(string.format('Patch%04d:\t%s\n', i, l))
i = i + 1
end
}
然后,重新定义 %autopatch
宏,在 %apply_patch
时传递正确的路径:
%define autopatch(vp:m:M:)\
%{lua:\
local options = rpm.expand("%{!-v:-q} %{-p:-p%{-p*}} ")\
local low_limit = tonumber(rpm.expand("%{-m:%{-m*}}"))\
local high_limit = tonumber(rpm.expand("%{-M:%{-M*}}"))\
for i, p in ipairs(patches) do\
local inum = patch_nums[i]\
if ((not low_limit or inum>=low_limit) and (not high_limit or inum<=high_limit)) \
then\
print(rpm.expand("%apply_patch -m %{basename:"..p.."} "..options.."debian/patches/%{basename:"..p.."} "..i.."\\n")) \
end\
end}
需要注意 p
是带 %{_sourcedir}/
前缀的全路径,因此需要加上一个 basename
的处理。
最后,通过 rpmspec -P <name>spec
和 rpmbuild -bp <name>.spec
命令即可很容易的验证修改后的 spec 文件的正确性。
注意:rpm 的多行宏定义需要在每行行尾添加反斜杠进行多行连接,如上面的 %autopatch
宏定义,但是如果是在 spec 文件里直接写 lua 脚本,则不需要。
这里有一个最大的问题是生成 srcrpm 的时候由于找不到补丁文件,所以构建时会报错:
error: Bad file: ./0049-PVE-savevm-async-register-yank-before-migration_inco.patch: No such file or directory
error: Bad file: ./0048-PVE-whitelist-invalid-QAPI-names-for-backwards-compa.patch: No such file or directory
由于这是 rpmbuild C 源代码层面的逻辑:
// https://github.com/rpm-software-management/rpm/blob/rpm-4.17.0-beta1/build/files.c#L2675
for (srcPtr = spec->sources; srcPtr != NULL; srcPtr = srcPtr->next) {
char * sfn = rpmGetPath( ((srcPtr->flags & RPMBUILD_ISNO) ? "!" : ""),
srcPtr->path, NULL);
argvAdd(&files, sfn);
free(sfn);
}
for (ARGV_const_t fp = files; *fp != NULL; fp++) {
if (stat(diskPath, &flp->fl_st)) {
rpmlog(RPMLOG_ERR, _("Bad file: %s: %s\n"),
diskPath, strerror(errno));
fl.processingFailed = 1;
}
}
所以唯一的规避办法就是执行 rpmbuild 的时候使用 -bb
选项忽略源代码包的构建。
rpm spec preamble
Source
与 Patch
定义的位置有严格的要求,对于同一个 package(spec 可以定义多个 package)所有的 preamble tag 必须集中定义,比如,如下的 spec 写法是错误的:
%description
Source1: x1
Patch1: 1.patch
而如下的 spec 写法是正确的:
Source1: x1
Patch1: 1.patch
%description
参考资料
rpm spec 内置宏定义
https://rpm.org/user_doc/macros.html
Lua in RPM
https://rpm.org/user_doc/lua.html
The Preamble
http://ftp.rpm.org/max-rpm/s1-rpm-specref-preamble.html
最后修改于 2021-08-10