From 94983652cff6a952e81eca18d3a4e0e05dec59e7 Mon Sep 17 00:00:00 2001 From: David Walluck Date: Mon, 21 Apr 2025 13:34:22 -0400 Subject: [PATCH] GH-31: Allow setting payload coding and flags in configuration Closes #31. --- pom.xml | 38 ++------ src/it/test17-reproducible-date/verify.groovy | 2 +- .../test17-reproducible-epoch/verify.groovy | 2 +- src/it/test20-payload/pom.xml | 89 +++++++++++++++++++ src/it/test20-payload/verify.groovy | 16 ++++ .../de/dentrassi/rpm/builder/RpmMojo.java | 41 ++++++++- .../dentrassi/rpm/builder/RpmUnpackMojo.java | 37 +++----- src/site/markdown/payload_compression.md | 84 +++++++++++++++++ src/site/site.xml | 2 + 9 files changed, 250 insertions(+), 61 deletions(-) create mode 100644 src/it/test20-payload/pom.xml create mode 100644 src/it/test20-payload/verify.groovy create mode 100644 src/site/markdown/payload_compression.md diff --git a/pom.xml b/pom.xml index decbc51..3b16ccd 100644 --- a/pom.xml +++ b/pom.xml @@ -39,12 +39,12 @@ UTF-8 UTF-8 - 3.8.3 + 3.8.8 1.77 1.78 - 0.21.0 + 0.21.1-SNAPSHOT @@ -113,14 +113,9 @@ - org.bouncycastle - bcpg-jdk18on - ${org.bouncycastle.bcpg.version} - - - org.bouncycastle - bcprov-jdk18on - ${org.bouncycastle.bcprov.version} + commons-io + commons-io + 2.19.0 @@ -130,12 +125,6 @@ provided - - commons-codec - commons-codec - 1.15 - - @@ -149,26 +138,11 @@ - - com.google.guava - guava - 32.1.2-jre - - - org.apache.commons - commons-compress - 1.26.0 - org.codehaus.plexus plexus-archiver 4.9.1 - - commons-io - commons-io - 2.15.1 - @@ -532,7 +506,7 @@ java-10 - [1.10, + [1.10,) 8 diff --git a/src/it/test17-reproducible-date/verify.groovy b/src/it/test17-reproducible-date/verify.groovy index b696d06..6cd221e 100644 --- a/src/it/test17-reproducible-date/verify.groovy +++ b/src/it/test17-reproducible-date/verify.groovy @@ -16,7 +16,7 @@ def verify() { def result = verify() println "Verify: " + result -def expectedMd5Sum = "cd831b22be9f30db30eb69ab35ddb3c5"; +def expectedMd5Sum = "d5295483d055f6fe9d19b2c36490a997"; def md5sum = generateMD5(new File(basedir, "target/test17-1.0.0-0.200901011100.noarch.rpm")) if (md5sum != expectedMd5Sum) { System.out.format("RPM MD5 doesn't match - actual: %s, expected: %s%n", md5sum, expectedMd5Sum); diff --git a/src/it/test17-reproducible-epoch/verify.groovy b/src/it/test17-reproducible-epoch/verify.groovy index facd9af..32ff0cc 100644 --- a/src/it/test17-reproducible-epoch/verify.groovy +++ b/src/it/test17-reproducible-epoch/verify.groovy @@ -16,7 +16,7 @@ def verify() { def result = verify() println "Verify: " + result -def expectedMd5Sum = "6fc07068db044f8f73a8e6ecb01256ce"; +def expectedMd5Sum = "d5e4e3cfd001f5d7a71ac6ab10744be8"; def md5sum = generateMD5(new File(basedir, "target/test17-1.0.0-0.197001010000.noarch.rpm")) if (md5sum != expectedMd5Sum) { System.out.format("RPM MD5 doesn't match - actual: %s, expected: %s%n", md5sum, expectedMd5Sum); diff --git a/src/it/test20-payload/pom.xml b/src/it/test20-payload/pom.xml new file mode 100644 index 0000000..b82f348 --- /dev/null +++ b/src/it/test20-payload/pom.xml @@ -0,0 +1,89 @@ + + + 4.0.0 + + de.dentrassi.maven.rpm.test + test20 + 1.0.0-SNAPSHOT + jar + + Test Package #20 + + Test explicitly setting payload coding and payload flags + + + http://dentrassi.de + + + Jens Reimann + http://dentrassi.de + + + + + Eclipse Public License - v 1.0 + repo + https://www.eclipse.org/legal/epl-v10.html + + + + + UTF-8 + UTF-8 + true + + 2025-04-16T16:25:26.470Z + + + + + + + de.dentrassi.maven + rpm + @project.version@ + + + + rpm + + + test20.rpm + false + Application/Misc + + + ${keyId} + ${user.home}/.gnupg/secring.gpg + ${passphrase} + SHA1 + ${skipSigning} + + + + Zstd + 19 + 0 + 23 + + + + + + + + + + + sign + + false + + + false + + + + + diff --git a/src/it/test20-payload/verify.groovy b/src/it/test20-payload/verify.groovy new file mode 100644 index 0000000..13d0e35 --- /dev/null +++ b/src/it/test20-payload/verify.groovy @@ -0,0 +1,16 @@ +def verifyPayload() { + Process proc = ['rpm', '-q', '--queryformat', '%{PAYLOADCOMPRESSOR} %{PAYLOADFLAGS}\n', basedir.toString().replace(File.separator, "/") + '/target/test20.rpm'].execute() + proc.waitFor() + return proc.in.getText().trim() +} + +def actual = verifyPayload() +println "Verify payload:\n" + actual +def expected = "zstd 19T0L23" + +if (actual != expected) { + System.out.format("RPM payloads don't match - actual:%n%s%nexpected:%n%s%n", actual, expected); + return false; +} + +return true; diff --git a/src/main/java/de/dentrassi/rpm/builder/RpmMojo.java b/src/main/java/de/dentrassi/rpm/builder/RpmMojo.java index 6ec0dff..f18fc1b 100644 --- a/src/main/java/de/dentrassi/rpm/builder/RpmMojo.java +++ b/src/main/java/de/dentrassi/rpm/builder/RpmMojo.java @@ -66,6 +66,8 @@ import org.eclipse.packager.rpm.build.*; import org.eclipse.packager.rpm.build.RpmBuilder.PackageInformation; import org.eclipse.packager.rpm.build.RpmBuilder.Version; +import org.eclipse.packager.rpm.coding.PayloadCoding; +import org.eclipse.packager.rpm.coding.PayloadFlags; import org.eclipse.packager.rpm.deps.RpmDependencyFlags; import org.eclipse.packager.rpm.signature.RsaHeaderSignatureProcessor; import org.eclipse.packager.rpm.signature.RsaSignatureProcessor; @@ -85,6 +87,10 @@ */ @Mojo(name = "rpm", defaultPhase = LifecyclePhase.PACKAGE, requiresProject = true, threadSafe = true) public class RpmMojo extends AbstractMojo { + private static final PayloadCoding DEFAULT_PAYLOAD_CODING = PayloadCoding.gzip; + + private static final String DEFAULT_PAYLOAD_FLAGS = "9"; + private static final String SNAPSHOT_SUFFIX = "-SNAPSHOT"; /** @@ -601,6 +607,19 @@ public void setGenerateDefaultSourcePackage(final boolean generateDefaultSourceP @Parameter(property = "rpm.signature") Signature signature; + /** + * Optional payload flags to use for compressing the payload of the final RPM. + *

