在打包程序工作流程中使用 Diffoscope

在打包者的角色中,更新包是一项经常性的任务。 对于某些项目,打包者参与上游维护,或者编写良好的发行说明可以很容易地找出版本之间的变化。 情况并非总是如此,例如由 github 上某处的一两个人维护的一些小项目,验证究竟发生了什么变化可能很有用。 衍射镜 可以帮助确定软件包版本之间的更改。

衍射镜 是一个“智能二进制差异”工具,诞生于 可重现的构建 Debian 中的项目,也可以在 Fedora. 它“了解”各种类型的文本和二进制格式,并将尝试递归解包和比较两个 blob。 特别是它知道在比较之前需要解压缩一些对象,需要解压缩档案,以及如何解构二进制对象,如 ELF 程序和库、Java .jar 文件、Windows .cab 文件等。

就在今天,我收到了一个错误报告,指出有一个新版本的 python-libarchive-c 可用(3.2,而 3.1 是当前打包的)。 它是一个简单的 Python 包。 但即使是一个简单的 Python 包也有一些二进制文件,所以对未打包的 rpm 进行直接比较并不能真正起作用。 让我们看看如何 衍射镜 可用于详细显示二进制包之间的差异。

比较上游档案

第一步是对比上游档案:

$ diffoscope python-libarchive-c-3.{1,2}.tar.gz
+++ python-libarchive-c-3.2.tar.gz
│   --- python-libarchive-c-3.1.tar        ❶
├── +++ python-libarchive-c-3.2.tar
│ ├── file list
│ │ @@ -1,46 +1,46 @@                      ❷
│ │ -drwxrwxr-x  0 root (0) root (0)     0 2021-06-01 07:32:24.000000 python-libarchive-c-3.1/                     
│ │ --rw-rw-r--  0 root (0) root (0)    25 2021-06-01 07:32:24.000000 python-libarchive-c-3.1/.gitattributes
│ │ -drwxrwxr-x  0 root (0) root (0)     0 2021-06-01 07:32:24.000000 python-libarchive-c-3.1/.github/
│ │ --rw-rw-r--  0 root (0) root (0)    20 2021-06-01 07:32:24.000000 python-libarchive-c-3.1/.github/FUNDING.yml
...
│ │ --rw-rw-r--  0 root (0) root (0)  1331 2021-06-01 07:32:24.000000 python-libarchive-c-3.1/version.py
│ │ +drwxrwxr-x  0 root (0) root (0)     0 2021-10-06 12:40:03.000000 python-libarchive-c-3.2/
│ │ +-rw-rw-r--  0 root (0) root (0)    25 2021-10-06 12:40:03.000000 python-libarchive-c-3.2/.gitattributes
│ │ +drwxrwxr-x  0 root (0) root (0)     0 2021-10-06 12:40:03.000000 python-libarchive-c-3.2/.github/
│ │ +-rw-rw-r--  0 root (0) root (0)    20 2021-10-06 12:40:03.000000 python-libarchive-c-3.2/.github/FUNDING.yml
...
│ │ +-rw-rw-r--  0 root (0) root (0)  1331 2021-10-06 12:40:03.000000 python-libarchive-c-3.2/version.py
...
│ │   --- python-libarchive-c-3.1/libarchive/ffi.py
│ ├── +++ python-libarchive-c-3.2/libarchive/ffi.py
│ │┄ Files 0% similar despite different names
│ │ @@ -43,15 +43,15 @@
│ │  SEEK_CALLBACK = CFUNCTYPE(
│ │ -    c_longlong, c_int, c_void_p, c_longlong, c_int ❸
│ │ +    c_longlong, c_void_p, c_void_p, c_longlong, c_int
│ │  )
│ │   --- python-libarchive-c-3.1/libarchive/read.py
│ ├── +++ python-libarchive-c-3.2/libarchive/read.py
│ │┄ Files 2% similar despite different names
│ │ @@ -61,17 +61,18 @@
│ │      close_cb = CLOSE_CALLBACK(close_func) if close_func else NO_CLOSE_CB
│ │ +    seek_cb = SEEK_CALLBACK(seek_func)
│ │      with new_archive_read(format_name, filter_name, passphrase) as archive_p:
│ │          if seek_func:
│ │ -            ffi.read_set_seek_callback(archive_p, SEEK_CALLBACK(seek_func))
│ │ +            ffi.read_set_seek_callback(archive_p, seek_cb)
│ │          ffi.read_open(archive_p, None, open_cb, read_cb, close_cb)
│ │          yield archive_read_class(archive_p)
...
│ │   --- python-libarchive-c-3.1/libarchive/write.py
│ ├── +++ python-libarchive-c-3.2/libarchive/write.py
│ │┄ Files identical despite different names
│ │   --- python-libarchive-c-3.1/setup.py              ❹
│ ├── +++ python-libarchive-c-3.2/setup.py
│ │┄ Files identical despite different names
...
│ │   --- python-libarchive-c-3.1/version.py
│ ├── +++ python-libarchive-c-3.2/version.py
│ │┄ Files 1% similar despite different names
│ │ @@ -9,15 +9,15 @@
│ │  def get_version():
│ │      # Return the version if it has been injected into the file by git-archive
│ │ -    version = tag_re.search('HEAD -> master, tag: 3.1')
│ │ +    version = tag_re.search('HEAD -> master, tag: 3.2') ❺
│ │      if version:
│ │          return version.group(1)

