diff options
| author | Maxim Cournoyer <maxim.cournoyer@gmail.com> | 2023-01-21 15:04:09 -0500 |
|---|---|---|
| committer | Maxim Cournoyer <maxim.cournoyer@gmail.com> | 2023-02-19 21:13:23 -0500 |
| commit | 598f4c509bbfec2b983a8ee246cce0a0fe45ec7f (patch) | |
| tree | 1bf799843929f714428c27eb2325c255a92793f3 | |
| parent | ac1d530d56c1a259630c8873b2281033878a4acb (diff) | |
pack: Add RPM format.
* guix/rpm.scm: New file.
* guix/scripts/pack.scm (rpm-archive): New procedure.
(%formats): Register it.
(show-formats): Add it.
(guix-pack): Register supported extra-options for the rpm format.
* tests/pack.scm (rpm-for-tests): New variable.
("rpm archive can be installed/uninstalled"): New test.
* tests/rpm.scm: New test.
* doc/guix.texi (Invoking guix pack): Document it.
| -rw-r--r-- | Makefile.am | 2 | ||||
| -rw-r--r-- | doc/guix.texi | 46 | ||||
| -rw-r--r-- | guix/rpm.scm | 623 | ||||
| -rw-r--r-- | guix/scripts/pack.scm | 230 | ||||
| -rw-r--r-- | tests/pack.scm | 57 | ||||
| -rw-r--r-- | tests/rpm.scm | 86 |
6 files changed, 1031 insertions, 13 deletions
diff --git a/Makefile.am b/Makefile.am index 5ce6cc84f4c..8e3815b9c22 100644 --- a/Makefile.am +++ b/Makefile.am | |||
| @@ -111,6 +111,7 @@ MODULES = \ | |||
| 111 | guix/derivations.scm \ | 111 | guix/derivations.scm \ |
| 112 | guix/grafts.scm \ | 112 | guix/grafts.scm \ |
| 113 | guix/repl.scm \ | 113 | guix/repl.scm \ |
| 114 | guix/rpm.scm \ | ||
| 114 | guix/transformations.scm \ | 115 | guix/transformations.scm \ |
| 115 | guix/inferior.scm \ | 116 | guix/inferior.scm \ |
| 116 | guix/describe.scm \ | 117 | guix/describe.scm \ |
| @@ -535,6 +536,7 @@ SCM_TESTS = \ | |||
| 535 | tests/pypi.scm \ | 536 | tests/pypi.scm \ |
| 536 | tests/read-print.scm \ | 537 | tests/read-print.scm \ |
| 537 | tests/records.scm \ | 538 | tests/records.scm \ |
| 539 | tests/rpm.scm \ | ||
| 538 | tests/scripts.scm \ | 540 | tests/scripts.scm \ |
| 539 | tests/search-paths.scm \ | 541 | tests/search-paths.scm \ |
| 540 | tests/services.scm \ | 542 | tests/services.scm \ |
diff --git a/doc/guix.texi b/doc/guix.texi index 44e2165a82a..05615b95498 100644 --- a/doc/guix.texi +++ b/doc/guix.texi | |||
| @@ -6896,6 +6896,7 @@ such file or directory'' message. | |||
| 6896 | @end quotation | 6896 | @end quotation |
| 6897 | 6897 | ||
| 6898 | @item deb | 6898 | @item deb |
| 6899 | @cindex Debian, build a .deb package with guix pack | ||
| 6899 | This produces a Debian archive (a package with the @samp{.deb} file | 6900 | This produces a Debian archive (a package with the @samp{.deb} file |
| 6900 | extension) containing all the specified binaries and symbolic links, | 6901 | extension) containing all the specified binaries and symbolic links, |
| 6901 | that can be installed on top of any dpkg-based GNU(/Linux) distribution. | 6902 | that can be installed on top of any dpkg-based GNU(/Linux) distribution. |
| @@ -6912,7 +6913,8 @@ guix pack -f deb -C xz -S /usr/bin/hello=bin/hello hello | |||
| 6912 | Because archives produced with @command{guix pack} contain a collection | 6913 | Because archives produced with @command{guix pack} contain a collection |
| 6913 | of store items and because each @command{dpkg} package must not have | 6914 | of store items and because each @command{dpkg} package must not have |
| 6914 | conflicting files, in practice that means you likely won't be able to | 6915 | conflicting files, in practice that means you likely won't be able to |
| 6915 | install more than one such archive on a given system. | 6916 | install more than one such archive on a given system. You can |
| 6917 | nonetheless pack as many Guix packages as you want in one such archive. | ||
| 6916 | @end quotation | 6918 | @end quotation |
| 6917 | 6919 | ||
| 6918 | @quotation Warning | 6920 | @quotation Warning |
| @@ -6923,6 +6925,48 @@ shared by other software, such as a Guix installation or other, non-deb | |||
| 6923 | packs. | 6925 | packs. |
| 6924 | @end quotation | 6926 | @end quotation |
| 6925 | 6927 | ||
| 6928 | @item rpm | ||
| 6929 | @cindex RPM, build an RPM archive with guix pack | ||
| 6930 | This produces an RPM archive (a package with the @samp{.rpm} file | ||
| 6931 | extension) containing all the specified binaries and symbolic links, | ||
| 6932 | that can be installed on top of any RPM-based GNU/Linux distribution. | ||
| 6933 | The RPM format embeds checksums for every file it contains, which the | ||
| 6934 | @command{rpm} command uses to validate the integrity of the archive. | ||
| 6935 | |||
| 6936 | Advanced RPM-related options are revealed via the | ||
| 6937 | @option{--help-rpm-format} option. These options allow embedding | ||
| 6938 | maintainer scripts that can run before or after the installation of the | ||
| 6939 | RPM archive, for example. | ||
| 6940 | |||
| 6941 | The RPM format supports relocatable packages via the @option{--prefix} | ||
| 6942 | option of the @command{rpm} command, which can be handy to install an | ||
| 6943 | RPM package to a specific prefix. | ||
| 6944 | |||
| 6945 | @example | ||
| 6946 | guix pack -f rpm -R -C xz -S /usr/bin/hello=bin/hello hello | ||
| 6947 | @end example | ||
| 6948 | |||
| 6949 | @example | ||
| 6950 | sudo rpm --install --prefix=/opt /gnu/store/...-hello.rpm | ||
| 6951 | @end example | ||
| 6952 | |||
| 6953 | @quotation Note | ||
| 6954 | Contrary to Debian packages, conflicting but @emph{identical} files in | ||
| 6955 | RPM packages can be installed simultaneously, which means multiple | ||
| 6956 | @command{guix pack}-produced RPM packages can usually be installed side | ||
| 6957 | by side without any problem. | ||
| 6958 | @end quotation | ||
| 6959 | |||
| 6960 | @quotation Warning | ||
| 6961 | @command{rpm} assumes ownership of any files contained in the pack, | ||
| 6962 | which means it will remove @file{/gnu/store} upon uninstalling a | ||
| 6963 | Guix-generated RPM package, unless the RPM package was installed with | ||
| 6964 | the @option{--prefix} option of the @command{rpm} command. It is unwise | ||
| 6965 | to install Guix-produced @samp{.rpm} packages on a system where | ||
| 6966 | @file{/gnu/store} is shared by other software, such as a Guix | ||
| 6967 | installation or other, non-rpm packs. | ||
| 6968 | @end quotation | ||
| 6969 | |||
| 6926 | @end table | 6970 | @end table |
| 6927 | 6971 | ||
| 6928 | @cindex relocatable binaries | 6972 | @cindex relocatable binaries |
diff --git a/guix/rpm.scm b/guix/rpm.scm new file mode 100644 index 00000000000..1cb8326a9b2 --- /dev/null +++ b/guix/rpm.scm | |||
| @@ -0,0 +1,623 @@ | |||
| 1 | ;;; GNU Guix --- Functional package management for GNU | ||
| 2 | ;;; Copyright © 2023 Maxim Cournoyer <maxim.cournoyer@gmail.com> | ||
| 3 | ;;; | ||
| 4 | ;;; This file is part of GNU Guix. | ||
| 5 | ;;; | ||
| 6 | ;;; GNU Guix is free software; you can redistribute it and/or modify it | ||
| 7 | ;;; under the terms of the GNU General Public License as published by | ||
| 8 | ;;; the Free Software Foundation; either version 3 of the License, or (at | ||
| 9 | ;;; your option) any later version. | ||
| 10 | ;;; | ||
| 11 | ;;; GNU Guix is distributed in the hope that it will be useful, but | ||
| 12 | ;;; WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 13 | ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 14 | ;;; GNU General Public License for more details. | ||
| 15 | ;;; | ||
| 16 | ;;; You should have received a copy of the GNU General Public License | ||
| 17 | ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. | ||
| 18 | |||
| 19 | (define-module (guix rpm) | ||
| 20 | #:autoload (gcrypt hash) (hash-algorithm file-hash md5) | ||
| 21 | #:use-module (guix build utils) | ||
| 22 | #:use-module (ice-9 format) | ||
| 23 | #:use-module (ice-9 match) | ||
| 24 | #:use-module (ice-9 textual-ports) | ||
| 25 | #:use-module (rnrs bytevectors) | ||
| 26 | #:use-module (srfi srfi-1) | ||
| 27 | #:use-module (srfi srfi-9) | ||
| 28 | #:use-module (srfi srfi-26) | ||
| 29 | #:use-module (srfi srfi-71) | ||
| 30 | #:use-module (srfi srfi-171) | ||
| 31 | #:export (generate-lead | ||
| 32 | generate-signature | ||
| 33 | generate-header | ||
| 34 | assemble-rpm-metadata | ||
| 35 | |||
| 36 | ;; XXX: These are internals, but the inline disabling trick | ||
| 37 | ;; doesn't work on them. | ||
| 38 | make-header-entry | ||
| 39 | header-entry? | ||
| 40 | header-entry-tag | ||
| 41 | header-entry-count | ||
| 42 | header-entry-value | ||
| 43 | |||
| 44 | bytevector->hex-string | ||
| 45 | |||
| 46 | fhs-directory?)) | ||
| 47 | |||
| 48 | ;;; Commentary: | ||
| 49 | ;;; | ||
| 50 | ;;; This module provides the building blocks required to construct RPM | ||
| 51 | ;;; archives. It is intended to be importable on the build side, so shouldn't | ||
| 52 | ;;; depend on (guix diagnostics) or other host-side-only modules. | ||
| 53 | ;;; | ||
| 54 | ;;; Code: | ||
| 55 | |||
| 56 | (define (gnu-system-triplet->machine-type triplet) | ||
| 57 | "Return the machine component of TRIPLET, a GNU system triplet." | ||
| 58 | (first (string-split triplet #\-))) | ||
| 59 | |||
| 60 | (define (gnu-machine-type->rpm-arch type) | ||
| 61 | "Return the canonical RPM architecture string, given machine TYPE." | ||
| 62 | (match type | ||
| 63 | ("arm" "armv7hl") | ||
| 64 | ("powerpc" "ppc") | ||
| 65 | ("powerpc64le" "ppc64le") | ||
| 66 | (machine machine))) ;unchanged | ||
| 67 | |||
| 68 | (define (gnu-machine-type->rpm-number type) | ||
| 69 | "Translate machine TYPE to its corresponding RPM integer value." | ||
| 70 | ;; Refer to the rpmrc.in file in the RPM source for the complete | ||
| 71 | ;; translation tables. | ||
| 72 | (match type | ||
| 73 | ((or "i486" "i586" "i686" "x86_64") 1) | ||
| 74 | ((? (cut string-prefix? "powerpc" <>)) 5) | ||
| 75 | ("mips64el" 11) | ||
| 76 | ((? (cut string-prefix? "arm" <>)) 12) | ||
| 77 | ("aarch64" 19) | ||
| 78 | ((? (cut string-prefix? "riscv" <>)) 22) | ||
| 79 | (_ (error "no RPM number known for machine type" type)))) | ||
| 80 | |||
| 81 | (define (u16-number->u8-list number) | ||
| 82 | "Return a list of byte values made of NUMBER, a 16 bit unsigned integer." | ||
| 83 | (let ((bv (uint-list->bytevector (list number) (endianness big) 2))) | ||
| 84 | (bytevector->u8-list bv))) | ||
| 85 | |||
| 86 | (define (u32-number->u8-list number) | ||
| 87 | "Return a list of byte values made of NUMBER, a 32 bit unsigned integer." | ||
| 88 | (let ((bv (uint-list->bytevector (list number) (endianness big) 4))) | ||
| 89 | (bytevector->u8-list bv))) | ||
| 90 | |||
| 91 | (define (s32-number->u8-list number) | ||
| 92 | "Return a list of byte values made of NUMBER, a 32 bit signed integer." | ||
| 93 | (let ((bv (sint-list->bytevector (list number) (endianness big) 4))) | ||
| 94 | (bytevector->u8-list bv))) | ||
| 95 | |||
| 96 | (define (u8-list->u32-number lst) | ||
| 97 | "Return the 32 bit unsigned integer corresponding to the 4 bytes in LST." | ||
| 98 | (bytevector-u32-ref (u8-list->bytevector lst) 0 (endianness big))) | ||
| 99 | |||
| 100 | |||
| 101 | ;;; | ||
| 102 | ;;; Lead section. | ||
| 103 | ;;; | ||
| 104 | |||
| 105 | ;; Refer to the docs/manual/format.md file of the RPM source for the details | ||
| 106 | ;; regarding the binary format of an RPM archive. | ||
| 107 | (define* (generate-lead name-version #:key (target %host-type)) | ||
| 108 | "Generate a RPM lead u8-list that uses NAME-VERSION, the name and version | ||
| 109 | string of the package, and TARGET, a GNU triplet used to derive the target | ||
| 110 | machine type." | ||
| 111 | (define machine-type (gnu-system-triplet->machine-type target)) | ||
| 112 | (define magic (list #xed #xab #xee #xdb)) | ||
| 113 | (define file-format-version (list 3 0)) ;3.0 | ||
| 114 | (define type (list 0 0)) ;0 for binary packages | ||
| 115 | (define arch-number (u16-number->u8-list | ||
| 116 | (gnu-machine-type->rpm-number machine-type))) | ||
| 117 | ;; The 66 bytes from 10 to 75 are for the name-version-release string. | ||
| 118 | (define name | ||
| 119 | (let ((padding-bytes (make-list (- 66 (string-length name-version)) 0))) | ||
| 120 | (append (bytevector->u8-list (string->utf8 name-version)) | ||
| 121 | padding-bytes))) | ||
| 122 | ;; There is no OS number corresponding to GNU/Hurd (GNU), only Linux, per | ||
| 123 | ;; rpmrc.in. | ||
| 124 | (define os-number (list 0 1)) | ||
| 125 | |||
| 126 | ;; For RPM format 3.0, the signature type is 5, which means a "Header-style" | ||
| 127 | ;; signature. | ||
| 128 | (define signature-type (list 0 5)) | ||
| 129 | |||
| 130 | (define reserved-bytes (make-list 16 0)) | ||
| 131 | |||
| 132 | (append magic file-format-version type arch-number name | ||
| 133 | os-number signature-type reserved-bytes)) | ||
| 134 | |||
| 135 | |||
| 136 | ;;; | ||
| 137 | ;;; Header section. | ||
| 138 | ;;; | ||
| 139 | |||
| 140 | (define header-magic (list #x8e #xad #xe8)) | ||
| 141 | (define header-version (list 1)) | ||
| 142 | (define header-reserved (make-list 4 0)) ;4 reserved bytes | ||
| 143 | ;;; Every header starts with 8 bytes made by the header magic number, the | ||
| 144 | ;;; header version and 4 reserved bytes. | ||
| 145 | (define header-intro (append header-magic header-version header-reserved)) | ||
| 146 | |||
| 147 | ;;; Header entry data types. | ||
| 148 | (define NULL 0) | ||
| 149 | (define CHAR 1) | ||
| 150 | (define INT8 2) | ||
| 151 | (define INT16 3) ;2-bytes aligned | ||
| 152 | (define INT32 4) ;4-bytes aligned | ||
| 153 | (define INT64 5) ;8-bytes aligned | ||
| 154 | (define STRING 6) | ||
| 155 | (define BIN 7) | ||
| 156 | (define STRING_ARRAY 8) | ||
| 157 | (define I18NSTRIN_TYPE 9) | ||
| 158 | |||
| 159 | ;;; Header entry tags. | ||
| 160 | (define-record-type <rpm-tag> | ||
| 161 | (make-rpm-tag number type) | ||
| 162 | rpm-tag? | ||
| 163 | (number rpm-tag-number) | ||
| 164 | (type rpm-tag-type)) | ||
| 165 | |||
| 166 | ;;; The following are internal tags used to identify the data sections. | ||
| 167 | (define RPMTAG_HEADERSIGNATURES (make-rpm-tag 62 BIN)) ;signature header | ||
| 168 | (define RPMTAG_HEADERIMMUTABLE (make-rpm-tag 63 BIN)) ;main/data header | ||
| 169 | (define RPMTAG_HEADERI18NTABLE (make-rpm-tag 100 STRING_ARRAY)) | ||
| 170 | |||
| 171 | ;;; Subset of RPM tags from include/rpm/rpmtag.h. | ||
| 172 | (define RPMTAG_NAME (make-rpm-tag 1000 STRING)) | ||
| 173 | (define RPMTAG_VERSION (make-rpm-tag 1001 STRING)) | ||
| 174 | (define RPMTAG_RELEASE (make-rpm-tag 1002 STRING)) | ||
| 175 | (define RPMTAG_SUMMARY (make-rpm-tag 1004 STRING)) | ||
| 176 | (define RPMTAG_SIZE (make-rpm-tag 1009 INT32)) | ||
| 177 | (define RPMTAG_LICENSE (make-rpm-tag 1014 STRING)) | ||
| 178 | (define RPMTAG_OS (make-rpm-tag 1021 STRING)) | ||
| 179 | (define RPMTAG_ARCH (make-rpm-tag 1022 STRING)) | ||
| 180 | (define RPMTAG_PREIN (make-rpm-tag 1023 STRING)) | ||
| 181 | (define RPMTAG_POSTIN (make-rpm-tag 1024 STRING)) | ||
| 182 | (define RPMTAG_PREUN (make-rpm-tag 1025 STRING)) | ||
| 183 | (define RPMTAG_POSTUN (make-rpm-tag 1026 STRING)) | ||
| 184 | (define RPMTAG_FILESIZES (make-rpm-tag 1028 INT32)) | ||
| 185 | (define RPMTAG_FILEMODES (make-rpm-tag 1030 INT16)) | ||
| 186 | (define RPMTAG_FILEDIGESTS (make-rpm-tag 1035 STRING_ARRAY)) | ||
| 187 | (define RPMTAG_FILELINKTOS (make-rpm-tag 1036 STRING_ARRAY)) | ||
| 188 | (define RPMTAG_FILEUSERNAME (make-rpm-tag 1039 STRING_ARRAY)) | ||
| 189 | (define RPMTAG_GROUPNAME (make-rpm-tag 1040 STRING_ARRAY)) | ||
| 190 | (define RPMTAG_PREFIXES (make-rpm-tag 1098 STRING_ARRAY)) | ||
| 191 | (define RPMTAG_DIRINDEXES (make-rpm-tag 1116 INT32)) | ||
| 192 | (define RPMTAG_BASENAMES (make-rpm-tag 1117 STRING_ARRAY)) | ||
| 193 | (define RPMTAG_DIRNAMES (make-rpm-tag 1118 STRING_ARRAY)) | ||
| 194 | (define RPMTAG_PAYLOADFORMAT (make-rpm-tag 1124 STRING)) | ||
| 195 | (define RPMTAG_PAYLOADCOMPRESSOR (make-rpm-tag 1125 STRING)) | ||
| 196 | (define RPMTAG_LONGFILESIZES (make-rpm-tag 5008 INT64)) | ||
| 197 | (define RPMTAG_LONGSIZE (make-rpm-tag 5009 INT64)) | ||
| 198 | ;;; The algorithm used to compute the digest of each file, e.g. RPM_HASH_MD5. | ||
| 199 | (define RPMTAG_FILEDIGESTALGO (make-rpm-tag 5011 INT32)) | ||
| 200 | ;;; RPMTAG_ENCODING specifies the encoding used for strings, e.g. "utf-8". | ||
| 201 | (define RPMTAG_ENCODING (make-rpm-tag 5062 STRING)) | ||
| 202 | ;;; Compressed payload digest. Its type is a string array, but currently in | ||
| 203 | ;;; practice it is equivalent to STRING, since only the first element is used. | ||
| 204 | (define RPMTAG_PAYLOADDIGEST (make-rpm-tag 5092 STRING_ARRAY)) | ||
| 205 | ;;; The algorithm used to compute the payload digest, e.g. RPM_HASH_SHA256. | ||
| 206 | (define RPMTAG_PAYLOADDIGESTALGO (make-rpm-tag 5093 INT32)) | ||
| 207 | ;;; The following are taken from the rpmHashAlgo_e enum in rpmcrypto.h. | ||
| 208 | (define RPM_HASH_MD5 1) | ||
| 209 | (define RPM_HASH_SHA256 8) | ||
| 210 | |||
| 211 | ;;; Other useful internal definitions. | ||
| 212 | (define REGION_TAG_COUNT 16) ;number of bytes | ||
| 213 | (define INT32_MAX (1- (expt 2 32))) ;4294967295 bytes (unsigned) | ||
| 214 | |||
| 215 | (define (rpm-tag->u8-list tag) | ||
| 216 | "Return the u8 list corresponding to RPM-TAG, a <rpm-tag> object." | ||
| 217 | (append (u32-number->u8-list (rpm-tag-number tag)) | ||
| 218 | (u32-number->u8-list (rpm-tag-type tag)))) | ||
| 219 | |||
| 220 | (define-record-type <header-entry> | ||
| 221 | (make-header-entry tag count value) | ||
| 222 | header-entry? | ||
| 223 | (tag header-entry-tag) ;<rpm-tag> | ||
| 224 | (count header-entry-count) ;number (u32) | ||
| 225 | (value header-entry-value)) ;string|number|list|... | ||
| 226 | |||
| 227 | (define (entry-type->alignement type) | ||
| 228 | "Return the byte alignment of TYPE, an RPM header entry type." | ||
| 229 | (cond ((= INT16 type) 2) | ||
| 230 | ((= INT32 type) 4) | ||
| 231 | ((= INT64 type) 8) | ||
| 232 | (else 1))) | ||
| 233 | |||
| 234 | (define (next-aligned-offset offset alignment) | ||
| 235 | "Return the next position from OFFSET which satisfies ALIGNMENT." | ||
| 236 | (if (= 0 (modulo offset alignment)) | ||
| 237 | offset | ||
| 238 | (next-aligned-offset (1+ offset) alignment))) | ||
| 239 | |||
| 240 | (define (header-entry->data entry) | ||
| 241 | "Return the data of ENTRY, a <header-entry> object, as a u8 list." | ||
| 242 | (let* ((tag (header-entry-tag entry)) | ||
| 243 | (count (header-entry-count entry)) | ||
| 244 | (value (header-entry-value entry)) | ||
| 245 | (number (rpm-tag-number tag)) | ||
| 246 | (type (rpm-tag-type tag))) | ||
| 247 | (cond | ||
| 248 | ((= STRING type) | ||
| 249 | (unless (string? value) | ||
| 250 | (error "expected string value for STRING type, got" value)) | ||
| 251 | (unless (= 1 count) | ||
| 252 | (error "count must be 1 for STRING type")) | ||
| 253 | (let ((value (cond ((= (rpm-tag-number RPMTAG_VERSION) number) | ||
| 254 | ;; Hyphens are not allowed in version strings. | ||
| 255 | (string-map (match-lambda | ||
| 256 | (#\- #\+) | ||
| 257 | (c c)) | ||
| 258 | value)) | ||
| 259 | (else value)))) | ||
| 260 | (append (bytevector->u8-list (string->utf8 value)) | ||
| 261 | (list 0)))) ;strings must end with null byte | ||
| 262 | ((= STRING_ARRAY type) | ||
| 263 | (unless (list? value) | ||
| 264 | (error "expected a list of strings for STRING_ARRAY type, got" value)) | ||
| 265 | (unless (= count (length value)) | ||
| 266 | (error "expected count to be equal to" (length value) 'got count)) | ||
| 267 | (append-map (lambda (s) | ||
| 268 | (append (bytevector->u8-list (string->utf8 s)) | ||
| 269 | (list 0))) ;null byte separated | ||
| 270 | value)) | ||
| 271 | ((member type (list INT8 INT16 INT32)) | ||
| 272 | (if (= 1 count) | ||
| 273 | (unless (number? value) | ||
| 274 | (error "expected number value for scalar INT type; got" value)) | ||
| 275 | (unless (list? value) | ||
| 276 | (error "expected list value for array INT type; got" value))) | ||
| 277 | (if (list? value) | ||
| 278 | (cond ((= INT8 type) value) | ||
| 279 | ((= INT16 type) (append-map u16-number->u8-list value)) | ||
| 280 | ((= INT32 type) (append-map u32-number->u8-list value)) | ||
| 281 | (else (error "unexpected type" type))) | ||
| 282 | (cond ((= INT8 type) (list value)) | ||
| 283 | ((= INT16 type) (u16-number->u8-list value)) | ||
| 284 | ((= INT32 type) (u32-number->u8-list value)) | ||
| 285 | (else (error "unexpected type" type))))) | ||
| 286 | ((= BIN type) | ||
| 287 | (unless (list? value) | ||
| 288 | (error "expected list value for BIN type; got" value)) | ||
| 289 | value) | ||
| 290 | (else (error "unimplemented type" type))))) | ||
| 291 | |||
| 292 | (define (make-header-index+data entries) | ||
| 293 | "Return the index and data sections as u8 number lists, via multiple values. | ||
| 294 | An index is composed of four u32 (16 bytes total) quantities, in order: tag, | ||
| 295 | type, offset and count." | ||
| 296 | (match (fold (match-lambda* | ||
| 297 | ((entry (offset . (index . data))) | ||
| 298 | (let* ((tag (header-entry-tag entry)) | ||
| 299 | (tag-number (rpm-tag-number tag)) | ||
| 300 | (tag-type (rpm-tag-type tag)) | ||
| 301 | (count (header-entry-count entry)) | ||
| 302 | (data* (header-entry->data entry)) | ||
| 303 | (alignment (entry-type->alignement tag-type)) | ||
| 304 | (aligned-offset (next-aligned-offset offset alignment)) | ||
| 305 | (padding (make-list (- aligned-offset offset) 0))) | ||
| 306 | (cons (+ aligned-offset (length data*)) | ||
| 307 | (cons (append index | ||
| 308 | (u32-number->u8-list tag-number) | ||
| 309 | (u32-number->u8-list tag-type) | ||
| 310 | (u32-number->u8-list aligned-offset) | ||
| 311 | (u32-number->u8-list count)) | ||
| 312 | (append data padding data*)))))) | ||
| 313 | '(0 . (() . ())) | ||
| 314 | entries) | ||
| 315 | ((offset . (index . data)) | ||
| 316 | (values index data)))) | ||
| 317 | |||
| 318 | ;; Prevent inlining of the variables/procedures accessed by unit tests. | ||
| 319 | (set! make-header-index+data make-header-index+data) | ||
| 320 | (set! RPMTAG_ARCH RPMTAG_ARCH) | ||
| 321 | (set! RPMTAG_LICENSE RPMTAG_LICENSE) | ||
| 322 | (set! RPMTAG_NAME RPMTAG_NAME) | ||
| 323 | (set! RPMTAG_OS RPMTAG_OS) | ||
| 324 | (set! RPMTAG_RELEASE RPMTAG_RELEASE) | ||
| 325 | (set! RPMTAG_SUMMARY RPMTAG_SUMMARY) | ||
| 326 | (set! RPMTAG_VERSION RPMTAG_VERSION) | ||
| 327 | |||
| 328 | (define (wrap-in-region-tags header region-tag) | ||
| 329 | "Wrap HEADER, a header provided as u8-list with REGION-TAG." | ||
| 330 | (let* ((type (rpm-tag-type region-tag)) | ||
| 331 | (header-intro (take header 16)) | ||
| 332 | (header-rest (drop header 16)) | ||
| 333 | ;; Increment the existing index value to account for the added region | ||
| 334 | ;; tag index. | ||
| 335 | (index-length (1+ (u8-list->u32-number | ||
| 336 | (drop-right (drop header-intro 8) 4)))) ;bytes 8-11 | ||
| 337 | ;; Increment the data length value to account for the added region | ||
| 338 | ;; tag data. | ||
| 339 | (data-length (+ REGION_TAG_COUNT | ||
| 340 | (u8-list->u32-number | ||
| 341 | (take-right header-intro 4))))) ;last 4 bytes of intro | ||
| 342 | (unless (member region-tag (list RPMTAG_HEADERSIGNATURES | ||
| 343 | RPMTAG_HEADERIMMUTABLE)) | ||
| 344 | (error "expected RPMTAG_HEADERSIGNATURES or RPMTAG_HEADERIMMUTABLE, got" | ||
| 345 | region-tag)) | ||
| 346 | (append (drop-right header-intro 8) ;strip existing index and data lengths | ||
| 347 | (u32-number->u8-list index-length) | ||
| 348 | (u32-number->u8-list data-length) | ||
| 349 | ;; Region tag (16 bytes). | ||
| 350 | (u32-number->u8-list (rpm-tag-number region-tag)) ;number | ||
| 351 | (u32-number->u8-list type) ;type | ||
| 352 | (u32-number->u8-list (- data-length REGION_TAG_COUNT)) ;offset | ||
| 353 | (u32-number->u8-list REGION_TAG_COUNT) ;count | ||
| 354 | ;; Immutable region. | ||
| 355 | header-rest | ||
| 356 | ;; Region tag trailer (16 bytes). Note: the trailer offset value | ||
| 357 | ;; is an enforced convention; it has no practical use. | ||
| 358 | (u32-number->u8-list (rpm-tag-number region-tag)) ;number | ||
| 359 | (u32-number->u8-list type) ;type | ||
| 360 | (s32-number->u8-list (* -1 index-length 16)) ;negative offset | ||
| 361 | (u32-number->u8-list REGION_TAG_COUNT)))) ;count | ||
| 362 | |||
| 363 | (define (bytevector->hex-string bv) | ||
| 364 | (format #f "~{~2,'0x~}" (bytevector->u8-list bv))) | ||
| 365 | |||
| 366 | (define (files->md5-checksums files) | ||
| 367 | "Return the MD5 checksums (formatted as hexadecimal strings) for FILES." | ||
| 368 | (let ((file-md5 (cut file-hash (hash-algorithm md5) <>))) | ||
| 369 | (map (lambda (f) | ||
| 370 | (or (and=> (false-if-exception (file-md5 f)) | ||
| 371 | bytevector->hex-string) | ||
| 372 | ;; Only regular files (e.g., not directories) can have their | ||
| 373 | ;; checksum computed. | ||
| 374 | "")) | ||
| 375 | files))) | ||
| 376 | |||
| 377 | (define (strip-leading-dot name) | ||
| 378 | "Remove the leading \".\" from NAME, if present. If a single \".\" is | ||
| 379 | encountered, translate it to \"/\"." | ||
| 380 | (match name | ||
| 381 | ("." "/") ;special case | ||
| 382 | ((? (cut string-prefix? "." <>)) | ||
| 383 | (string-drop name 1)) | ||
| 384 | (x name))) | ||
| 385 | |||
| 386 | ;;; An extensive list of required and optional FHS directories, per its 3.0 | ||
| 387 | ;;; revision. | ||
| 388 | (define %fhs-directories | ||
| 389 | (list "/bin" "/boot" "/dev" | ||
| 390 | "/etc" "/etc/opt" "/etc/X11" "/etc/sgml" "/etc/xml" | ||
| 391 | "/home" "/root" "/lib" "/media" "/mnt" | ||
| 392 | "/opt" "/opt/bin" "/opt/doc" "/opt/include" | ||
| 393 | "/opt/info" "/opt/lib" "/opt/man" | ||
| 394 | "/run" "/sbin" "/srv" "/sys" "/tmp" | ||
| 395 | "/usr" "/usr/bin" "/usr/include" "/usr/libexec" | ||
| 396 | "/usr/share/color" "/usr/share/dict" "/usr/share/doc" "/usr/share/games" | ||
| 397 | "/usr/share/info" "/usr/share/locale" "/usr/share/man" "/usr/share/misc" | ||
| 398 | "/usr/share/nls" "/usr/share/ppd" "/usr/share/sgml" | ||
| 399 | "/usr/share/terminfo" "/usr/share/tmac" "/usr/share/xml" | ||
| 400 | "/usr/share/zoneinfo" "/usr/local" "/usr/local/bin" "/usr/local/etc" | ||
| 401 | "/usr/local/games" "/usr/local/include" "/usr/local/lib" | ||
| 402 | "/usr/local/man" "/usr/local/sbin" "/usr/local/sbin" "/usr/local/share" | ||
| 403 | "/usr/local/src" "/var" "/var/account" "/var/backups" | ||
| 404 | "/var/cache" "/var/cache/fonts" "/var/cache/man" "/var/cache/www" | ||
| 405 | "/var/crash" "/var/cron" "/var/games" "/var/mail" "/var/msgs" | ||
| 406 | "/var/lib" "/var/lib/color" "/var/lib/hwclock" "/var/lib/misc" | ||
| 407 | "/var/local" "/var/lock" "/var/log" "/var/opt" "/var/preserve" | ||
| 408 | "/var/run" "/var/spool" "/var/spool/lpd" "/var/spool/mqueue" | ||
| 409 | "/var/spool/news" "/var/spool/rwho" "/var/spool/uucp" | ||
| 410 | "/var/tmp" "/var/yp")) | ||
| 411 | |||
| 412 | (define (fhs-directory? file-name) | ||
| 413 | "Predicate to check if FILE-NAME is a known File Hierarchy Standard (FHS) | ||
| 414 | directory." | ||
| 415 | (member (strip-leading-dot file-name) %fhs-directories)) | ||
| 416 | |||
| 417 | (define (directory->file-entries directory) | ||
| 418 | "Return the file lists triplet header entries for the files found under | ||
| 419 | DIRECTORY." | ||
| 420 | (with-directory-excursion directory | ||
| 421 | ;; Skip the initial "." directory, as its name would get concatenated with | ||
| 422 | ;; the "./" dirname and fail to match "." in the payload. | ||
| 423 | (let* ((files (cdr (find-files "." #:directories? #t))) | ||
| 424 | (file-stats (map lstat files)) | ||
| 425 | (directories | ||
| 426 | (append (list ".") | ||
| 427 | (filter-map (match-lambda | ||
| 428 | ((index . file) | ||
| 429 | (let ((st (list-ref file-stats index))) | ||
| 430 | (and (eq? 'directory (stat:type st)) | ||
| 431 | file)))) | ||
| 432 | (list-transduce (tenumerate) rcons files)))) | ||
| 433 | ;; Omit any FHS directories found in FILES to avoid the RPM package | ||
| 434 | ;; from owning them. This can occur when symlinks directives such | ||
| 435 | ;; as "/usr/bin/hello -> bin/hello" are used. | ||
| 436 | (package-files package-file-stats | ||
| 437 | (unzip2 (reverse | ||
| 438 | (fold (lambda (file stat res) | ||
| 439 | (if (fhs-directory? file) | ||
| 440 | res | ||
| 441 | (cons (list file stat) res))) | ||
| 442 | '() files file-stats)))) | ||
| 443 | |||
| 444 | ;; When provided with the index of a file, the directory index must | ||
| 445 | ;; return the index of the corresponding directory entry. | ||
| 446 | (dirindexes (map (lambda (d) | ||
| 447 | (list-index (cut string=? <> d) directories)) | ||
| 448 | (map dirname package-files))) | ||
| 449 | ;; The files owned are those appearing in 'basenames'; own them | ||
| 450 | ;; all. | ||
| 451 | (basenames (map basename package-files)) | ||
| 452 | ;; The directory names must end with a trailing "/". | ||
| 453 | (dirnames (map (compose strip-leading-dot (cut string-append <> "/")) | ||
| 454 | directories)) | ||
| 455 | ;; Note: All the file-related entries must have the same length as | ||
| 456 | ;; the basenames entry. | ||
| 457 | (symlink-targets (map (lambda (f) | ||
| 458 | (if (symbolic-link? f) | ||
| 459 | (readlink f) | ||
| 460 | "")) ;unused | ||
| 461 | package-files)) | ||
| 462 | (file-modes (map stat:mode package-file-stats)) | ||
| 463 | (file-sizes (map stat:size package-file-stats)) | ||
| 464 | (file-md5s (files->md5-checksums package-files))) | ||
| 465 | (let ((basenames-length (length basenames)) | ||
| 466 | (dirindexes-length (length dirindexes))) | ||
| 467 | (unless (= basenames-length dirindexes-length) | ||
| 468 | (error "length mismatch for dirIndexes; expected/actual" | ||
| 469 | basenames-length dirindexes-length)) | ||
| 470 | (append | ||
| 471 | (if (> (apply max file-sizes) INT32_MAX) | ||
| 472 | (list (make-header-entry RPMTAG_LONGFILESIZES (length file-sizes) | ||
| 473 | file-sizes) | ||
| 474 | (make-header-entry RPMTAG_LONGSIZE 1 | ||
| 475 | (reduce + 0 file-sizes))) | ||
| 476 | (list (make-header-entry RPMTAG_FILESIZES (length file-sizes) | ||
| 477 | file-sizes) | ||
| 478 | (make-header-entry RPMTAG_SIZE 1 (reduce + 0 file-sizes)))) | ||
| 479 | (list | ||
| 480 | (make-header-entry RPMTAG_FILEMODES (length file-modes) file-modes) | ||
| 481 | (make-header-entry RPMTAG_FILEDIGESTS (length file-md5s) file-md5s) | ||
| 482 | (make-header-entry RPMTAG_FILEDIGESTALGO 1 RPM_HASH_MD5) | ||
| 483 | (make-header-entry RPMTAG_FILELINKTOS (length symlink-targets) | ||
| 484 | symlink-targets) | ||
| 485 | (make-header-entry RPMTAG_FILEUSERNAME basenames-length | ||
| 486 | (make-list basenames-length "root")) | ||
| 487 | (make-header-entry RPMTAG_GROUPNAME basenames-length | ||
| 488 | (make-list basenames-length "root")) | ||
| 489 | ;; The dirindexes, basenames and dirnames tags form the so-called RPM | ||
| 490 | ;; "path triplet". | ||
| 491 | (make-header-entry RPMTAG_DIRINDEXES dirindexes-length dirindexes) | ||
| 492 | (make-header-entry RPMTAG_BASENAMES basenames-length basenames) | ||
| 493 | (make-header-entry RPMTAG_DIRNAMES (length dirnames) dirnames))))))) | ||
| 494 | |||
| 495 | (define (make-header entries) | ||
| 496 | "Return the u8 list of a RPM header containing ENTRIES, a list of | ||
| 497 | <rpm-entry> objects." | ||
| 498 | (let* ((entries (sort entries (lambda (x y) | ||
| 499 | (< (rpm-tag-number (header-entry-tag x)) | ||
| 500 | (rpm-tag-number (header-entry-tag y)))))) | ||
| 501 | (count (length entries)) | ||
| 502 | (index data (make-header-index+data entries))) | ||
| 503 | (append header-intro ;8 bytes | ||
| 504 | (u32-number->u8-list count) ;4 bytes | ||
| 505 | (u32-number->u8-list (length data)) ;4 bytes | ||
| 506 | ;; Now starts the header index, which can contain up to 32 entries | ||
| 507 | ;; of 16 bytes each. | ||
| 508 | index data))) | ||
| 509 | |||
| 510 | (define* (generate-header name version | ||
| 511 | payload-digest | ||
| 512 | payload-directory | ||
| 513 | payload-compressor | ||
| 514 | #:key | ||
| 515 | relocatable? | ||
| 516 | prein-file postin-file | ||
| 517 | preun-file postun-file | ||
| 518 | (target %host-type) | ||
| 519 | (release "0") | ||
| 520 | (license "N/A") | ||
| 521 | (summary "RPM archive generated by GNU Guix.") | ||
| 522 | (os "Linux")) ;see rpmrc.in | ||
| 523 | "Return the u8 list corresponding to the Header section. PAYLOAD-DIGEST is | ||
| 524 | the SHA256 checksum string of the compressed payload. PAYLOAD-DIRECTORY is | ||
| 525 | the directory containing the payload files. PAYLOAD-COMPRESSOR is the name of | ||
| 526 | the compressor used to compress the CPIO payload, such as \"none\", \"gz\", | ||
| 527 | \"xz\" or \"zstd\"." | ||
| 528 | (let* ((rpm-arch (gnu-machine-type->rpm-arch | ||
| 529 | (gnu-system-triplet->machine-type target))) | ||
| 530 | (file->string (cut call-with-input-file <> get-string-all)) | ||
| 531 | (prein-script (and=> prein-file file->string)) | ||
| 532 | (postin-script (and=> postin-file file->string)) | ||
| 533 | (preun-script (and=> preun-file file->string)) | ||
| 534 | (postun-script (and=> postun-file file->string))) | ||
| 535 | (wrap-in-region-tags | ||
| 536 | (make-header (append | ||
| 537 | (list (make-header-entry RPMTAG_HEADERI18NTABLE 1 (list "C")) | ||
| 538 | (make-header-entry RPMTAG_NAME 1 name) | ||
| 539 | (make-header-entry RPMTAG_VERSION 1 version) | ||
| 540 | (make-header-entry RPMTAG_RELEASE 1 release) | ||
| 541 | (make-header-entry RPMTAG_SUMMARY 1 summary) | ||
| 542 | (make-header-entry RPMTAG_LICENSE 1 license) | ||
| 543 | (make-header-entry RPMTAG_OS 1 os) | ||
| 544 | (make-header-entry RPMTAG_ARCH 1 rpm-arch)) | ||
| 545 | (directory->file-entries payload-directory) | ||
| 546 | (if relocatable? | ||
| 547 | ;; Note: RPMTAG_PREFIXES must not have a trailing | ||
| 548 | ;; slash, unless it's '/'. This allows installing the | ||
| 549 | ;; package via 'rpm -i --prefix=/tmp', for example. | ||
| 550 | (list (make-header-entry RPMTAG_PREFIXES 1 (list "/"))) | ||
| 551 | '()) | ||
| 552 | (if prein-script | ||
| 553 | (list (make-header-entry RPMTAG_PREIN 1 prein-script)) | ||
| 554 | '()) | ||
| 555 | (if postin-script | ||
| 556 | (list (make-header-entry RPMTAG_POSTIN 1 postin-script)) | ||
| 557 | '()) | ||
| 558 | (if preun-script | ||
| 559 | (list (make-header-entry RPMTAG_PREUN 1 preun-script)) | ||
| 560 | '()) | ||
| 561 | (if postun-script | ||
| 562 | (list (make-header-entry RPMTAG_POSTUN 1 postun-script)) | ||
| 563 | '()) | ||
| 564 | (if (string=? "none" payload-compressor) | ||
| 565 | '() | ||
| 566 | (list (make-header-entry RPMTAG_PAYLOADCOMPRESSOR 1 | ||
| 567 | payload-compressor))) | ||
| 568 | (list (make-header-entry RPMTAG_ENCODING 1 "utf-8") | ||
| 569 | (make-header-entry RPMTAG_PAYLOADFORMAT 1 "cpio") | ||
| 570 | (make-header-entry RPMTAG_PAYLOADDIGEST 1 | ||
| 571 | (list payload-digest)) | ||
| 572 | (make-header-entry RPMTAG_PAYLOADDIGESTALGO 1 | ||
| 573 | RPM_HASH_SHA256)))) | ||
| 574 | RPMTAG_HEADERIMMUTABLE))) | ||
| 575 | |||
| 576 | |||
| 577 | ;;; | ||
| 578 | ;;; Signature section | ||
| 579 | ;;; | ||
| 580 | |||
| 581 | ;;; Header sha256 checksum. | ||
| 582 | (define RPMSIGTAG_SHA256 (make-rpm-tag 273 STRING)) | ||
| 583 | ;;; Uncompressed payload size. | ||
| 584 | (define RPMSIGTAG_PAYLOADSIZE (make-rpm-tag 1007 INT32)) | ||
| 585 | ;;; Header and compressed payload combined size. | ||
| 586 | (define RPMSIGTAG_SIZE (make-rpm-tag 1000 INT32)) | ||
| 587 | ;;; Uncompressed payload size (when size > max u32). | ||
| 588 | (define RPMSIGTAG_LONGARCHIVESIZE (make-rpm-tag 271 INT64)) | ||
| 589 | ;;; Header and compressed payload combined size (when size > max u32). | ||
| 590 | (define RPMSIGTAG_LONGSIZE (make-rpm-tag 270 INT64)) | ||
| 591 | ;;; Extra space reserved for signatures (typically 32 bytes). | ||
| 592 | (define RPMSIGTAG_RESERVEDSPACE (make-rpm-tag 1008 BIN)) | ||
| 593 | |||
| 594 | (define (generate-signature header-sha256 | ||
| 595 | header+compressed-payload-size | ||
| 596 | ;; uncompressed-payload-size | ||
| 597 | ) | ||
| 598 | "Return the u8 list representing a signature header containing the | ||
| 599 | HEADER-SHA256 (a string) and the PAYLOAD-SIZE, which is the combined size of | ||
| 600 | the header and compressed payload." | ||
| 601 | (define size-tag (if (> header+compressed-payload-size INT32_MAX) | ||
| 602 | RPMSIGTAG_LONGSIZE | ||
| 603 | RPMSIGTAG_SIZE)) | ||
| 604 | (wrap-in-region-tags | ||
| 605 | (make-header (list (make-header-entry RPMSIGTAG_SHA256 1 header-sha256) | ||
| 606 | (make-header-entry size-tag 1 | ||
| 607 | header+compressed-payload-size) | ||
| 608 | ;; (make-header-entry RPMSIGTAG_PAYLOADSIZE 1 | ||
| 609 | ;; uncompressed-payload-size) | ||
| 610 | ;; Reserve 32 bytes of extra space in case users would | ||
| 611 | ;; like to add signatures, as done in rpmGenerateSignature. | ||
| 612 | (make-header-entry RPMSIGTAG_RESERVEDSPACE 32 | ||
| 613 | (make-list 32 0)))) | ||
| 614 | RPMTAG_HEADERSIGNATURES)) | ||
| 615 | |||
| 616 | (define (assemble-rpm-metadata lead signature header) | ||
| 617 | "Align and append the various u8 list components together, and return the | ||
| 618 | result as a bytevector." | ||
| 619 | (let* ((offset (+ (length lead) (length signature))) | ||
| 620 | (header-offset (next-aligned-offset offset 8)) | ||
| 621 | (padding (make-list (- header-offset offset) 0))) | ||
| 622 | ;; The Header is 8-bytes aligned. | ||
| 623 | (u8-list->bytevector (append lead signature padding header)))) | ||
diff --git a/guix/scripts/pack.scm b/guix/scripts/pack.scm index 77425e5b0f1..701e41ff1ab 100644 --- a/guix/scripts/pack.scm +++ b/guix/scripts/pack.scm | |||
| @@ -5,7 +5,7 @@ | |||
| 5 | ;;; Copyright © 2018 Chris Marusich <cmmarusich@gmail.com> | 5 | ;;; Copyright © 2018 Chris Marusich <cmmarusich@gmail.com> |
| 6 | ;;; Copyright © 2018 Efraim Flashner <efraim@flashner.co.il> | 6 | ;;; Copyright © 2018 Efraim Flashner <efraim@flashner.co.il> |
| 7 | ;;; Copyright © 2020 Tobias Geerinckx-Rice <me@tobias.gr> | 7 | ;;; Copyright © 2020 Tobias Geerinckx-Rice <me@tobias.gr> |
| 8 | ;;; Copyright © 2020, 2021, 2022 Maxim Cournoyer <maxim.cournoyer@gmail.com> | 8 | ;;; Copyright © 2020, 2021, 2022, 2023 Maxim Cournoyer <maxim.cournoyer@gmail.com> |
| 9 | ;;; Copyright © 2020 Eric Bavier <bavier@posteo.net> | 9 | ;;; Copyright © 2020 Eric Bavier <bavier@posteo.net> |
| 10 | ;;; Copyright © 2022 Alex Griffin <a@ajgrf.com> | 10 | ;;; Copyright © 2022 Alex Griffin <a@ajgrf.com> |
| 11 | ;;; | 11 | ;;; |
| @@ -67,6 +67,7 @@ | |||
| 67 | 67 | ||
| 68 | self-contained-tarball | 68 | self-contained-tarball |
| 69 | debian-archive | 69 | debian-archive |
| 70 | rpm-archive | ||
| 70 | docker-image | 71 | docker-image |
| 71 | squashfs-image | 72 | squashfs-image |
| 72 | 73 | ||
| @@ -856,6 +857,166 @@ Section: misc | |||
| 856 | 857 | ||
| 857 | 858 | ||
| 858 | ;;; | 859 | ;;; |
| 860 | ;;; RPM archive format. | ||
| 861 | ;;; | ||
| 862 | (define* (rpm-archive name profile | ||
| 863 | #:key target | ||
| 864 | (profile-name "guix-profile") | ||
| 865 | entry-point | ||
| 866 | (compressor (first %compressors)) | ||
| 867 | deduplicate? | ||
| 868 | localstatedir? | ||
| 869 | (symlinks '()) | ||
| 870 | archiver | ||
| 871 | (extra-options '())) | ||
| 872 | "Return a RPM archive (.rpm) containing a store initialized with the closure | ||
| 873 | of PROFILE, a derivation. The archive contains /gnu/store. SYMLINKS must be | ||
| 874 | a list of (SOURCE -> TARGET) tuples denoting symlinks to be added to the pack. | ||
| 875 | ARCHIVER and ENTRY-POINT are not used. RELOCATABLE?, PREIN-FILE, POSTIN-FILE, | ||
| 876 | PREUN-FILE and POSTUN-FILE can be provided via EXTRA-OPTIONS." | ||
| 877 | (when entry-point | ||
| 878 | (warning (G_ "entry point not supported in the '~a' format~%") 'rpm)) | ||
| 879 | |||
| 880 | (define root (populate-profile-root profile | ||
| 881 | #:profile-name profile-name | ||
| 882 | #:target target | ||
| 883 | #:localstatedir? localstatedir? | ||
| 884 | #:deduplicate? deduplicate? | ||
| 885 | #:symlinks symlinks)) | ||
| 886 | |||
| 887 | (define payload | ||
| 888 | (let* ((raw-cpio-file-name "payload.cpio") | ||
| 889 | (compressed-cpio-file-name (string-append raw-cpio-file-name | ||
| 890 | (compressor-extension | ||
| 891 | compressor)))) | ||
| 892 | (computed-file compressed-cpio-file-name | ||
| 893 | (with-imported-modules (source-module-closure | ||
| 894 | '((guix build utils) | ||
| 895 | (guix cpio) | ||
| 896 | (guix rpm))) | ||
| 897 | #~(begin | ||
| 898 | (use-modules (guix build utils) | ||
| 899 | (guix cpio) | ||
| 900 | (guix rpm) | ||
| 901 | (srfi srfi-1)) | ||
| 902 | |||
| 903 | ;; Make sure non-ASCII file names are properly handled. | ||
| 904 | #+(set-utf8-locale profile) | ||
| 905 | |||
| 906 | (define %root (if #$localstatedir? "." #$root)) | ||
| 907 | |||
| 908 | (when #$localstatedir? | ||
| 909 | ;; Fix the permission of the Guix database file, which was made | ||
| 910 | ;; read-only when copied to the store in populate-profile-root. | ||
| 911 | (copy-recursively #$root %root) | ||
| 912 | (chmod (string-append %root "/var/guix/db/db.sqlite") #o644)) | ||
| 913 | |||
| 914 | (call-with-output-file #$raw-cpio-file-name | ||
| 915 | (lambda (port) | ||
| 916 | (with-directory-excursion %root | ||
| 917 | ;; The first "." entry is discarded. | ||
| 918 | (write-cpio-archive | ||
| 919 | (remove fhs-directory? | ||
| 920 | (cdr (find-files "." #:directories? #t))) | ||
| 921 | port)))) | ||
| 922 | (when #+(compressor-command compressor) | ||
| 923 | (apply invoke (append #+(compressor-command compressor) | ||
| 924 | (list #$raw-cpio-file-name)))) | ||
| 925 | (copy-file #$compressed-cpio-file-name #$output))) | ||
| 926 | #:local-build? #f))) ;allow offloading | ||
| 927 | |||
| 928 | (define build | ||
| 929 | (with-extensions (list guile-gcrypt) | ||
| 930 | (with-imported-modules `(((guix config) => ,(make-config.scm)) | ||
| 931 | ,@(source-module-closure | ||
| 932 | `((gcrypt hash) | ||
| 933 | (guix build utils) | ||
| 934 | (guix profiles) | ||
| 935 | (guix rpm)) | ||
| 936 | #:select? not-config?)) | ||
| 937 | #~(begin | ||
| 938 | (use-modules (gcrypt hash) | ||
| 939 | (guix build utils) | ||
| 940 | (guix profiles) | ||
| 941 | (guix rpm) | ||
| 942 | (ice-9 binary-ports) | ||
| 943 | (ice-9 match) ;for manifest->friendly-name | ||
| 944 | (ice-9 optargs) | ||
| 945 | (rnrs bytevectors) | ||
| 946 | (srfi srfi-1)) | ||
| 947 | |||
| 948 | (define machine-type | ||
| 949 | (and=> (or #$target %host-type) | ||
| 950 | (lambda (triplet) | ||
| 951 | (first (string-split triplet #\-))))) | ||
| 952 | |||
| 953 | #$(procedure-source manifest->friendly-name) | ||
| 954 | |||
| 955 | (define manifest (profile-manifest #$profile)) | ||
| 956 | |||
| 957 | (define single-entry ;manifest entry | ||
| 958 | (match (manifest-entries manifest) | ||
| 959 | ((entry) | ||
| 960 | entry) | ||
| 961 | (_ #f))) | ||
| 962 | |||
| 963 | (define name | ||
| 964 | (or (and=> single-entry manifest-entry-name) | ||
| 965 | (manifest->friendly-name manifest))) | ||
| 966 | |||
| 967 | (define version | ||
| 968 | (or (and=> single-entry manifest-entry-version) "0.0.0")) | ||
| 969 | |||
| 970 | (define lead | ||
| 971 | (generate-lead (string-append name "-" version) | ||
| 972 | #:target (or #$target %host-type))) | ||
| 973 | |||
| 974 | (define payload-digest | ||
| 975 | (bytevector->hex-string (file-sha256 #$payload))) | ||
| 976 | |||
| 977 | (let-keywords '#$extra-options #f ((relocatable? #f) | ||
| 978 | (prein-file #f) | ||
| 979 | (postin-file #f) | ||
| 980 | (preun-file #f) | ||
| 981 | (postun-file #f)) | ||
| 982 | |||
| 983 | (let ((header (generate-header name version | ||
| 984 | payload-digest | ||
| 985 | #$root | ||
| 986 | #$(compressor-name compressor) | ||
| 987 | #:target (or #$target %host-type) | ||
| 988 | #:relocatable? relocatable? | ||
| 989 | #:prein-file prein-file | ||
| 990 | #:postin-file postin-file | ||
| 991 | #:preun-file preun-file | ||
| 992 | #:postun-file postun-file))) | ||
| 993 | |||
| 994 | (define header-sha256 | ||
| 995 | (bytevector->hex-string (sha256 (u8-list->bytevector header)))) | ||
| 996 | |||
| 997 | (define payload-size (stat:size (stat #$payload))) | ||
| 998 | |||
| 999 | (define header+compressed-payload-size | ||
| 1000 | (+ (length header) payload-size)) | ||
| 1001 | |||
| 1002 | (define signature | ||
| 1003 | (generate-signature header-sha256 | ||
| 1004 | header+compressed-payload-size)) | ||
| 1005 | |||
| 1006 | ;; Serialize the archive components to a file. | ||
| 1007 | (call-with-input-file #$payload | ||
| 1008 | (lambda (in) | ||
| 1009 | (call-with-output-file #$output | ||
| 1010 | (lambda (out) | ||
| 1011 | (put-bytevector out (assemble-rpm-metadata lead | ||
| 1012 | signature | ||
| 1013 | header)) | ||
| 1014 | (sendfile out in payload-size))))))))))) | ||
| 1015 | |||
| 1016 | (gexp->derivation (string-append name ".rpm") build)) | ||
| 1017 | |||
| 1018 | |||
| 1019 | ;;; | ||
| 859 | ;;; Compiling C programs. | 1020 | ;;; Compiling C programs. |
| 860 | ;;; | 1021 | ;;; |
| 861 | 1022 | ||
| @@ -1187,7 +1348,8 @@ last resort for relocation." | |||
| 1187 | `((tarball . ,self-contained-tarball) | 1348 | `((tarball . ,self-contained-tarball) |
| 1188 | (squashfs . ,squashfs-image) | 1349 | (squashfs . ,squashfs-image) |
| 1189 | (docker . ,docker-image) | 1350 | (docker . ,docker-image) |
| 1190 | (deb . ,debian-archive))) | 1351 | (deb . ,debian-archive) |
| 1352 | (rpm . ,rpm-archive))) | ||
| 1191 | 1353 | ||
| 1192 | (define (show-formats) | 1354 | (define (show-formats) |
| 1193 | ;; Print the supported pack formats. | 1355 | ;; Print the supported pack formats. |
| @@ -1201,18 +1363,22 @@ last resort for relocation." | |||
| 1201 | docker Tarball ready for 'docker load'")) | 1363 | docker Tarball ready for 'docker load'")) |
| 1202 | (display (G_ " | 1364 | (display (G_ " |
| 1203 | deb Debian archive installable via dpkg/apt")) | 1365 | deb Debian archive installable via dpkg/apt")) |
| 1366 | (display (G_ " | ||
| 1367 | rpm RPM archive installable via rpm/yum")) | ||
| 1204 | (newline)) | 1368 | (newline)) |
| 1205 | 1369 | ||
| 1370 | (define (required-option symbol) | ||
| 1371 | "Return an SYMBOL option that requires a value." | ||
| 1372 | (option (list (symbol->string symbol)) #t #f | ||
| 1373 | (lambda (opt name arg result . rest) | ||
| 1374 | (apply values | ||
| 1375 | (alist-cons symbol arg result) | ||
| 1376 | rest)))) | ||
| 1377 | |||
| 1206 | (define %deb-format-options | 1378 | (define %deb-format-options |
| 1207 | (let ((required-option (lambda (symbol) | 1379 | (list (required-option 'control-file) |
| 1208 | (option (list (symbol->string symbol)) #t #f | 1380 | (required-option 'postinst-file) |
| 1209 | (lambda (opt name arg result . rest) | 1381 | (required-option 'triggers-file))) |
| 1210 | (apply values | ||
| 1211 | (alist-cons symbol arg result) | ||
| 1212 | rest)))))) | ||
| 1213 | (list (required-option 'control-file) | ||
| 1214 | (required-option 'postinst-file) | ||
| 1215 | (required-option 'triggers-file)))) | ||
| 1216 | 1382 | ||
| 1217 | (define (show-deb-format-options) | 1383 | (define (show-deb-format-options) |
| 1218 | (display (G_ " | 1384 | (display (G_ " |
| @@ -1231,6 +1397,32 @@ last resort for relocation." | |||
| 1231 | (newline) | 1397 | (newline) |
| 1232 | (exit 0)) | 1398 | (exit 0)) |
| 1233 | 1399 | ||
| 1400 | (define %rpm-format-options | ||
| 1401 | (list (required-option 'prein-file) | ||
| 1402 | (required-option 'postin-file) | ||
| 1403 | (required-option 'preun-file) | ||
| 1404 | (required-option 'postun-file))) | ||
| 1405 | |||
| 1406 | (define (show-rpm-format-options) | ||
| 1407 | (display (G_ " | ||
| 1408 | --help-rpm-format list options specific to the RPM format"))) | ||
| 1409 | |||
| 1410 | (define (show-rpm-format-options/detailed) | ||
| 1411 | (display (G_ " | ||
| 1412 | --prein-file=FILE | ||
| 1413 | Embed the provided prein script")) | ||
| 1414 | (display (G_ " | ||
| 1415 | --postin-file=FILE | ||
| 1416 | Embed the provided postin script")) | ||
| 1417 | (display (G_ " | ||
| 1418 | --preun-file=FILE | ||
| 1419 | Embed the provided preun script")) | ||
| 1420 | (display (G_ " | ||
| 1421 | --postun-file=FILE | ||
| 1422 | Embed the provided postun script")) | ||
| 1423 | (newline) | ||
| 1424 | (exit 0)) | ||
| 1425 | |||
| 1234 | (define %options | 1426 | (define %options |
| 1235 | ;; Specifications of the command-line options. | 1427 | ;; Specifications of the command-line options. |
| 1236 | (cons* (option '(#\h "help") #f #f | 1428 | (cons* (option '(#\h "help") #f #f |
| @@ -1307,7 +1499,12 @@ last resort for relocation." | |||
| 1307 | (lambda args | 1499 | (lambda args |
| 1308 | (show-deb-format-options/detailed))) | 1500 | (show-deb-format-options/detailed))) |
| 1309 | 1501 | ||
| 1502 | (option '("help-rpm-format") #f #f | ||
| 1503 | (lambda args | ||
| 1504 | (show-rpm-format-options/detailed))) | ||
| 1505 | |||
| 1310 | (append %deb-format-options | 1506 | (append %deb-format-options |
| 1507 | %rpm-format-options | ||
| 1311 | %transformation-options | 1508 | %transformation-options |
| 1312 | %standard-build-options | 1509 | %standard-build-options |
| 1313 | %standard-cross-build-options | 1510 | %standard-cross-build-options |
| @@ -1325,6 +1522,7 @@ Create a bundle of PACKAGE.\n")) | |||
| 1325 | (show-transformation-options-help) | 1522 | (show-transformation-options-help) |
| 1326 | (newline) | 1523 | (newline) |
| 1327 | (show-deb-format-options) | 1524 | (show-deb-format-options) |
| 1525 | (show-rpm-format-options) | ||
| 1328 | (newline) | 1526 | (newline) |
| 1329 | (display (G_ " | 1527 | (display (G_ " |
| 1330 | -f, --format=FORMAT build a pack in the given FORMAT")) | 1528 | -f, --format=FORMAT build a pack in the given FORMAT")) |
| @@ -1483,6 +1681,16 @@ Create a bundle of PACKAGE.\n")) | |||
| 1483 | (process-file-arg opts 'postinst-file) | 1681 | (process-file-arg opts 'postinst-file) |
| 1484 | #:triggers-file | 1682 | #:triggers-file |
| 1485 | (process-file-arg opts 'triggers-file))) | 1683 | (process-file-arg opts 'triggers-file))) |
| 1684 | ('rpm | ||
| 1685 | (list #:relocatable? relocatable? | ||
| 1686 | #:prein-file | ||
| 1687 | (process-file-arg opts 'prein-file) | ||
| 1688 | #:postin-file | ||
| 1689 | (process-file-arg opts 'postin-file) | ||
| 1690 | #:preun-file | ||
| 1691 | (process-file-arg opts 'preun-file) | ||
| 1692 | #:postun-file | ||
| 1693 | (process-file-arg opts 'postun-file))) | ||
| 1486 | (_ '()))) | 1694 | (_ '()))) |
| 1487 | (target (assoc-ref opts 'target)) | 1695 | (target (assoc-ref opts 'target)) |
| 1488 | (bootstrap? (assoc-ref opts 'bootstrap?)) | 1696 | (bootstrap? (assoc-ref opts 'bootstrap?)) |
diff --git a/tests/pack.scm b/tests/pack.scm index a02924b7d20..734ae1c69b6 100644 --- a/tests/pack.scm +++ b/tests/pack.scm | |||
| @@ -1,7 +1,7 @@ | |||
| 1 | ;;; GNU Guix --- Functional package management for GNU | 1 | ;;; GNU Guix --- Functional package management for GNU |
| 2 | ;;; Copyright © 2017, 2018, 2019, 2020, 2021 Ludovic Courtès <ludo@gnu.org> | 2 | ;;; Copyright © 2017, 2018, 2019, 2020, 2021 Ludovic Courtès <ludo@gnu.org> |
| 3 | ;;; Copyright © 2018 Ricardo Wurmus <rekado@elephly.net> | 3 | ;;; Copyright © 2018 Ricardo Wurmus <rekado@elephly.net> |
| 4 | ;;; Copyright © 2021 Maxim Cournoyer <maxim.cournoyer@gmail.com> | 4 | ;;; Copyright © 2021, 2023 Maxim Cournoyer <maxim.cournoyer@gmail.com> |
| 5 | ;;; | 5 | ;;; |
| 6 | ;;; This file is part of GNU Guix. | 6 | ;;; This file is part of GNU Guix. |
| 7 | ;;; | 7 | ;;; |
| @@ -28,13 +28,16 @@ | |||
| 28 | #:use-module (guix tests) | 28 | #:use-module (guix tests) |
| 29 | #:use-module (guix gexp) | 29 | #:use-module (guix gexp) |
| 30 | #:use-module (guix modules) | 30 | #:use-module (guix modules) |
| 31 | #:use-module (guix utils) | ||
| 31 | #:use-module (gnu packages) | 32 | #:use-module (gnu packages) |
| 32 | #:use-module ((gnu packages base) #:select (glibc-utf8-locales)) | 33 | #:use-module ((gnu packages base) #:select (glibc-utf8-locales)) |
| 33 | #:use-module (gnu packages bootstrap) | 34 | #:use-module (gnu packages bootstrap) |
| 35 | #:use-module ((gnu packages package-management) #:select (rpm)) | ||
| 34 | #:use-module ((gnu packages compression) #:select (squashfs-tools)) | 36 | #:use-module ((gnu packages compression) #:select (squashfs-tools)) |
| 35 | #:use-module ((gnu packages debian) #:select (dpkg)) | 37 | #:use-module ((gnu packages debian) #:select (dpkg)) |
| 36 | #:use-module ((gnu packages guile) #:select (guile-sqlite3)) | 38 | #:use-module ((gnu packages guile) #:select (guile-sqlite3)) |
| 37 | #:use-module ((gnu packages gnupg) #:select (guile-gcrypt)) | 39 | #:use-module ((gnu packages gnupg) #:select (guile-gcrypt)) |
| 40 | #:use-module ((gnu packages linux) #:select (fakeroot)) | ||
| 38 | #:use-module (srfi srfi-64)) | 41 | #:use-module (srfi srfi-64)) |
| 39 | 42 | ||
| 40 | (define %store | 43 | (define %store |
| @@ -59,6 +62,17 @@ | |||
| 59 | 62 | ||
| 60 | (define %ar-bootstrap %bootstrap-binutils) | 63 | (define %ar-bootstrap %bootstrap-binutils) |
| 61 | 64 | ||
| 65 | ;;; This is a variant of the RPM package configured so that its database can | ||
| 66 | ;;; be created on a writable location readily available inside the build | ||
| 67 | ;;; container ("/tmp"). | ||
| 68 | (define rpm-for-tests | ||
| 69 | (package | ||
| 70 | (inherit rpm) | ||
| 71 | (arguments (substitute-keyword-arguments (package-arguments rpm) | ||
| 72 | ((#:configure-flags flags '()) | ||
| 73 | #~(cons "--localstatedir=/tmp" | ||
| 74 | (delete "--localstatedir=/var" #$flags))))))) | ||
| 75 | |||
| 62 | 76 | ||
| 63 | (test-begin "pack") | 77 | (test-begin "pack") |
| 64 | 78 | ||
| @@ -356,6 +370,47 @@ | |||
| 356 | (assert (file-exists? "triggers")) | 370 | (assert (file-exists? "triggers")) |
| 357 | 371 | ||
| 358 | (mkdir #$output)))))) | 372 | (mkdir #$output)))))) |
| 373 | (built-derivations (list check)))) | ||
| 374 | |||
| 375 | (unless store (test-skip 1)) | ||
| 376 | (test-assertm "rpm archive can be installed/uninstalled" store | ||
| 377 | (mlet* %store-monad | ||
| 378 | ((guile (set-guile-for-build (default-guile))) | ||
| 379 | (profile (profile-derivation (packages->manifest | ||
| 380 | (list %bootstrap-guile)) | ||
| 381 | #:hooks '() | ||
| 382 | #:locales? #f)) | ||
| 383 | (rpm-pack (rpm-archive "rpm-pack" profile | ||
| 384 | #:compressor %gzip-compressor | ||
| 385 | #:symlinks '(("/bin/guile" -> "bin/guile")) | ||
| 386 | #:extra-options '(#:relocatable? #t))) | ||
| 387 | (check | ||
| 388 | (gexp->derivation "check-rpm-pack" | ||
| 389 | (with-imported-modules (source-module-closure | ||
| 390 | '((guix build utils))) | ||
| 391 | #~(begin | ||
| 392 | (use-modules (guix build utils)) | ||
| 393 | |||
| 394 | (define fakeroot #+(file-append fakeroot "/bin/fakeroot")) | ||
| 395 | (define rpm #+(file-append rpm-for-tests "/bin/rpm")) | ||
| 396 | (mkdir-p "/tmp/lib/rpm") | ||
| 397 | |||
| 398 | ;; Install the RPM package. This causes RPM to validate the | ||
| 399 | ;; signatures, header as well as the file digests, which | ||
| 400 | ;; makes it a rather thorough test. | ||
| 401 | (mkdir "test-prefix") | ||
| 402 | (invoke fakeroot rpm "--install" | ||
| 403 | (string-append "--prefix=" (getcwd) "/test-prefix") | ||
| 404 | #$rpm-pack) | ||
| 405 | |||
| 406 | ;; Invoke the installed Guile command. | ||
| 407 | (invoke "./test-prefix/bin/guile" "--version") | ||
| 408 | |||
| 409 | ;; Uninstall the RPM package. | ||
| 410 | (invoke fakeroot rpm "--erase" "guile-bootstrap") | ||
| 411 | |||
| 412 | ;; Required so the above is run. | ||
| 413 | (mkdir #$output)))))) | ||
| 359 | (built-derivations (list check))))) | 414 | (built-derivations (list check))))) |
| 360 | 415 | ||
| 361 | (test-end) | 416 | (test-end) |
diff --git a/tests/rpm.scm b/tests/rpm.scm new file mode 100644 index 00000000000..f40b36fe603 --- /dev/null +++ b/tests/rpm.scm | |||
| @@ -0,0 +1,86 @@ | |||
| 1 | ;;; GNU Guix --- Functional package management for GNU | ||
| 2 | ;;; Copyright © 2023 Maxim Cournoyer <maxim.cournoyer@gmail.com> | ||
| 3 | ;;; | ||
| 4 | ;;; This file is part of GNU Guix. | ||
| 5 | ;;; | ||
| 6 | ;;; GNU Guix is free software; you can redistribute it and/or modify it | ||
| 7 | ;;; under the terms of the GNU General Public License as published by | ||
| 8 | ;;; the Free Software Foundation; either version 3 of the License, or (at | ||
| 9 | ;;; your option) any later version. | ||
| 10 | ;;; | ||
| 11 | ;;; GNU Guix is distributed in the hope that it will be useful, but | ||
| 12 | ;;; WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 13 | ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 14 | ;;; GNU General Public License for more details. | ||
| 15 | ;;; | ||
| 16 | ;;; You should have received a copy of the GNU General Public License | ||
| 17 | ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. | ||
| 18 | |||
| 19 | (define-module (test-rpm) | ||
| 20 | #:use-module (guix rpm) | ||
| 21 | #:use-module (rnrs bytevectors) | ||
| 22 | #:use-module (srfi srfi-1) | ||
| 23 | #:use-module (srfi srfi-26) | ||
| 24 | #:use-module (srfi srfi-64) | ||
| 25 | #:use-module (srfi srfi-71)) | ||
| 26 | |||
| 27 | ;; For white-box testing. | ||
| 28 | (define-syntax-rule (expose-internal name) | ||
| 29 | (define name (@@ (guix rpm) name))) | ||
| 30 | |||
| 31 | (expose-internal RPMTAG_ARCH) | ||
| 32 | (expose-internal RPMTAG_LICENSE) | ||
| 33 | (expose-internal RPMTAG_NAME) | ||
| 34 | (expose-internal RPMTAG_OS) | ||
| 35 | (expose-internal RPMTAG_RELEASE) | ||
| 36 | (expose-internal RPMTAG_SUMMARY) | ||
| 37 | (expose-internal RPMTAG_VERSION) | ||
| 38 | (expose-internal header-entry-count) | ||
| 39 | (expose-internal header-entry-tag) | ||
| 40 | (expose-internal header-entry-value) | ||
| 41 | (expose-internal header-entry?) | ||
| 42 | (expose-internal make-header) | ||
| 43 | (expose-internal make-header-entry) | ||
| 44 | (expose-internal make-header-index+data) | ||
| 45 | |||
| 46 | (test-begin "rpm") | ||
| 47 | |||
| 48 | (test-equal "lead must be 96 bytes long" | ||
| 49 | 96 | ||
| 50 | (length (generate-lead "hello-2.12.1"))) | ||
| 51 | |||
| 52 | (define header-entries | ||
| 53 | (list (make-header-entry RPMTAG_NAME 1 "hello") | ||
| 54 | (make-header-entry RPMTAG_VERSION 1 "2.12.1") | ||
| 55 | (make-header-entry RPMTAG_RELEASE 1 "0") | ||
| 56 | (make-header-entry RPMTAG_SUMMARY 1 | ||
| 57 | "Hello, GNU world: An example GNU package") | ||
| 58 | (make-header-entry RPMTAG_LICENSE 1 "GPL 3 or later") | ||
| 59 | (make-header-entry RPMTAG_OS 1 "Linux") | ||
| 60 | (make-header-entry RPMTAG_ARCH 1 "x86_64"))) | ||
| 61 | |||
| 62 | (define expected-header-index-length | ||
| 63 | (* 16 (length header-entries))) ;16 bytes per index entry | ||
| 64 | |||
| 65 | (define expected-header-data-length | ||
| 66 | (+ (length header-entries) ;to account for null bytes | ||
| 67 | (fold + 0 (map (compose string-length (cut header-entry-value <>)) | ||
| 68 | header-entries)))) | ||
| 69 | |||
| 70 | (let ((index data (make-header-index+data header-entries))) | ||
| 71 | (test-equal "header index" | ||
| 72 | expected-header-index-length | ||
| 73 | (length index)) | ||
| 74 | |||
| 75 | ;; This test depends on the fact that only STRING entries are used, and that | ||
| 76 | ;; they are composed of single byte characters and the delimiting null byte. | ||
| 77 | (test-equal "header data" | ||
| 78 | expected-header-data-length | ||
| 79 | (length data))) | ||
| 80 | |||
| 81 | (test-equal "complete header section" | ||
| 82 | (+ 16 ;leading magic + count bytes | ||
| 83 | expected-header-index-length expected-header-data-length) | ||
| 84 | (length (make-header header-entries))) | ||
| 85 | |||
| 86 | (test-end) | ||
