跳转到主要内容

用于解析和操作RPM spec文件的库。

项目描述

specfile

Python库,用于解析和操作RPM spec文件。主要关注修改现有的spec文件,任何更改都应产生最小的差异。

动机

最初,rebase-helper 提供了一个用于修改 spec 文件的 API,该 API 也被 packit 使用。本项目的目标是使接口更通用,更方便使用,不仅限于 packit,也适用于其他需要与 RPM spec 文件交互的 Python 项目。

本库中使用的术语

节是 spec 文件的一个节,它有一个以 % 字符开头的明确名称,后面可以可选地跟有参数。

为了方便,本库中省略了节名开头的 %

存在一个内部称为 %package 的特殊节,通常也称为序言,它表示位于第一个命名节(通常是 %description)之前的内容。本节包含主要包元数据(标签)。子包的元数据定义在后续的 %package 节中,这些节不是匿名的,并且总是跟有指定子包名称的参数(例如 %package doc%package -n completely-different-subpackage-name)。

标签

标签代表包的元数据的一个单独项。它有一个明确的名称和值。标签定义在 %package 节中。

对于本库的目的,标签可以关联注释。这些是直接位于 spec 文件中标签定义之上的连续注释行。

源是一个源文件或由 Source/Patch 标签或由 %sourcelist/%patchlist 节中的条目定义的下流补丁。

源可以是本地源,由文件名指定,也可以是远程源,由 URL 指定。本地源应位于称为 sourcedir 的目录中。远程源应下载到该目录。

由标签定义的源可以明确编号,例如 Source0Patch999,否则会进行隐式编号,源编号按顺序自动分配。

Prep 宏

Prep 宏是在 %prep 节中经常出现(并且只在其中出现,在其他地方没有意义)的宏。

本库识别了 4 个这样的宏,分别是 %setup%patch%autosetup%autopatch。一个典型的 spec 文件使用 %autosetup%setup%patch%autopatch 的组合。

文档

从代码生成的完整文档.

示例和使用场景

以下示例应涵盖 packit 需要的使用场景。

实例化

from specfile import Specfile

# using an absolute path
specfile = Specfile('/tmp/test.spec')

# using a relative path and a different sourcedir
specfile = Specfile('test.spec', sourcedir='/tmp/sources')

重新加载

# if the spec file happens to be modified externally, it can be reloaded
specfile.reload()

保存更改

# no autosave
specfile = Specfile('test.spec')
...
# saving explicitly when needed
specfile.save()

# enabling autosave, changes are saved immediately after any modification
specfile = Specfile('test.spec', autosave=True)

# as a context manager, saving is performed at context exit
with Specfile('test.spec') as specfile:
    ...

定义和取消定义宏

# override macros loaded from system macro files
specfile = Specfile('test.spec', macros=[('fedora', '38'), ('dist', '.fc38')])

# undefine a system macro (in case it's defined)
specfile = Specfile('test.spec', macros=[('rhel', None)])

低级操作

with specfile.sections() as sections:
    # replacing the content of a section
    sections.prep = ['%autosetup -p1']
    # removing a section
    del sections.changelog
    # swapping two sections
    sections[1], sections[2] = sections[2], sections[1]
    # accessing a section with arguments
    print(sections.get('package devel'))
    # inserting a line into a section
    sections.build.insert(0, 'export VERBOSE=1')

# copying a section from one specfile to another
with specfile1.sections() as sections1, with specfile2.sections() as sections2:
    sections2.changelog[:] = sections1.changelog

中级操作 - 标签、变更日志和 prep

# accessing tags in preamble
with specfile.tags() as tags:
    # name of the first tag
    print(tags[0].name)
    # raw value of the first tag
    print(tags[0].value)
    # expanded value of the first tag
    print(tags[0].expanded_value)
    # comments associated with the first tag
    print(tags[0].comments)
    # value of a tag by name
    print(tags.url)
    tags.url = 'https://example.com'

# accessing tags in subpackages
with specfile.tags('package devel') as tags:
    print(tags.requires)

# working with changelog
with specfile.changelog() as changelog:
    # most recent changelog entry
    print(changelog[-1])
    # making changes
    changelog[1].content.append('- another line')
    # removing the oldest entry
    del changelog[0]

# working with macros in %prep section, supports %setup, %patch, %autosetup and %autopatch
from specfile.prep import AutosetupMacro