在❶处,我们看到我们正在比较两个档案。 (注意:为了便于阅读,此差异输出已被大量修剪。)

在❷处,我们看到两个档案的列表不同,但差异是意料之中的:档案中顶级目录的名称中包含版本号,因此所有路径都不同。 我们还看到,这两个档案的创建日期不同(分别为 2021-06-01 07:32:24 和 2021-10-06 12:40:03)。 如果上游意外添加或删除文件,此列表将提醒我们。

在❸处,我们看到 SEEK_CALLBACK 函数中有一些代码更改。
上游发行说明 关于它说“这个版本修复了由 custom_reader 和 stream_reader 函数传递给 libarchive 的 seek 回调”,所以这个变化看起来很合理。

大多数文件都没有改变,所以在 ❹ diffoscope 尽职尽责地报告它们是相同的,尽管文件名发生了变化……而在 ❺ 我们得到了另一个与版本相关的更改。

就是这样 – 上游 tarball 不再有任何变化。
然后让我们构建包并将其与之前的构建进行比较。

比较二进制包

为新版本调整好spec文件后,第二步是构建包并与旧版本进行比较。 fedpkg mockbuild 将生成的包放在以版本命名的子目录中:

$ fedpkg mockbuild
...
INFO: Results and/or logs in: ~/fedora/python-libarchive-c/results_python-libarchive-c/3.2/1.fc36

$ diffoscope results_python-libarchive-c/3.1/3.fc36/python3-libarchive-c-3.1-3.fc36.noarch.rpm 
             results_python-libarchive-c/3.2/1.fc36/python3-libarchive-c-3.2-1.fc36.noarch.rpm
+++ results_python-libarchive-c/3.2/1.fc36/python3-libarchive-c-3.2-1.fc36.noarch.rpm
├── header
│ @@ -1,79 +1,79 @@            ❶
│ -HEADERIMMUTABLE: 0000003d00001ed50000003f...1000003e8000000060000+HEADERIMMUTABLE: 0000003d00001e8d0000003f...1000003e8000000060000

在二进制包的情况下,与上游 tarball 相比,存在更多差异。 但是,让我们尝试通过它们。

│  HEADERI18NTABLE:
│   - C
 -SIGSIZE: 26733              
 -SIGMD5: 1aa148ac91484fe8cb55fe3334aae10b
 -SHA1HEADER: 1659a1431af930a0a824c193780e27f28fc2d03e
 -SHA256HEADER: 60e4f84e905bd42693cabe88e63542916a7dfffef052f3e7499cb80a1770c736
+SIGSIZE: 26678+SIGMD5: e35e3157e01b6ec26b8e1981f0ba38af+SHA1HEADER: faab0b7ee86b23f753b2c49a52d6a8f3deefc7ca+SHA256HEADER: 66d39a70dc9e081ba5cc4243e72f434e953d68c9bce8be1127b2b87fa1923d06
│  NAME: python3-libarchive-c
│ -VERSION: 3.1                ❸
│ -RELEASE: 3.fc36+VERSION: 3.2
 +RELEASE: 1.fc36
