diff options
13 files changed, 617 insertions, 1 deletions
@@ -40,7 +40,31 @@ pairs of [String][] and `int`, you can use the following code: List<ExampleRecord> example = decoder.decode(ByteBuffer.wrap(bytes)); -## Installation +## Command line tool + +The `jgvariant-tool` module contains a tool called `jgvariant` that can +be used to manipulate [GVariant][]-formatted files from the command line. +Its primary purpose is to enable the scripting of [OSTree][] repository +management tasks. + +Usage example (dumping the contents of an [OSTree][] summary file): + + $ jgvariant ostree summary read ./jgvariant-ostree/src/test/resources/ostree/summary + +You can build the tool either as a shaded JAR or as a native executable. + +To build and run a shaded JAR: + + $ mvn package -pl jgvariant-tool -am -Pshade + $ java -jar /home/mulk/Arbeitskasten/jgvariant/jgvariant-tool/target/jgvariant-tool-*.jar + +To build and run a native executable: + + $ mvn package -pl jgvariant-tool -am -Pnative + $ ./jgvariant-tool/target/jgvariant + + +## Library installation ### Usage with Maven diff --git a/jgvariant-parent/pom.xml b/jgvariant-parent/pom.xml index 91acdfa..4a2c2f2 100644 --- a/jgvariant-parent/pom.xml +++ b/jgvariant-parent/pom.xml @@ -59,10 +59,12 @@ SPDX-License-Identifier: LGPL-3.0-or-later <failsafe-plugin.version>${surefire-plugin.version}</failsafe-plugin.version> <flatten-plugin.version>1.5.0</flatten-plugin.version> <jar-plugin.version>3.3.0</jar-plugin.version> + <jpackage-plugin.version>0.1.5</jpackage-plugin.version> <maven-scm-plugin.version>2.0.1</maven-scm-plugin.version> <maven-gpg-plugin.version>3.1.0</maven-gpg-plugin.version> <maven-javadoc-plugin.version>3.6.3</maven-javadoc-plugin.version> <maven-source-plugin.version>3.3.0</maven-source-plugin.version> + <native-plugin.version>0.9.23</native-plugin.version> <nexus-staging-plugin.version>1.6.13</nexus-staging-plugin.version> <spotless-plugin.version>2.41.1</spotless-plugin.version> <surefire-plugin.version>3.2.2</surefire-plugin.version> @@ -71,10 +73,13 @@ SPDX-License-Identifier: LGPL-3.0-or-later <apiguardian.version>1.1.2</apiguardian.version> <errorprone.version>2.23.0</errorprone.version> <google-java-format.version>1.15.0</google-java-format.version> + <guava.version>32.1.3-jre</guava.version> <inject-resources.version>0.3.3</inject-resources.version> <jetbrains-annotations.version>24.1.0</jetbrains-annotations.version> <junit-jupiter.version>5.10.1</junit-jupiter.version> <nullaway.version>0.10.18</nullaway.version> + <picocli.version>4.7.4</picocli.version> + <yasson.version>3.0.2</yasson.version> <xz.version>1.9</xz.version> </properties> @@ -111,6 +116,27 @@ SPDX-License-Identifier: LGPL-3.0-or-later <version>${xz.version}</version> </dependency> + <!-- Command line tooling --> + <dependency> + <groupId>info.picocli</groupId> + <artifactId>picocli</artifactId> + <version>${picocli.version}</version> + </dependency> + + <!-- JSON --> + <dependency> + <groupId>org.eclipse</groupId> + <artifactId>yasson</artifactId> + <version>${yasson.version}</version> + </dependency> + + <!-- Guava --> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + <version>${guava.version}</version> + </dependency> + <!-- Testing --> <dependency> <groupId>org.junit.jupiter</groupId> @@ -240,6 +266,18 @@ SPDX-License-Identifier: LGPL-3.0-or-later </executions> </plugin> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + <version>${native-plugin.version}</version> + </plugin> + + <plugin> + <groupId>com.github.akman</groupId> + <artifactId>jpackage-maven-plugin</artifactId> + <version>${jpackage-plugin.version}</version> + </plugin> + </plugins> </pluginManagement> diff --git a/jgvariant-tool/pom.xml b/jgvariant-tool/pom.xml new file mode 100644 index 0000000..ee2b8a8 --- /dev/null +++ b/jgvariant-tool/pom.xml @@ -0,0 +1,239 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- +SPDX-FileCopyrightText: © 2023 Matthias Andreas Benkard <code@mail.matthias.benkard.de> + +SPDX-License-Identifier: GPL-3.0-or-later +--> + +<project + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" + xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + + <modelVersion>4.0.0</modelVersion> + + <version>0.1.8-SNAPSHOT</version> + + <artifactId>jgvariant-tool</artifactId> + <packaging>jar</packaging> + + <name>JGVariant Command Line Tool</name> + <url>https://gerrit.benkard.de/plugins/gitiles/jgvariant</url> + + <description> + GVariant command line tool. + </description> + + <parent> + <groupId>eu.mulk.jgvariant</groupId> + <artifactId>jgvariant-parent</artifactId> + <version>0.1.8-SNAPSHOT</version> + <relativePath>../jgvariant-parent/pom.xml</relativePath> + </parent> + + <dependencies> + <!-- JGVariant --> + <dependency> + <groupId>eu.mulk.jgvariant</groupId> + <artifactId>jgvariant-core</artifactId> + <version>0.1.8-SNAPSHOT</version> + </dependency> + <dependency> + <groupId>eu.mulk.jgvariant</groupId> + <artifactId>jgvariant-ostree</artifactId> + <version>0.1.8-SNAPSHOT</version> + </dependency> + + <!-- Annotations --> + <dependency> + <groupId>com.google.errorprone</groupId> + <artifactId>error_prone_annotations</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.jetbrains</groupId> + <artifactId>annotations</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apiguardian</groupId> + <artifactId>apiguardian-api</artifactId> + <scope>provided</scope> + </dependency> + + <!-- Command line tooling --> + <dependency> + <groupId>info.picocli</groupId> + <artifactId>picocli</artifactId> + </dependency> + + <!-- JSON --> + <dependency> + <groupId>org.eclipse</groupId> + <artifactId>yasson</artifactId> + </dependency> + + <!-- Guava --> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <annotationProcessorPaths> + <path> + <groupId>info.picocli</groupId> + <artifactId>picocli-codegen</artifactId> + <version>${picocli.version}</version> + </path> + </annotationProcessorPaths> + <compilerArgs> + <arg>-Aproject=${project.groupId}/${project.artifactId}</arg> + </compilerArgs> + </configuration> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <configuration> + <archive> + <manifest> + <mainClass>eu.mulk.jgvariant.tool.Main</mainClass> + </manifest> + </archive> + </configuration> + </plugin> + </plugins> + </build> + + <profiles> + <profile> + <id>shade</id> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>shade</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> + + <profile> + <id>uberjar</id> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-assembly-plugin</artifactId> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>single</goal> + </goals> + <configuration> + <archive> + <manifest> + <mainClass>eu.mulk.jgvariant.tool.Main</mainClass> + </manifest> + </archive> + <descriptorRefs> + <descriptorRef>jar-with-dependencies</descriptorRef> + </descriptorRefs> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> + + <profile> + <id>native</id> + <build> + <plugins> + <plugin> + <groupId>org.graalvm.buildtools</groupId> + <artifactId>native-maven-plugin</artifactId> + <extensions>true</extensions> + <executions> + <execution> + <id>build-native</id> + <goals> + <goal>compile-no-fork</goal> + </goals> + <phase>package</phase> + </execution> + <execution> + <id>test-native</id> + <goals> + <goal>test</goal> + </goals> + <phase>test</phase> + </execution> + </executions> + <configuration> + <debug>false</debug> + <fallback>false</fallback> + <buildArgs> + <arg>-O3</arg> + <arg>--strict-image-heap</arg> + </buildArgs> + <imageName>jgvariant</imageName> + </configuration> + </plugin> + </plugins> + </build> + </profile> + + <profile> + <id>jpackage</id> + <build> + <plugins> + <plugin> + <groupId>com.github.akman</groupId> + <artifactId>jpackage-maven-plugin</artifactId> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>jpackage</goal> + </goals> + <configuration> + <type>IMAGE</type> + <module>eu.mulk.jgvariant.tool/eu.mulk.jgvariant.tool.Main</module> + <modulepath> + <dependencysets> + <dependencyset> + <includeoutput>true</includeoutput> + <excludeautomatic>true</excludeautomatic> + </dependencyset> + </dependencysets> + </modulepath> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> + </profiles> + +</project> diff --git a/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/Main.java b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/Main.java new file mode 100644 index 0000000..64e375a --- /dev/null +++ b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/Main.java @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: © 2023 Matthias Andreas Benkard <code@mail.matthias.benkard.de> +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package eu.mulk.jgvariant.tool; + +import static java.util.logging.Level.WARNING; + +import java.util.logging.Logger; +import picocli.CommandLine; + +/** + * A command line tool to read and manipulate GVariant-formatted files. + * + * <p>Also provides ways to manipulate OSTree repositories. + */ +public final class Main { + static { + Logger.getGlobal().setLevel(WARNING); + } + + public static void main(String[] args) { + int exitCode = new CommandLine(new MainCommand()).execute(args); + System.exit(exitCode); + } + + private Main() {} +} diff --git a/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/MainCommand.java b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/MainCommand.java new file mode 100644 index 0000000..7b25dfc --- /dev/null +++ b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/MainCommand.java @@ -0,0 +1,115 @@ +// SPDX-FileCopyrightText: © 2023 Matthias Andreas Benkard <code@mail.matthias.benkard.de> +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package eu.mulk.jgvariant.tool; + +import static java.util.logging.Level.*; + +import eu.mulk.jgvariant.ostree.Summary; +import eu.mulk.jgvariant.tool.jsonb.*; +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; +import jakarta.json.bind.JsonbConfig; +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.ByteBuffer; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.util.logging.Logger; +import org.jetbrains.annotations.VisibleForTesting; +import picocli.AutoComplete; +import picocli.CommandLine; +import picocli.CommandLine.*; + +@Command( + name = "jgvariant", + mixinStandardHelpOptions = true, + description = "Manipulate files in GVariant format.", + subcommands = {MainCommand.OstreeCommand.class, AutoComplete.GenerateCompletion.class}) +final class MainCommand { + + private static final Logger LOG = Logger.getLogger("eu.mulk.jgvariant.tool"); + + private static final Jsonb jsonb = + JsonbBuilder.newBuilder() + .withConfig( + new JsonbConfig() + .withFormatting(true) + .withAdapters(ChecksumAdapter.INSTANCE) + .withSerializers( + ByteStringSerializer.INSTANCE, + ByteArraySerializer.INSTANCE, + SignatureSerializer.INSTANCE, + VariantSerializer.INSTANCE)) + .build(); + + @Option( + names = {"-v", "--verbose"}, + description = "Enable verbose logging.", + scope = CommandLine.ScopeType.INHERIT) + void setVerbose(boolean[] verbose) { + Logger.getGlobal() + .setLevel( + switch (verbose.length) { + case 0 -> WARNING; + case 1 -> INFO; + case 2 -> FINE; + default -> ALL; + }); + } + + @Command( + name = "ostree", + mixinStandardHelpOptions = true, + description = "Manipulate OSTree files.", + subcommands = {OstreeCommand.SummaryCommand.class}) + static final class OstreeCommand { + + @Command( + name = "summary", + mixinStandardHelpOptions = true, + description = "Manipulate OSTree summary files.") + static final class SummaryCommand extends BaseCommand { + + @Command(mixinStandardHelpOptions = true) + void read(@Parameters(paramLabel = "<file>") File file) throws IOException { + LOG.fine(() -> "Reading file %s".formatted(file)); + var fileBytes = ByteBuffer.wrap(Files.readAllBytes(fs().getPath(file.getPath()))); + var decoder = Summary.decoder(); + var thing = decoder.decode(fileBytes); + out().println(jsonb.toJson(thing)); + } + + SummaryCommand() {} + } + + OstreeCommand() {} + } + + @Command + abstract static class BaseCommand { + + @Spec CommandLine.Model.CommandSpec spec; + + @VisibleForTesting FileSystem fs = FileSystems.getDefault(); + + protected BaseCommand() {} + + protected PrintWriter out() { + return spec.commandLine().getOut(); + } + + protected PrintWriter err() { + return spec.commandLine().getErr(); + } + + protected FileSystem fs() { + return fs; + } + } + + MainCommand() {} +} diff --git a/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/jsonb/ByteArraySerializer.java b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/jsonb/ByteArraySerializer.java new file mode 100644 index 0000000..7f80440 --- /dev/null +++ b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/jsonb/ByteArraySerializer.java @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: © 2023 Matthias Andreas Benkard <code@mail.matthias.benkard.de> +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package eu.mulk.jgvariant.tool.jsonb; + +import jakarta.json.bind.serializer.JsonbSerializer; +import jakarta.json.bind.serializer.SerializationContext; +import jakarta.json.stream.JsonGenerator; +import java.util.HexFormat; + +@SuppressWarnings("java:S6548") +public final class ByteArraySerializer implements JsonbSerializer<byte[]> { + + public static final ByteArraySerializer INSTANCE = new ByteArraySerializer(); + + private ByteArraySerializer() {} + + @Override + public void serialize( + byte[] o, JsonGenerator jsonGenerator, SerializationContext serializationContext) { + jsonGenerator.write(HexFormat.of().formatHex(o)); + } +} diff --git a/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/jsonb/ByteStringSerializer.java b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/jsonb/ByteStringSerializer.java new file mode 100644 index 0000000..3537c3d --- /dev/null +++ b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/jsonb/ByteStringSerializer.java @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: © 2023 Matthias Andreas Benkard <code@mail.matthias.benkard.de> +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package eu.mulk.jgvariant.tool.jsonb; + +import eu.mulk.jgvariant.ostree.ByteString; +import jakarta.json.bind.serializer.JsonbSerializer; +import jakarta.json.bind.serializer.SerializationContext; +import jakarta.json.stream.JsonGenerator; + +@SuppressWarnings("java:S6548") +public final class ByteStringSerializer implements JsonbSerializer<ByteString> { + + public static final ByteStringSerializer INSTANCE = new ByteStringSerializer(); + + private ByteStringSerializer() {} + + @Override + public void serialize( + ByteString o, JsonGenerator jsonGenerator, SerializationContext serializationContext) { + jsonGenerator.write(o.hex()); + } +} diff --git a/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/jsonb/ChecksumAdapter.java b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/jsonb/ChecksumAdapter.java new file mode 100644 index 0000000..58a8951 --- /dev/null +++ b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/jsonb/ChecksumAdapter.java @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: © 2023 Matthias Andreas Benkard <code@mail.matthias.benkard.de> +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package eu.mulk.jgvariant.tool.jsonb; + +import eu.mulk.jgvariant.ostree.ByteString; +import eu.mulk.jgvariant.ostree.Checksum; +import jakarta.json.bind.adapter.JsonbAdapter; + +@SuppressWarnings("java:S6548") +public final class ChecksumAdapter implements JsonbAdapter<Checksum, ByteString> { + + public static final ChecksumAdapter INSTANCE = new ChecksumAdapter(); + + private ChecksumAdapter() {} + + @Override + public ByteString adaptToJson(Checksum obj) throws Exception { + return obj.byteString(); + } + + @Override + public Checksum adaptFromJson(ByteString obj) throws Exception { + return new Checksum(obj); + } +} diff --git a/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/jsonb/SignatureSerializer.java b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/jsonb/SignatureSerializer.java new file mode 100644 index 0000000..dca1044 --- /dev/null +++ b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/jsonb/SignatureSerializer.java @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: © 2023 Matthias Andreas Benkard <code@mail.matthias.benkard.de> +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package eu.mulk.jgvariant.tool.jsonb; + +import eu.mulk.jgvariant.core.Signature; +import jakarta.json.bind.serializer.JsonbSerializer; +import jakarta.json.bind.serializer.SerializationContext; +import jakarta.json.stream.JsonGenerator; + +@SuppressWarnings("java:S6548") +public final class SignatureSerializer implements JsonbSerializer<Signature> { + + public static final SignatureSerializer INSTANCE = new SignatureSerializer(); + + private SignatureSerializer() {} + + @Override + public void serialize( + Signature o, JsonGenerator jsonGenerator, SerializationContext serializationContext) { + jsonGenerator.write(o.toString()); + } +} diff --git a/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/jsonb/VariantSerializer.java b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/jsonb/VariantSerializer.java new file mode 100644 index 0000000..99ff553 --- /dev/null +++ b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/jsonb/VariantSerializer.java @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: © 2023 Matthias Andreas Benkard <code@mail.matthias.benkard.de> +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package eu.mulk.jgvariant.tool.jsonb; + +import com.google.common.primitives.Bytes; +import eu.mulk.jgvariant.core.Signature; +import eu.mulk.jgvariant.core.Variant; +import jakarta.json.bind.serializer.JsonbSerializer; +import jakarta.json.bind.serializer.SerializationContext; +import jakarta.json.stream.JsonGenerator; +import java.text.ParseException; +import java.util.List; + +@SuppressWarnings("java:S6548") +public final class VariantSerializer implements JsonbSerializer<Variant> { + + public static final VariantSerializer INSTANCE = new VariantSerializer(); + + private final ByteArraySerializer byteArraySerializer = ByteArraySerializer.INSTANCE; + + private final Signature byteArraySignature; + + private VariantSerializer() { + try { + byteArraySignature = Signature.parse("ay"); + } catch (ParseException e) { + // impossible + throw new IllegalArgumentException(e); + } + } + + @Override + @SuppressWarnings("unchecked") + public void serialize(Variant obj, JsonGenerator generator, SerializationContext ctx) { + if (obj.signature().equals(byteArraySignature)) { + byteArraySerializer.serialize(Bytes.toArray((List<Byte>) obj.value()), generator, ctx); + } else { + ctx.serialize(obj.value(), generator); + } + } +} diff --git a/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/package-info.java b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/package-info.java new file mode 100644 index 0000000..da5c3ba --- /dev/null +++ b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/package-info.java @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: © 2023 Matthias Andreas Benkard <code@mail.matthias.benkard.de> +// +// SPDX-License-Identifier: GPL-3.0-or-later + +@API(status = API.Status.INTERNAL) +package eu.mulk.jgvariant.tool; + +import org.apiguardian.api.API; diff --git a/jgvariant-tool/src/main/java/module-info.java b/jgvariant-tool/src/main/java/module-info.java new file mode 100644 index 0000000..e66981c --- /dev/null +++ b/jgvariant-tool/src/main/java/module-info.java @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: © 2023 Matthias Andreas Benkard <code@mail.matthias.benkard.de> +// +// SPDX-License-Identifier: GPL-3.0-or-later + +module eu.mulk.jgvariant.tool { + requires transitive eu.mulk.jgvariant.ostree; + requires com.google.common; + requires info.picocli; + requires jakarta.json; + requires jakarta.json.bind; + requires java.logging; + requires static com.google.errorprone.annotations; + requires static org.apiguardian.api; + requires static org.jetbrains.annotations; + + opens eu.mulk.jgvariant.tool to + info.picocli; + + exports eu.mulk.jgvariant.tool; +} @@ -37,6 +37,8 @@ SPDX-License-Identifier: LGPL-3.0-or-later <module>jgvariant-core</module> <module>jgvariant-ostree</module> + <module>jgvariant-tool</module> + <module>jgvariant-bom</module> </modules> |