with specfile.prep() as prep:
    # name of the first macro
    print(prep.macros[0].name)
    # checking if %autosetup is being used
    print('%autosetup' in prep)
    print(AutosetupMacro in prep)
    # changing macro options
    prep.autosetup.options.n = '%{srcname}-%{version}'
    # adding a new %patch macro
    prep.add_patch_macro(28, p=1, b='.test')
    # removing an existing %patch macro by name
    del prep.patch0
    # this works for both '%patch0' and '%patch -P0'
    prep.remove_patch_macro(0)

高级操作

版本和发布

# getting version and release
print(specfile.version)
print(specfile.release)

# setting version and release
specfile.version = '2.1'
specfile.release = '3'

# setting both at the same time (release defaults to 1)
specfile.set_version_and_release('2.1', release='3')

# setting version while trying to preserve macros
specfile.set_version_and_release('2.1', preserve_macros=True)

增加发布版本

要增加发布版本并添加新的变更日志条目,可以使用以下代码

from specfile import Specfile

with Specfile("example.spec") as spec:
    spec.release = str(int(spec.expanded_release) + 1)
    spec.add_changelog_entry("- Bumped release for test purposes")

变更日志

# adding a new entry, author is automatically determined
# (using the same heuristics that rpmdev-packager uses) if possible
# this function already honors autochangelog
specfile.add_changelog_entry('- New upstream release 2.1')

# adding a new entry, specifying author and timestamp explicitly
specfile.add_changelog_entry(
    '- New upstream release 2.1',
    author='Nikola Forró',
    email='nforro@redhat.com',
    timestamp=datetime.date(2021, 11, 20),
)

if specfile.has_autochangelog:
    # do something

源和补丁

with specfile.sources() as sources:
    # expanded location of the first source
    print(sources[0].expanded_location)
    # adding a source
    sources.append('tests.tar.gz')

with specfile.patches() as patches:
    # modifying location of the first patch
    patches[0].location = 'downstream.patch'
    # removing comments associated with the last patch
    patches[-1].comments.clear()
    # adding and removing patches
    patches.append('another.patch')
    del patches[2]
    # inserting a patch with a specific number
    patches.insert_numbered(999, 'final.patch')

# adding a single patch
specfile.add_patch('necessary.patch', comment='a human-friendly comment to the patch')

其他属性

print(specfile.name)
print(specfile.license)
print(specfile.summary)
specfile.url = 'https://example.com'

注意,如果您想访问多个标签值,使用 tags 上下文管理器可能会更快。

# same as above, but roughly 4x times faster (parsing/saving happens only once)
with specfile.tags() as tags:
    print(tags.name.value)
    print(tags.license.value)
    print(tags.summary.value)
    tags.url.value = 'https://example.com'

只读访问

如果您不需要写入访问,您可以使用上下文管理器的 content 属性,避免使用 with 语句。

# no changes done to the tags object will be saved
tags = specfile.tags().content

print(tags.version.expanded_value)
print(tags.release.expanded_value)

# number of sources
print(len(specfile.sources().content))

有效性

宏定义、标签、%sourcelist/%patchlist 条目以及源/补丁都有一个 valid 属性。如果一个实体不在任何条件假分支中,则认为它是有效的。

在规范文件中考虑以下内容

%if 0%{?fedora} >= 36
Recommends: %{name}-selinux
%endif

如果没有其他 Recommends 标签,则以下代码将根据 %fedora 宏的值打印 TrueFalse

with specfile.tags() as tags:
    print(tags.recommends.valid)

您可以使用构造函数的 macros 参数或通过修改 Specfile 实例的 macros 属性来定义宏或重新定义/取消定义系统宏。

相同的规则也适用于 %ifarch/%ifos 语句

%ifarch %{java_arches}
BuildRequires: java-devel
%endif

如果没有其他 BuildRequires 标签,则如果当前平台是 %java_arches 的一部分,则以下代码将打印 True

with specfile.tags() as tags:
    print(tags.buildrequires.valid)

要覆盖此行为,您需要重新定义系统宏 %_target_cpu(或者 %_target_os,如果是 %ifos 的情况)。

视频

以下是一个演示 Specfile.update_tag() 方法及其用例的示例

Demo of Specfile.update_tag() functionality

项目详情


下载文件

下载适用于您的平台的文件。如果您不确定选择哪个,请了解更多关于 安装包 的信息。

源分发

specfile-0.32.3.tar.gz (107.1 kB 查看散列值)

上传时间

构建分发

specfile-0.32.3-py3-none-any.whl (64.7 kB 查看散列值)

上传时间 Python 3

由以下机构支持

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF 赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误记录 StatusPage StatusPage 状态页面