package picocli.codegen.aot.graalvm;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Mixin;
import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import picocli.codegen.util.Util;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
/**
* {@code ResourceConfigGenerator} generates a JSON String with the resource bundles and other classpath resources
* that should be included in the Substrate VM native image.
*
* The GraalVM native-image builder by default will not integrate any of the
* classpath resources into the image it creates.
*
* The output of {@code ResourceConfigGenerator} is intended to be passed to the {@code -H:ResourceConfigurationFiles=/path/to/resource-config.json}
* option of the {@code native-image} GraalVM utility.
* This allows picocli-based native image applications to access these resources.
*
* Alternatively, the generated configuration
* files can be supplied to the {@code native-image} tool by placing them in a
* {@code META-INF/native-image/} directory on the class path, for example, in a JAR file used in the image build.
* This directory (or any of its subdirectories) is searched for files with the names {@code jni-config.json},
* {@code reflect-config.json}, {@code proxy-config.json} and {@code resource-config.json}, which are then automatically
* included in the build. Not all of those files must be present.
* When multiple files with the same name are found, all of them are included.
*
*
* @since 4.0
*/
public class ResourceConfigGenerator {
@Command(name = "gen-resource-config", showAtFileInUsageHelp = true, sortOptions = false,
description = {"Generates a JSON file with the resources and resource bundles to include in the native image.",
"The generated JSON file can be passed to the `-H:ResourceConfigurationFiles=/path/to/resource-config.json` " +
"option of the `native-image` GraalVM utility.",
"See https://github.com/oracle/graal/blob/master/substratevm/Resources.md"},
exitCodeListHeading = "%nExit Codes (if enabled with `--exit`)%n",
exitCodeList = {
"0:Successful program execution.",
"1:A runtime exception occurred while generating man pages.",
"2:Usage error: user input for the command was incorrect, " +
"e.g., the wrong number of arguments, a bad flag, " +
"a bad syntax in a parameter, etc."
},
footerHeading = "%nExample%n",
footer = {
" java -cp \"myapp.jar;picocli-4.6.1.jar;picocli-codegen-4.6.1.jar\" " +
"picocli.codegen.aot.graalvm.ResourceConfigGenerator my.pkg.MyClass"
},
mixinStandardHelpOptions = true, version = "picocli-codegen gen-resource-config " + CommandLine.VERSION)
private static class App implements Callable {
@Parameters(arity = "0..*", description = "Zero or more `@Command` classes with a resource bundle to include in the image.")
Class>[] classes = new Class>[0];
@Option(names = {"-b", "--bundle"}, paramLabel = "",
description = "Additional resource bundle(s) to be included in the image. " +
"This option may be specified multiple times with different regular expression patterns.")
String[] bundles = new String[0];
@Option(names = {"-p", "--pattern"}, description = "Java regexp that matches resource(s) to be included in the image. " +
"This option may be specified multiple times with different regular expression patterns.")
String[] resourceRegex = new String[0];
@Option(names = {"-c", "--factory"}, description = "Optionally specify the fully qualified class name of the custom factory to use to instantiate the command class. " +
"When omitted, the default picocli factory is used.")
String factoryClass;
@Mixin
OutputFileMixin outputFile = new OutputFileMixin();
@Option(names = "--exit", negatable = true,
description = "Specify `--exit` if you want the application to call `System.exit` when finished. " +
"By default, `System.exit` is not called.")
boolean exit;
public Integer call() throws Exception {
List specs = Util.getCommandSpecs(factoryClass, classes);
String result = ResourceConfigGenerator.generateResourceConfig(specs.toArray(new CommandSpec[0]), bundles, resourceRegex);
outputFile.write(result);
return 0;
}
}
/**
* Runs this class as a standalone application, printing the resulting JSON String to a file or to {@code System.out}.
* @param args one or more fully qualified class names of {@code @Command}-annotated classes.
*/
public static void main(String... args) {
App app = new App();
int exitCode = new CommandLine(app).execute(args);
if (app.exit) {
System.exit(exitCode);
}
}
/**
* Returns a JSON String with the resources and resource bundles to include for the specified
* {@code CommandSpec} objects.
*
* @param specs one or more {@code CommandSpec} objects to inspect for resource bundles
* @param bundles base names of additional resource bundles to be included in the image
* @param resourceRegex one or more Java regular expressions that match resource(s) to be included in the image
* @return a JSON String in the format
* required by the {@code -H:ResourceConfigurationFiles=/path/to/resource-config.json} option of the GraalVM {@code native-image} utility.
*/
public static String generateResourceConfig(CommandSpec[] specs, String[] bundles, String[] resourceRegex) {
Visitor visitor = new Visitor();
visitor.bundles.addAll(Arrays.asList(bundles));
visitor.resources.addAll(Arrays.asList(resourceRegex));
for (CommandSpec spec : specs) {
visitor.visitCommandSpec(spec);
}
return visitor.toString();
}
static final class Visitor {
Set resources = new LinkedHashSet();
Set bundles = new LinkedHashSet();
void visitCommandSpec(CommandSpec spec) {
String bundle = spec.resourceBundleBaseName();
if (bundle != null) {
bundles.add(bundle);
}
for (CommandSpec mixin : spec.mixins().values()) {
visitCommandSpec(mixin);
}
for (CommandLine sub : spec.subcommands().values()) {
visitCommandSpec(sub.getCommandSpec());
}
}
@Override
public String toString() {
return String.format("" +
"{%n" +
" \"bundles\" : [%s%n" +
" ],%n" +
" \"resources\" : [%s%n" +
" ]%n" +
"}%n", bundlesJson(), resourcesJson());
}
private StringBuilder bundlesJson() {
return json(bundles, "name");
}
private StringBuilder resourcesJson() {
return json(resources, "pattern");
}
private static StringBuilder json(Collection strings, String label) {
StringBuilder result = new StringBuilder(1024);
for (String str : strings) {
if (result.length() > 0) {
result.append(",");
}
result.append(String.format("%n {\"%s\" : \"%s\"}", label, str));
}
return result;
}
}
}