+ * The coding must be one of the names returned by {@link PayloadCoding#values()}. + * The default coding is {@link PayloadCoding#gzip}. + * The level must be a number. + * The default level is {@code 9}. + *

+ * Also see Payload compression + */ + @Parameter(property = "rpm.payloadFlags") + PayloadFlags payloadFlags; + /** * Disable the mojo altogether. * @@ -842,10 +861,30 @@ public void execute() throws MojoExecutionException, MojoFailureException { testLeadFlags(); final BuilderOptions options = new BuilderOptions(); - DigestAlgorithm fileDigestAlgorithm = evalDigestAlgorithm(this.fileDigestAlgorithm); + final DigestAlgorithm fileDigestAlgorithm = evalDigestAlgorithm(this.fileDigestAlgorithm); this.logger.info("File Digest Algorithm: %s", fileDigestAlgorithm.getAlgorithm()); options.setFileDigestAlgorithm(fileDigestAlgorithm); + final PayloadCoding payloadCoding; + if (this.payloadFlags.getCoding() != null) { + payloadCoding = PayloadCoding.valueOf(this.payloadFlags.getCoding()); + this.logger.info("Payload Coding: %s", payloadCoding); + } else { + payloadCoding = DEFAULT_PAYLOAD_CODING; + this.logger.info("Using default Payload Coding: %s", payloadCoding); + } + + options.setPayloadCoding(payloadCoding); + + if (this.payloadFlags != null && !this.payloadFlags.toString().isEmpty()) { + this.logger.info("Payload Flags: %s", this.payloadFlags); + } else { + this.payloadFlags = new PayloadFlags(payloadCoding, DEFAULT_PAYLOAD_FLAGS); + this.logger.info("Using default Payload Flags: %s", this.payloadFlags); + } + + options.setPayloadFlags(this.payloadFlags); + // setup basic signature processors final SignatureConfiguration provider; diff --git a/src/main/java/de/dentrassi/rpm/builder/RpmUnpackMojo.java b/src/main/java/de/dentrassi/rpm/builder/RpmUnpackMojo.java index 009a775..6a1bdf0 100644 --- a/src/main/java/de/dentrassi/rpm/builder/RpmUnpackMojo.java +++ b/src/main/java/de/dentrassi/rpm/builder/RpmUnpackMojo.java @@ -28,6 +28,7 @@ import java.nio.file.attribute.UserPrincipalLookupService; import java.nio.file.attribute.UserPrincipalNotFoundException; import java.util.EnumSet; +import java.util.List; import java.util.Set; import java.util.regex.Pattern; @@ -383,41 +384,25 @@ private void setFileOwnership(final InputHeader payloadHeader, } private static String getName(final InputHeader payloadHeader, final RpmTag tag, final long id) { - final Object values = - payloadHeader.getEntry(tag) - .orElseThrow(() -> new IllegalStateException("RPM lacks " + tag + " lookup table")) - .getValue(); - - if (!(values instanceof String[])) { - throw new IllegalStateException("RPM " + tag + " header is not a list of Strings, got " + - values.getClass()); - } + final List names = payloadHeader.getStringList(tag); + final int size = names.size(); - final String[] names = (String[]) values; - if (id < 0 || names.length <= id) { - throw new IllegalArgumentException("id out of range [0," + names.length + ']'); + if (id < 0 || size <= id) { + throw new IllegalArgumentException("id out of range [0," + size + ']'); } - return names[(int) id]; + return names.get((int) id); } private static String getLinkTarget(final InputHeader payloadHeader, final long inode) { - final Object values = - payloadHeader.getEntry(RpmTag.FILE_LINKTO) - .orElseThrow(() -> - new IllegalStateException("RPM contains symbolic link, but lacks linkTo header")) - .getValue(); - - if (!(values instanceof String[])) { - throw new IllegalStateException("RPM linkTo header is not a list of Strings, got " + values.getClass()); - } + final List linkTo = payloadHeader.getStringList(RpmTag.FILE_LINKTO); + final int size = linkTo.size(); - final String[] linkTo = (String[]) values; - if (inode < 0 || linkTo.length <= inode) { - throw new IllegalArgumentException("Symbolic link inode out of range [0," + linkTo.length + ']'); + if (inode < 0 || size <= inode) { + throw new IllegalArgumentException("Symbolic link inode out of range [0," + size + ']'); } - return linkTo[(int) inode]; + return linkTo.get((int) inode); } public void setRpmFile(final File rpmFile) { diff --git a/src/site/markdown/payload_compression.md b/src/site/markdown/payload_compression.md new file mode 100644 index 0000000..84d6e0d --- /dev/null +++ b/src/site/markdown/payload_compression.md @@ -0,0 +1,84 @@ +# Payload compression + +RPM packages can be compressed using one of several methods. All of these methods allow setting a custom +compression level as well as additional options. + +## Configuration + +Payload compression is configured using the `` element. For example: + +```xml + + + zstd + 19 + + +``` + +### Payload coding + +Payload `coding` must be one of the following compressors: + +* none (uncompressed) +* gzip (default compressor, with a compression level of 9) +* bzip2 +* lzma +* xz +* zstd + +### Payload flags + +| coding | level (number) | threads (number) | strategy (number) | windowLog (number) | smallMode (boolean) | +|--------|----------------|------------------|------------------------------------------------|--------------------|---------------------| +| none | ✗ | ✗ | ✗ | ✗ | ✗ | +| gzip | ✓ 0-9 | ✗ | ✓ 0 (default), 1 (filtered), 2 (huffman) | ✗ | ✗ | +| bzip2 | ✓ 0-9 | ✗ | ✗ | ✗ | ✓ false, true | +| lzma | ✓ 0-9 | ✓ | ✗ | ✗ | ✗ | +| xz | ✓ 0-9 | ✓ | ✗ | ✗ | ✗ | +| zstd | ✓ 0-22 | ✓ | ✗ | ✓ | ✗ | + +#### level + +The compressors `gzip`, `lzma`, `bzip2`,and `xz` support a range of compression `level`s between 0 (no compression) and +9\. Additionally, `gzip` supports the value -1 for the default compression level. The compressor `zstd` supports a range +of compression levels between 0 (use default compression level) and 22. + +#### threads + +The compressors `lzma`, `xz`, and `zstd` support a number of `threads`. A value of 0 means that the compressor will use +`Runtime.getRuntime().availableProcessors()` threads. The compressors `gzip` and `bzip2` do not support this option. +The Java implementations of `lzma` and `xz` currently do not support multithreaded operation, so this value has no +effect. + +#### strategy + +The compressor `gzip` supports a `strategy` value. The default value is 0, which means that the compressor will +use the default strategy. A value of 1 means that the compressor will use a filtered strategy. A value of 2 means that +the compressor will use a huffman-only strategy. + +#### windowLog + +The compressor `zstd` supports a numeric `windowLog` value. The special value 0 will cause zstd to use the default +`windowLog` value. + +#### smallMode + +The compressor `bzip2` supports a `smallMode` boolean value. The Java implementation of `bzip2` does not support this, +so this value has no effect. + +### Payload flags strings + + The payload flags are converted to a string representation in the RPM header. + +| payload flags | description | valid for codings | +|---------------|--------------------------------------------------------------------|-----------------------------| +| "9" | level 9 | gzip, bzip2, lzma, xz, zstd | +| "9h" | level 9 huffman-only strategy | gzip | +| "9f" | level 9 filtered strategy | gzip | +| "9s" | level 9 small mode | bzip2 | +| "7T16" | level 7 using 16 threads | lzma, xz, zstd | +| "7T0" | level 7 using `Runtime.getRuntime().availableProcessors()` threads | lzma, xz, zstd | +| "7T" | level 7 using `Runtime.getRuntime().availableProcessors()` threads | lzma, xz, zstd | +| "7L" | level 7 using the default window log value | zstd | +| "7L0" | level 7 using the default window log value | zstd | diff --git a/src/site/site.xml b/src/site/site.xml index c22f2d1..6595d04 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -16,6 +16,8 @@ +