│  SUMMARY: Python interface to libarchive
│  DESCRIPTION: The libarchive library provides a flexible interface for reading and writing archives in various
│  formats such as tar and cpio. libarchive also supports reading and writing archives compressed using
│  various compression filters such as gzip and bzip2.  A Python interface to libarchive. It uses the
│  standard ctypes module to dynamically load and access the C library.
│ -BUILDTIME: 1638015932+BUILDTIME: 1638015863       ❹
│  BUILDHOST: spora.local
│ -SIZE: 68979+SIZE: 69052
│  LICENSE: CC0
│  GROUP: Unspecified
│  URL: https://github.com/Changaco/python-libarchive-c
│  OS: linux
│  ARCH: noarch

我们可以看到 衍射镜 在两个档案中执行类似于 rpmdiff 的操作,但具有更多细节。
在 ❶ 我们看到 rpm 标头发生了变化,这并不奇怪 ? 在 ❷ 我们得到了签名的详细信息,以及在 ❸ 的版本信息。 构建时间戳和 rpm 大小在 ❹ 处也可能很有趣。 衍射镜 然后打印 rpm 标头中的 FILESIZES、FILEMTIMES、FILEMD5S 表的比较。 如果我们试图找出包之间的一些意外差异,这将很有用。

│  CHANGELOGTIME:
│ + - 1638014400
│   - 1627387200
...
│ - - 1570104000 - - 1566216000
│  CHANGELOGNAME:
│ + - Zbigniew Jędrzejewski-Szmek  3.2-1
│   - Fedora Release Engineering  - 3.1-2
...
│ - - Miro Hrončok  - 2.8-10
 - - Miro Hrončok  - 2.8-9
