org.graalvm.launcher.LanguageLauncherBase Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of launcher-common Show documentation
Show all versions of launcher-common Show documentation
Common infrastructure to create language launchers using the Polyglot API.
The newest version!
/*
* Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
*
* Subject to the condition set forth below, permission is hereby granted to any
* person obtaining a copy of this software, associated documentation and/or
* data (collectively the "Software"), free of charge and under any and all
* copyright rights in the Software, and any and all patent rights owned or
* freely licensable by each licensor hereunder covering either (i) the
* unmodified Software as contributed to or provided by such licensor, or (ii)
* the Larger Works (as defined below), to deal in both
*
* (a) the Software, and
*
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
* one is included with the Software each a "Larger Work" to which the Software
* is contributed by such licensors),
*
* without restriction, including without limitation the rights to copy, create
* derivative works of, display, perform, and distribute the Software and make,
* use, sell, offer for sale, import, export, have made, and have sold the
* Software and the Larger Work(s), and to sublicense the foregoing rights on
* either these or other terms.
*
* This license is subject to the following condition:
*
* The above copyright notice and either this complete permission notice or at a
* minimum a reference to the UPL must be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.graalvm.launcher;
import static java.lang.Integer.max;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.graalvm.options.OptionCategory;
import org.graalvm.options.OptionDescriptor;
import org.graalvm.options.OptionDescriptors;
import org.graalvm.options.OptionStability;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Engine;
import org.graalvm.polyglot.Instrument;
import org.graalvm.polyglot.Language;
import org.graalvm.polyglot.PolyglotException;
/**
* Base implementation for polyglot-aware languages and tools. Prints additional language-related
* help items, prints installed engine's options.
*/
public abstract class LanguageLauncherBase extends Launcher {
private static final String ID_HEADER = "[id]";
private static final String NAME_HEADER = "[name]";
private static final String WEBSITE_HEADER = "[website]";
private static Engine tempEngine;
private boolean seenPolyglot;
private VersionAction versionAction = VersionAction.None;
static Engine getTempEngine() {
if (tempEngine == null) {
tempEngine = Engine.newBuilder().useSystemProperties(false).//
out(OutputStream.nullOutputStream()).//
err(OutputStream.nullOutputStream()).//
option("engine.WarnInterpreterOnly", "false").//
build();
}
return tempEngine;
}
private static String website(Instrument instrument) {
String website = instrument.getWebsite();
return website.isEmpty() ? "" : " (" + website + ")";
}
private static String title(Language language) {
final StringBuilder title = new StringBuilder(" " + language.getName());
final String website = language.getWebsite();
if (!"".equals(website)) {
title.append(" (");
title.append(website);
title.append(")");
}
title.append(":");
return title.toString();
}
private static List filterOptions(OptionDescriptors descriptors, OptionCategory optionCategory, String optionPrefixDot) {
assert optionPrefixDot.endsWith(".");
List options = new ArrayList<>();
for (OptionDescriptor descriptor : descriptors) {
if (!descriptor.isDeprecated() && sameCategory(descriptor, optionCategory)) {
if (optionPrefixDot.equals("all.") || descriptor.getName().startsWith(optionPrefixDot)) {
options.add(asPrintableOption(descriptor));
}
}
}
options.sort(PrintableOption::compareTo);
return options;
}
private static boolean sameCategory(OptionDescriptor descriptor, OptionCategory optionCategory) {
return descriptor.getCategory().ordinal() == optionCategory.ordinal();
}
private static Launcher.PrintableOption asPrintableOption(OptionDescriptor descriptor) {
StringBuilder key = new StringBuilder("--");
final String name = descriptor.getName();
key.append(name);
if (descriptor.isOptionMap()) {
key.append(".");
}
String usageSyntax = descriptor.getUsageSyntax();
if (usageSyntax != null) {
key.append("=");
key.append(usageSyntax);
}
String help = descriptor.getHelp();
if (descriptor.isDeprecated()) {
help = help + " [Deprecated]";
}
return new PrintableOption(name, key.toString(), help, descriptor.getStability() == OptionStability.EXPERIMENTAL);
}
private static void addOptions(OptionDescriptors descriptors, Set target) {
for (OptionDescriptor descriptor : descriptors) {
target.add("--" + descriptor.getName());
}
}
static List sortedLanguages(Engine engine) {
List languages = new ArrayList<>(engine.getLanguages().values());
languages.sort(Comparator.comparing(Language::getId));
return languages;
}
static List sortedInstruments(Engine engine) {
List instruments = new ArrayList<>();
for (Instrument instrument : engine.getInstruments().values()) {
// no options not accessible to the user.
if (!instrument.getOptions().iterator().hasNext()) {
continue;
}
instruments.add(instrument);
}
instruments.sort(Comparator.comparing(Instrument::getId));
return instruments;
}
final boolean isPolyglot() {
return seenPolyglot;
}
final void setPolyglot(boolean polyglot) {
seenPolyglot = polyglot;
}
final void setupContextBuilder(Context.Builder builder) {
Path logFile = getLogFile();
if (logFile != null) {
try {
builder.logHandler(newLogStream(logFile));
} catch (IOException ioe) {
throw abort(ioe);
}
}
if (System.err != getError()) {
builder.err(getError());
}
if (System.out != getOutput()) {
builder.out(getOutput());
}
}
protected void argumentsProcessingDone() {
if (tempEngine != null) {
tempEngine.close();
tempEngine = null;
}
}
@Override
protected boolean runLauncherAction() {
switch (versionAction) {
case PrintAndExit:
printPolyglotVersions();
return true;
case PrintAndContinue:
printPolyglotVersions();
break;
case None:
break;
}
return super.runLauncherAction();
}
@Override
protected boolean parseCommonOption(String defaultOptionPrefix, Map polyglotOptions, boolean experimentalOptions, String arg) {
switch (arg) {
case "--polyglot":
seenPolyglot = true;
break;
case "--version:graalvm":
versionAction = VersionAction.PrintAndExit;
break;
case "--show-version:graalvm":
versionAction = VersionAction.PrintAndContinue;
break;
default:
return super.parseCommonOption(defaultOptionPrefix, polyglotOptions, experimentalOptions, arg);
}
return true;
}
void handlePolyglotException(PolyglotException e) {
String message = null;
if (e.getMessage() != null) {
message = e.getMessage();
} else if (e.isInternalError()) {
message = "Unknown error";
}
if (message != null) {
System.err.println("ERROR: " + message);
}
if (e.isInternalError()) {
e.printStackTrace();
}
if (e.isExit()) {
System.exit(e.getExitStatus());
} else {
System.exit(1);
}
}
@Override
protected void printDefaultHelp(OptionCategory helpCategory) {
super.printDefaultHelp(helpCategory);
launcherOption("--help:engine", "Print engine options.");
launcherOption("--help:compiler", "Print engine compiler options.");
launcherOption("--help:all", "Print all options.");
launcherOption("--version:graalvm", "Print GraalVM version information and exit.");
launcherOption("--show-version:graalvm", "Print GraalVM version information and continue execution.");
println("");
printInstalledComponents();
println("");
println(" Use --help:[id] for component options.");
}
private void printInstalledComponents() {
List languages = sortedLanguages(getTempEngine());
int maxId = ID_HEADER.length();
int maxName = NAME_HEADER.length();
for (Language language : languages) {
maxId = Math.max(maxId, language.getId().length());
maxName = max(maxName, language.getName().length());
}
List instruments = sortedInstruments(getTempEngine());
for (Instrument instrument : instruments) {
maxId = max(maxId, instrument.getId().length());
maxName = max(maxName, instrument.getName().length());
}
if (!languages.isEmpty()) {
println("Languages:");
printInstalled(maxId, maxName, ID_HEADER, NAME_HEADER, WEBSITE_HEADER);
for (Language language : languages) {
printInstalled(maxId, maxName, language.getId(), language.getName(), language.getWebsite());
}
}
println("");
if (!instruments.isEmpty()) {
println("Tools:");
printInstalled(maxId, maxName, ID_HEADER, NAME_HEADER, WEBSITE_HEADER);
for (Instrument instrument : instruments) {
printInstalled(maxId, maxName, instrument.getId(), instrument.getName(), instrument.getWebsite());
}
}
}
private void printInstalled(int maxId, int maxName, String id, String name, String website) {
println(String.format(" %-" + maxId + "s %-" + maxName + "s %s", id, name, website, id));
}
@Override
protected void maybePrintAdditionalHelp(OptionCategory helpCategory) {
if (helpArg == null || "".equals(helpArg)) {
return;
}
final boolean all = "all".equals(helpArg);
if (all || "languages".equals(helpArg)) {
helpPrinted = printLanguageOptions(getTempEngine(), null);
}
if (all || "tools".equals(helpArg)) {
helpPrinted = printInstrumentOptions(getTempEngine(), null);
}
if (all || "engine".equals(helpArg) || "compiler".equals(helpArg)) {
helpPrinted = printEngineOptions(getTempEngine(), helpArg);
}
if (helpPrinted) {
return;
}
helpPrinted = printLanguageOptions(getTempEngine(), helpArg);
helpPrinted |= printInstrumentOptions(getTempEngine(), helpArg);
}
/**
* Prints version information about all known {@linkplain Language languages} and
* {@linkplain Instrument instruments} on {@linkplain System#out stdout}.
*/
protected void printPolyglotVersions() {
Engine engine = getTempEngine();
String mode = isAOT() ? "Native" : "JVM";
println(engine.getImplementationName() + " " + mode + " Polyglot Engine Version " + engine.getVersion());
println("Java Version " + System.getProperty("java.version"));
println("Java VM Version " + System.getProperty("java.vm.version"));
Path graalVMHome = Engine.findHome();
if (graalVMHome != null) {
println("GraalVM Home " + graalVMHome);
}
printLanguages(engine, true);
printInstruments(engine, true);
}
@Override
protected OptionDescriptor findOptionDescriptor(String group, String key) {
OptionDescriptors descriptors = null;
switch (group) {
case "engine":
case "compiler":
descriptors = getTempEngine().getOptions();
break;
default:
Engine engine = getTempEngine();
if (engine.getLanguages().containsKey(group)) {
descriptors = engine.getLanguages().get(group).getOptions();
} else if (engine.getInstruments().containsKey(group)) {
descriptors = engine.getInstruments().get(group).getOptions();
}
break;
}
if (descriptors == null) {
return null;
}
return descriptors.get(key);
}
private boolean printEngineOptions(Engine engine, String optionPrefix) {
final Map> options = getCategories(engine.getOptions(), optionPrefix);
if (options != null) {
println(optionsTitle("Engine", null));
printCategory(options, OptionCategory.USER, "User options:");
printCategory(options, OptionCategory.EXPERT, "Expert options:");
printCategory(options, OptionCategory.INTERNAL, "Internal options:");
return true;
}
return false;
}
private boolean printInstrumentOptions(Engine engine, String id) {
Map>> instrumentsOptions = new HashMap<>();
List instruments = sortedInstruments(engine);
for (Instrument instrument : instruments) {
if (id == null || instrument.getId().equals(id)) {
Map> categories = getCategories(instrument.getOptions());
if (categories != null) {
instrumentsOptions.put(instrument, categories);
}
}
}
if (!instrumentsOptions.isEmpty()) {
println();
println(optionsTitle("Tool", null));
for (Instrument instrument : instruments) {
Map> options = instrumentsOptions.get(instrument);
if (options == null) {
continue;
}
println("");
println(" " + instrument.getName() + website(instrument) + ":");
printCategory(options, OptionCategory.USER, "User options:");
printCategory(options, OptionCategory.EXPERT, "Expert options:");
printCategory(options, OptionCategory.INTERNAL, "Internal options:");
println("");
println(" Use --help:" + instrument.getId() + ":internal to also show internal options.");
}
return true;
}
return false;
}
private boolean printLanguageOptions(Engine engine, String id) {
Map>> languagesOptions = new HashMap<>();
List languages = sortedLanguages(engine);
for (Language language : languages) {
if (id == null || language.getId().equals(id)) {
Map> categories = getCategories(language.getOptions());
if (categories != null) {
languagesOptions.put(language, categories);
}
}
}
if (!languagesOptions.isEmpty()) {
println();
println(optionsTitle("Language", null));
for (Language language : languages) {
Map> options = languagesOptions.get(language);
if (options == null) {
continue;
}
println("");
println(title(language));
printCategory(options, OptionCategory.USER, "User options:");
printCategory(options, OptionCategory.EXPERT, "Expert options:");
printCategory(options, OptionCategory.INTERNAL, "Internal options:");
println("");
if (options.get(OptionCategory.INTERNAL).isEmpty()) {
println(" Use --help:" + language.getId() + ":internal to also show internal options.");
}
}
return true;
}
return false;
}
private Map> getCategories(OptionDescriptors options) {
return getCategories(options, "all");
}
private Map> getCategories(OptionDescriptors options, String optionPrefix) {
String optionPrefixDot = optionPrefix + ".";
List userOptions = filterOptions(options, OptionCategory.USER, optionPrefixDot);
List expertOptions = filterOptions(options, OptionCategory.EXPERT, optionPrefixDot);
List internalOptions = helpInternal ? filterOptions(options, OptionCategory.INTERNAL, optionPrefixDot) : Collections.emptyList();
Map> categories = null;
if (!userOptions.isEmpty() || !expertOptions.isEmpty() || !internalOptions.isEmpty()) {
categories = new HashMap<>();
categories.put(OptionCategory.USER, userOptions);
categories.put(OptionCategory.EXPERT, expertOptions);
categories.put(OptionCategory.INTERNAL, internalOptions);
}
return categories;
}
private void printCategory(Map> options, OptionCategory category, String title) {
final List printableOptions = options.get(category);
if (printableOptions != null && !printableOptions.isEmpty()) {
println();
printOptions(printableOptions, title, 3);
}
}
private void printLanguages(Engine engine, boolean printWhenEmpty) {
if (engine.getLanguages().isEmpty()) {
if (printWhenEmpty) {
println(" Installed Languages: none");
}
} else {
println(" Installed Languages:");
List languages = new ArrayList<>(engine.getLanguages().size());
int nameLength = 0;
for (Language language : engine.getLanguages().values()) {
languages.add(language);
nameLength = max(nameLength, language.getName().length());
}
languages.sort(Comparator.comparing(Language::getId));
String langFormat = " %-" + nameLength + "s%s version %s%n";
for (Language language : languages) {
String host;
host = "";
String version = language.getVersion();
if (version == null || version.length() == 0) {
version = "";
}
System.out.printf(langFormat, language.getName().isEmpty() ? "Unnamed" : language.getName(), host, version);
}
}
}
private void printInstruments(Engine engine, boolean printWhenEmpty) {
if (engine.getInstruments().isEmpty()) {
if (printWhenEmpty) {
println(" Installed Tools: none");
}
} else {
println(" Installed Tools:");
List instruments = sortedInstruments(engine);
int nameLength = 0;
for (Instrument instrument : instruments) {
nameLength = max(nameLength, instrument.getName().length());
}
String instrumentFormat = " %-" + nameLength + "s version %s%n";
for (Instrument instrument : instruments) {
String version = instrument.getVersion();
if (version == null || version.length() == 0) {
version = "";
}
System.out.printf(instrumentFormat, instrument.getName().isEmpty() ? instrument.getId() : instrument.getName(), version);
}
}
}
@Override
protected void collectArguments(Set options) {
options.add("--help:languages");
options.add("--help:tools");
options.add("--version:graalvm");
options.add("--show-version:graalvm");
Engine engine = getTempEngine();
addOptions(engine.getOptions(), options);
for (Instrument instrument : engine.getInstruments().values()) {
addOptions(instrument.getOptions(), options);
}
String languageId = null;
if (this instanceof AbstractLanguageLauncher) {
languageId = ((AbstractLanguageLauncher) this).getLanguageId();
}
for (Language language : engine.getLanguages().values()) {
if (language.getId().equals(languageId)) {
for (OptionDescriptor descriptor : language.getOptions()) {
options.add("--" + descriptor.getName().substring(languageId.length() + 1));
}
}
addOptions(language.getOptions(), options);
}
}
}