│  CHANGELOGTEXT:
│ + - - Version 3.2 (fixes #2027027)
│   - - Second attempt - Rebuilt for   https://fedoraproject.org/wiki/Fedora_35_Mass_Rebuild
...
│ - - - Rebuilt for Python 3.8.0rc1 (#1748018)
 - - - Rebuilt for Python 3.8

在这里,我们看到更改日志被修剪(添加了我的条目,删除了来自 Miro 的两个条目)。 在 Fedora 我们将 %_changelog_trimage 设置为 2 年,因此即使规范文件定义了更长的变更日志,在构建的包中最旧的条目也会被修剪掉。

然后我们得到一些预期但重要的区别:

│  PROVIDEVERSION:
│ - - 3.1-3.fc36+ - 3.2-1.fc36       ❶
│  OBSOLETEVERSION:
│ - - 3.1-3.fc36+ - 3.2-1.fc36
...
│  DIRNAMES:           ❷
│ - - /usr/lib/python3.10/site-packages/libarchive_c-3.1-py3.10.egg-info/ + - /usr/lib/python3.10/site-packages/libarchive_c-3.2-py3.10.egg-info/
...
│ @@ -1 +1 @@
│ -RPM v3.0 bin i386/x86_64 python3-libarchive-c-3.1-3.fc36+RPM v3.0 bin i386/x86_64 python3-libarchive-c-3.2-1.fc36
├── content
│ ├── file list
│ │ @@ -1,35 +1,35 @@
│ │ -drwxr-xr-x   1   0 0    0 2021-10-27 12:20:33.000000 ./usr/lib/python3.10/site-packages/libarchive
│ │ --rw-r--r--   1   0 0  601 2021-06-01 07:32:24.000000 ./usr/lib/python3.10/site-packages/libarchive/__init__.py
│ │ -drwxr-xr-x   1   0 0    0 2021-10-27 12:20:34.000000 ./usr/lib/python3.10/site-packages/libarchive/__pycache__
...
│ │ +drwxr-xr-x   1   0 0    0 2021-11-27 12:24:23.000000 ./usr/lib/python3.10/site-packages/libarchive    
│ │ +-rw-r--r--   1   0 0  601 2021-10-06 12:40:03.000000 ./usr/lib/python3.10/site-packages/libarchive/__init__.py
│ │ +drwxr-xr-x   1   0 0    0 2021-11-27 12:24:24.000000 ./usr/lib/python3.10/site-packages/libarchive/__pycache__
...
│ ├── ./usr/lib/python3.10/site-packages/libarchive/ffi.py
│ │ @@ -43,15 +43,15 @@
│ │  SEEK_CALLBACK = CFUNCTYPE(
│ │ -    c_longlong, c_int, c_void_p, c_longlong, c_int ❺
│ │ +    c_longlong, c_void_p, c_void_p, c_longlong, c_int
│ │  )
│ │ @@ -61,17 +61,18 @@
│ │      close_cb = CLOSE_CALLBACK(close_func) if close_func else NO_CLOSE_CB
│ │ +    seek_cb = SEEK_CALLBACK(seek_func)
│ │      with new_archive_read(format_name, filter_name, passphrase) as archive_p:
│ │          if seek_func:
│ │ -            ffi.read_set_seek_callback(archive_p, SEEK_CALLBACK(seek_func))
│ │ +            ffi.read_set_seek_callback(archive_p, seek_cb)
│ │          ffi.read_open(archive_p, None, open_cb, read_cb, close_cb)
...

在❶处,我们看到包版本更改反映在 rpm 标头中的 PROVIDEVERSION 和 OBSOLETEVERSION 表中。 还有 PROVIDENAME 和 OBSOLETENAME 表,但在这种情况下这些表没有变化。 这很好:我们预计不会对提供或过时的产品进行任何更改,除了版本冲突。

在❷处,我们再次看到版本反映在路径中。

您可以看到 ❸ 和 ❹ 是两个包中经过大量修剪的文件列表。 由于修改时间更改,所有文件都报告为不同。 但请仔细查看时间戳: __init__.py 是上游提供的文件,并且 mtime 在安装过程中被保留,因此我们看到与上游 tarball 比较的第一个列表中相同的时间戳。 但是 __pycache__ 目录是在构建期间创建的,并且有一个时间戳,显示构建完成的时间。 对于构建期间生成的其他文件,我们会看到相同的结果。

现在是无聊的部分:

│ ├── ./usr/lib/python3.10/site-packages/libarchive/__pycache__/__init__.cpython-310.pyc
│ │ @@ -1,8 +1,8 @@
│ │ -00000000: 6f0d 0d0a 0000 0000 88e2 b560 5902 0000  o..........`Y... ❶
│ │ +00000000: 6f0d 0d0a 0000 0000 2399 5d61 5902 0000  o.......#.]aY...
│ ├── ./usr/lib/python3.10/site-packages/libarchive/__pycache__/entry.cpython-310.pyc
│ │ @@ -1,8 +1,8 @@
│ │ -00000000: 6f0d 0d0a 0000 0000 88e2 b560 0c14 0000  o..........`.... ❷
│ │ +00000000: 6f0d 0d0a 0000 0000 2399 5d61 0c14 0000  o.......#.]a....
...
│ │   --- ./usr/lib/python3.10/site-packages/libarchive_c-3.1-py3.10.egg-info/PKG-INFO
│ ├── +++ ./usr/lib/python3.10/site-packages/libarchive_c-3.2-py3.10.egg-info/PKG-INFO
│ │┄ Files 0% similar despite different names
│ │ @@ -1,10 +1,10 @@
│ │  Name: libarchive-c
│ │ -Version: 3.1     ❸
│ │ +Version: 3.2
│ │  Summary: Python interface to libarchive
│ │  Home-page: https://github.com/Changaco/python-libarchive-c

… 是的, 扩散镜 显示 .pyc 文件的 hexdump 列表之间的差异(❶ 和 ❷ 处的 Python 字节码)。 这些文件是在构建过程中创建的,因此我们知道这些更改对应于对前面清单中显示的源的更改。 最后,我们再次看到 PKG-INFO 文件中的版本更改 (❸)。

目前,diffoscope 不知道如何以更好的方式显示 Python 字节码。 但有可能在未来它将能够将字节码反汇编成更易读的形式,并在其上显示差异。 新的解析器会定期添加到 diffoscope。 对于已编译的程序,它已经在反汇编的机器代码上显示了差异。

结论

在查看了所有这些差异之后,我认为可以肯定地说从 python-libarchive-c-3.1 升级到 python-libarchive-c-3.2 是安全的。 特别是,它甚至适用于稳定版本,因为它只有一个错误修复。

向 Chris Lamb 和 diffoscope 的其他维护者大声疾呼。