/*
* Copyright (c) 2017, 2021, 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.nio.file.StandardOpenOption.APPEND;
import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.CREATE_NEW;
import static java.nio.file.StandardOpenOption.WRITE;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.lang.ref.WeakReference;
import java.nio.channels.FileChannel;
import java.nio.channels.OverlappingFileLockException;
import java.nio.file.AccessDeniedException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Formatter;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.logging.Level;
import org.graalvm.home.HomeFinder;
import org.graalvm.nativeimage.ProcessProperties;
import org.graalvm.nativeimage.RuntimeOptions;
import org.graalvm.nativeimage.RuntimeOptions.OptionClass;
import org.graalvm.nativeimage.VMRuntime;
import org.graalvm.options.OptionCategory;
import org.graalvm.options.OptionDescriptor;
import org.graalvm.options.OptionDescriptors;
import org.graalvm.options.OptionStability;
import org.graalvm.options.OptionType;
public abstract class Launcher {
private static final boolean STATIC_VERBOSE = Boolean.getBoolean("org.graalvm.launcher.verbose");
private static final boolean SHELL_SCRIPT_LAUNCHER = Boolean.getBoolean("org.graalvm.launcher.shell");
Default option description indentation.
/**
* Default option description indentation.
*/
public static final int LAUNCHER_OPTIONS_INDENT = 45;
static final boolean IS_AOT = Boolean.getBoolean("com.oracle.graalvm.isaot");
public enum VMType {
Native,
JVM
}
final Native nativeAccess;
private final boolean verbose;
private PrintStream out = System.out;
private PrintStream err = System.err;
private boolean help;
private boolean helpInternal;
private boolean helpExpert;
private boolean helpVM;
Path to the desired log file, or null
if no log redirection is required. /**
* Path to the desired log file, or {@code null} if no log redirection is required.
*/
private Path logFile;
Number of spaces reserved for the options column. Can be set separately for each option
block.
/**
* Number of spaces reserved for the options column. Can be set separately for each option
* block.
*/
private int optionIndent = LAUNCHER_OPTIONS_INDENT;
Accumulates help categories and their relevant options.
See Also: - printOtherHelpCategory
/**
* Accumulates help categories and their relevant options.
*
* @see #printOtherHelpCategory
*/
private List<String> kindAndCategory = new ArrayList<>();
protected enum VersionAction {
None,
PrintAndExit,
PrintAndContinue
}
protected Launcher() {
verbose = STATIC_VERBOSE || Boolean.parseBoolean(System.getenv("VERBOSE_GRAALVM_LAUNCHERS"));
if (IS_AOT) {
nativeAccess = new Native();
} else {
nativeAccess = null;
}
}
Provides the name of the log file, if specified on the command line.
Returns: log file Path. null
if unspecified. Since: 20.0
/**
* Provides the name of the log file, if specified on the command line.
*
* @return log file Path. {@code null} if unspecified.
* @since 20.0
*/
protected final Path getLogFile() {
return logFile;
}
Uses the defined output to print messages.
Params: - ps – printStream to use as out
Since: 20.0
/**
* Uses the defined output to print messages.
*
* @param ps printStream to use as out
* @since 20.0
*/
protected final void setOutput(PrintStream ps) {
this.out = ps;
}
Uses the defined output to print error messages.
Params: - ps – printStream to use as err
Since: 20.0
/**
* Uses the defined output to print error messages.
*
* @param ps printStream to use as err
* @since 20.0
*/
protected final void setError(PrintStream ps) {
this.err = ps;
}
Returns: the stream for regular output. Defaults to System.out
Since: 20.0
/**
* @return the stream for regular output. Defaults to {@link System#out}
* @since 20.0
*/
protected final PrintStream getOutput() {
return out;
}
Returns: the stream for errors. Defaults to System.err
Since: 20.0
/**
* @return the stream for errors. Defaults to {@link System#err}
* @since 20.0
*/
protected final PrintStream getError() {
return err;
}
void handleAbortException(AbortException e) {
if (e.getMessage() != null) {
err.println("ERROR: " + e.getMessage());
}
if (e.getCause() != null) {
e.printStackTrace();
}
System.exit(e.getExitCode());
}
Exception which shall abort the launcher execution. Thrown by this class in the case of
malformed arguments or unknown options, or deliberate exit.
Since: 20.0
/**
* Exception which shall abort the launcher execution. Thrown by this class in the case of
* malformed arguments or unknown options, or deliberate exit.
*
* @since 20.0
*/
protected static final class AbortException extends RuntimeException {
static final long serialVersionUID = 4681646279864737876L;
private final int exitCode;
AbortException(String message, int exitCode) {
super(message);
this.exitCode = exitCode;
}
AbortException(Throwable cause, int exitCode) {
super(null, cause);
this.exitCode = exitCode;
}
public int getExitCode() {
return exitCode;
}
@SuppressWarnings("sync-override")
@Override
public Throwable fillInStackTrace() {
return this;
}
}
Exits the launcher, indicating success. This exits by throwing an AbortException
. /**
* Exits the launcher, indicating success.
*
* This exits by throwing an {@link AbortException}.
*/
protected final AbortException exit() {
return exit(0);
}
Exits the launcher with the provided exit code. This exits by throwing an AbortException
. Params: - exitCode – the exit code of the launcher process.
/**
* Exits the launcher with the provided exit code.
*
* This exits by throwing an {@link AbortException}.
*
* @param exitCode the exit code of the launcher process.
*/
protected final AbortException exit(int exitCode) {
return abort((String) null, exitCode);
}
Exits the launcher, indicating failure. This aborts by throwing an AbortException
. Params: - message – an error message that will be printed to stderr. If null, nothing will be printed.
/**
* Exits the launcher, indicating failure.
*
* This aborts by throwing an {@link AbortException}.
*
* @param message an error message that will be printed to {@linkplain System#err stderr}. If
* null, nothing will be printed.
*/
protected final AbortException abort(String message) {
return abort(message, 1);
}
Exits the launcher, with the provided exit code. This aborts by throwing an AbortException
. Params: - message – an error message that will be printed to stderr. If null, nothing will be printed.
- exitCode – the exit code of the launcher process.
/**
* Exits the launcher, with the provided exit code.
*
* This aborts by throwing an {@link AbortException}.
*
* @param message an error message that will be printed to {@linkplain System#err stderr}. If
* null, nothing will be printed.
* @param exitCode the exit code of the launcher process.
*/
@SuppressWarnings("static-method")
protected final AbortException abort(String message, int exitCode) {
throw new AbortException(message, exitCode);
}
Exits the launcher, indicating failure because of the provided Throwable
. This aborts by throwing an AbortException
. Params: - t – the exception that causes the launcher to abort.
/**
* Exits the launcher, indicating failure because of the provided {@link Throwable}.
*
* This aborts by throwing an {@link AbortException}.
*
* @param t the exception that causes the launcher to abort.
*/
protected final AbortException abort(Throwable t) {
return abort(t, 255);
}
Exits the launcher with the provided exit code because of the provided Throwable
. This aborts by throwing an AbortException
. Params: - t – the exception that causes the launcher to abort.
- exitCode – the exit code of the launcher process.
/**
* Exits the launcher with the provided exit code because of the provided {@link Throwable}.
*
* This aborts by throwing an {@link AbortException}.
*
* @param t the exception that causes the launcher to abort.
* @param exitCode the exit code of the launcher process.
*/
protected final AbortException abort(Throwable t, int exitCode) {
if (t.getCause() instanceof IOException && t.getClass() == RuntimeException.class) {
String message = t.getMessage();
if (message != null && !message.startsWith(t.getCause().getClass().getName() + ": ")) {
err.println(message);
}
throw abort((IOException) t.getCause(), exitCode);
}
throw new AbortException(t, exitCode);
}
Exits the launcher, indicating failure because of the provided IOException
. This tries to build a helpful error message based on exception. This aborts by throwing an AbortException
. Params: - e – the exception that causes the launcher to abort.
/**
* Exits the launcher, indicating failure because of the provided {@link IOException}.
*
* This tries to build a helpful error message based on exception.
*
* This aborts by throwing an {@link AbortException}.
*
* @param e the exception that causes the launcher to abort.
*/
protected final AbortException abort(IOException e) {
return abort(e, 74);
}
Exits the launcher with the provided exit code because of the provided IOException
. This tries to build a helpful error message based on exception. This aborts by throwing an AbortException
. Params: - e – the exception that causes the launcher to abort.
- exitCode – the exit code of the launcher process
/**
* Exits the launcher with the provided exit code because of the provided {@link IOException}.
*
* This tries to build a helpful error message based on exception.
*
* This aborts by throwing an {@link AbortException}.
*
* @param e the exception that causes the launcher to abort.
* @param exitCode the exit code of the launcher process
*/
protected final AbortException abort(IOException e, int exitCode) {
String message = e.getMessage();
if (message != null) {
if (e instanceof NoSuchFileException) {
throw abort("Not such file: " + message, exitCode);
} else if (e instanceof AccessDeniedException) {
throw abort("Access denied: " + message, exitCode);
} else {
throw abort(message + " (" + e.getClass().getSimpleName() + ")", exitCode);
}
}
throw abort((Throwable) e, exitCode);
}
This is called to abort execution when an argument can neither be recognized by the launcher
or as an option for the polyglot engine.
Params: - argument – the argument that was not recognized.
/**
* This is called to abort execution when an argument can neither be recognized by the launcher
* or as an option for the polyglot engine.
*
* @param argument the argument that was not recognized.
*/
protected AbortException abortUnrecognizedArgument(String argument) {
throw abortInvalidArgument(argument, "Unrecognized argument: '" + argument + "'. Use --help for usage instructions.");
}
Exits the launcher, indicating failure because of an invalid argument. This aborts by throwing an AbortException
. Params: - argument – the problematic argument.
- message – an error message that is printed to stderr.
/**
* Exits the launcher, indicating failure because of an invalid argument.
*
* This aborts by throwing an {@link AbortException}.
*
* @param argument the problematic argument.
* @param message an error message that is printed to {@linkplain System#err stderr}.
*/
protected final AbortException abortInvalidArgument(String argument, String message) {
return abortInvalidArgument(argument, message, 2);
}
Exits the launcher with the provided exit code because of an invalid argument. This aborts by throwing an AbortException
. Params: - argument – the problematic argument.
- message – an error message that is printed to stderr.
- exitCode – the exit code of the launcher process.
/**
* Exits the launcher with the provided exit code because of an invalid argument.
*
* This aborts by throwing an {@link AbortException}.
*
* @param argument the problematic argument.
* @param message an error message that is printed to {@linkplain System#err stderr}.
* @param exitCode the exit code of the launcher process.
*/
protected final AbortException abortInvalidArgument(String argument, String message, int exitCode) {
Set<String> allArguments = collectAllArguments();
int equalIndex;
String testString = argument;
if ((equalIndex = argument.indexOf('=')) != -1) {
testString = argument.substring(0, equalIndex);
}
List<String> matches = fuzzyMatch(allArguments, testString, 0.7f);
if (matches.isEmpty()) {
// try even harder
matches = fuzzyMatch(allArguments, testString, 0.5f);
}
StringBuilder sb = new StringBuilder();
if (message != null) {
sb.append(message);
}
if (!matches.isEmpty()) {
if (sb.length() > 0) {
sb.append(System.lineSeparator());
}
sb.append("Did you mean one of the following arguments?").append(System.lineSeparator());
Iterator<String> iterator = matches.iterator();
while (true) {
String match = iterator.next();
sb.append(" ").append(match);
if (iterator.hasNext()) {
sb.append(System.lineSeparator());
} else {
break;
}
}
}
if (sb.length() > 0) {
throw abort(sb.toString(), exitCode);
} else {
throw exit(exitCode);
}
}
protected void warn(String message) {
err.println("Warning: " + message);
}
protected void warn(String message, Object... args) {
StringBuilder sb = new StringBuilder("Warning: ");
new Formatter(sb).format(message, args);
sb.append(System.lineSeparator());
err.print(sb.toString());
}
Sets the indentation for option descriptions. Sets number of spaces in the first column reserved for option names. Defaults to LAUNCHER_OPTIONS_INDENT
. Params: - indent – the new indent.
Since: 20.0
/**
* Sets the indentation for option descriptions. Sets number of spaces in the first column
* reserved for option names. Defaults to {@link #LAUNCHER_OPTIONS_INDENT}.
*
* @param indent the new indent.
* @since 20.0
*/
protected final void setOptionIndent(int indent) {
if (indent < 0) {
optionIndent = LAUNCHER_OPTIONS_INDENT;
} else {
optionIndent = indent;
}
}
Prints a help message to stdout. This only prints options that belong to categories maxCategory or less
. Params: - maxCategory – the maximum category of options that should be printed.
/**
* Prints a help message to {@linkplain System#out stdout}. This only prints options that belong
* to categories {@code maxCategory or less}.
*
* @param maxCategory the maximum category of options that should be printed.
*/
protected abstract void printHelp(OptionCategory maxCategory);
Prints version information on stdout. /**
* Prints version information on {@linkplain System#out stdout}.
*/
protected abstract void printVersion();
Add all known arguments to the options
list. Params: - options – list to which valid arguments must be added.
/**
* Add all known arguments to the {@code options} list.
*
* @param options list to which valid arguments must be added.
*/
protected abstract void collectArguments(Set<String> options);
Finds the a descriptor for the option.
Params: - group – option group
- key – the option name (including the group)
Returns: descriptor or null
. Since: 20.0
/**
* Finds the a descriptor for the option.
*
* @param group option group
* @param key the option name (including the group)
* @return descriptor or {@code null}.
* @since 20.0
*/
protected abstract OptionDescriptor findOptionDescriptor(String group, String key);
Determines if the tool supports polyglot. Returns true, if --polyglot
option is valid for this tool and polyglot launcher works for it. The default implementation returns false only when isStandalone()
is true. Returns: true
, if polyglot is relevant in this launcher.Since: 20.0
/**
* Determines if the tool supports polyglot. Returns true, if {@code --polyglot} option is valid
* for this tool and polyglot launcher works for it. The default implementation returns false
* only when {@link #isStandalone()} is true.
*
* @return {@code true}, if polyglot is relevant in this launcher.
* @since 20.0
*/
protected boolean canPolyglot() {
return !isStandalone();
}
Should print tool-specific help. Regular languages print info on the installed tools and
languages. The default implementation prints nothing.
Params: - helpCategory – category of options to print
Since: 20.0
/**
* Should print tool-specific help. Regular languages print info on the installed tools and
* languages. The default implementation prints nothing.
*
* @param helpCategory category of options to print
* @since 20.0
*/
protected void maybePrintAdditionalHelp(OptionCategory helpCategory) {
// no op, no additional help printed.
}
private String[] executableNames(String basename) {
switch (OS.current) {
case Linux:
case Darwin:
case Solaris:
return new String[]{basename};
case Windows:
return new String[]{basename + ".exe", basename + ".cmd"};
default:
throw abort("executableName: OS not supported: " + OS.current);
}
}
Returns the name of the main class for this launcher.
Typically:
return MyLauncher.class.getName();
/**
* Returns the name of the main class for this launcher.
*
* Typically:
*
* <pre>
* return MyLauncher.class.getName();
* </pre>
*/
protected String getMainClass() {
return this.getClass().getName();
}
The return value specifies the default VM when none of --jvm, --native options is used.
Returns: the default VMType
/**
* The return value specifies the default VM when none of --jvm, --native options is used.
*
* @return the default VMType
*/
protected VMType getDefaultVMType() {
return VMType.Native;
}
Returns true if the current launcher was compiled ahead-of-time to native code.
/**
* Returns true if the current launcher was compiled ahead-of-time to native code.
*/
public static boolean isAOT() {
return IS_AOT;
}
private boolean isVerbose() {
return verbose;
}
protected boolean isGraalVMAvailable() {
return getGraalVMHome() != null;
}
protected boolean isStandalone() {
return !isGraalVMAvailable();
}
private Path home;
private OptionCategory getHelpCategory() {
if (helpInternal) {
return OptionCategory.INTERNAL;
} else if (helpExpert) {
return OptionCategory.EXPERT;
} else {
return OptionCategory.USER;
}
}
protected Path getGraalVMHome() {
if (home == null) {
home = HomeFinder.getInstance().getHomeFolder();
}
return home;
}
Returns filename of the binary, depending on OS. Binary will be searched in bin
or jre/bin
directory. Params: - binaryName – binary name, without path.
Returns: OS-dependent binary filename.
/**
* Returns filename of the binary, depending on OS. Binary will be searched in {@code bin} or
* {@code jre/bin} directory.
*
* @param binaryName binary name, without path.
* @return OS-dependent binary filename.
*/
protected final Path getGraalVMBinaryPath(String binaryName) {
String[] executableNames = executableNames(binaryName);
Path graalVMHome = getGraalVMHome();
if (graalVMHome == null) {
throw abort("Cannot exec to GraalVM binary: could not find GraalVM home");
}
for (String executableName : executableNames) {
Path[] execPaths = new Path[]{
graalVMHome.resolve("bin").resolve(executableName),
graalVMHome.resolve("jre").resolve("bin").resolve(executableName)
};
for (Path execPath : execPaths) {
if (Files.exists(execPath)) {
return execPath;
}
}
}
throw abort("Cannot exec to GraalVM binary: could not find a '" + binaryName + "' executable");
}
Runs launcher's action as version print or help. Returns true
, if the execution should terminate, e.g. after printing help. parseCommonOption
should be called for commandline argument(s) prior to this method to set up flags to display help etc. Returns: true
when execution should be terminated.Since: 20.0
/**
* Runs launcher's action as version print or help. Returns {@code true}, if the execution
* should terminate, e.g. after printing help. {@link #parseCommonOption} should be called for
* commandline argument(s) prior to this method to set up flags to display help etc.
*
* @return {@code true} when execution should be terminated.
* @since 20.0
*/
protected boolean runLauncherAction() {
boolean printDefaultHelp = help || ((helpExpert || helpInternal) && kindAndCategory.isEmpty() && !helpVM);
OptionCategory hc = getHelpCategory();
if (printDefaultHelp) {
printDefaultHelp(hc);
}
maybePrintAdditionalHelp(hc);
if (helpVM) {
if (nativeAccess == null) {
printJvmHelp();
} else {
nativeAccess.printNativeHelp();
}
}
return printAllOtherHelpCategories(printDefaultHelp);
}
Prints default help text. Prints options, starting with tool specific options. Launcher
implementations can override to provide launcher-specific intro / summary.
Params: - printCategory – options category to print.
Since: 20.0
/**
* Prints default help text. Prints options, starting with tool specific options. Launcher
* implementations can override to provide launcher-specific intro / summary.
*
* @param printCategory options category to print.
* @since 20.0
*/
protected void printDefaultHelp(OptionCategory printCategory) {
final VMType defaultVMType = SHELL_SCRIPT_LAUNCHER ? VMType.JVM : this.getDefaultVMType();
printHelp(printCategory);
out.println();
out.println("Runtime options:");
setOptionIndent(45);
if (canPolyglot()) {
launcherOption("--polyglot", "Run with all other guest languages accessible.");
}
if (!SHELL_SCRIPT_LAUNCHER) {
launcherOption("--native", "Run using the native launcher with limited access to Java libraries" + (defaultVMType == VMType.Native ? " (default)" : "") + ".");
}
if (!isStandalone()) {
launcherOption("--jvm", "Run on the Java Virtual Machine with access to Java libraries" + (defaultVMType == VMType.JVM ? " (default)" : "") + ".");
}
// @formatter:off
launcherOption("--vm.[option]", "Pass options to the host VM. To see available options, use '--help:vm'.");
launcherOption("--log.file=<String>", "Redirect guest languages logging into a given file.");
launcherOption("--log.[logger].level=<String>", "Set language log level to OFF, SEVERE, WARNING, INFO, CONFIG, FINE, FINER, FINEST or ALL.");
launcherOption("--help", "Print this help message.");
launcherOption("--help:vm", "Print options for the host VM.");
// @formatter:on
}
Instructs that information about other help categories should be printed.
Params: - kind – category kind name
- option – the option to print the category
Since: 20.0
/**
* Instructs that information about other help categories should be printed.
*
* @param kind category kind name
* @param option the option to print the category
* @since 20.0
*/
protected void printOtherHelpCategory(String kind, String option) {
kindAndCategory.add(kind);
kindAndCategory.add(option);
}
private boolean printAllOtherHelpCategories(boolean printHelp) {
boolean print = printHelp || helpVM || !kindAndCategory.isEmpty();
if (!print) {
return false;
}
out.println();
for (Iterator<String> it = kindAndCategory.iterator(); it.hasNext();) {
String kind = it.next();
String opt = it.next();
printOtherHelpCategories0(kind, opt);
}
out.println("See http://www.graalvm.org for more information.");
return true;
}
private void printOtherHelpCategories0(String kind, String option) {
if (helpExpert || helpInternal) {
out.println("Use '" + option + "' to list user " + kind + " options.");
}
if (!helpExpert) {
out.println("Use '" + option + " --help:expert' to list expert " + kind + " options.");
}
if (!helpInternal) {
out.println("Use '" + option + " --help:internal' to list internal " + kind + " options.");
}
}
static String optionsTitle(String kind, OptionCategory optionCategory) {
String category;
switch (optionCategory) {
case USER:
category = "User ";
break;
case EXPERT:
category = "Expert ";
break;
case INTERNAL:
category = "Internal ";
break;
default:
category = "";
break;
}
return category + kind + " options:";
}
Parses otherwise unrecognized options. Terminates the application if an option is not among
the generic launcher / VM ones.
Params: - defaultOptionPrefix – (language) prefix for the options
- polyglotOptions – options being built for the polyglot launcher
- unrecognizedArgs – arguments (options) to evaluate
Since: 20.0
/**
* Parses otherwise unrecognized options. Terminates the application if an option is not among
* the generic launcher / VM ones.
*
* @param defaultOptionPrefix (language) prefix for the options
* @param polyglotOptions options being built for the polyglot launcher
* @param unrecognizedArgs arguments (options) to evaluate
* @since 20.0
*/
protected final void parseUnrecognizedOptions(String defaultOptionPrefix, Map<String, String> polyglotOptions, List<String> unrecognizedArgs) {
boolean experimentalOptions = false;
// First, check if --experimental-options is passed
for (String arg : unrecognizedArgs) {
switch (arg) {
case "--experimental-options":
case "--experimental-options=true":
experimentalOptions = true;
break;
case "--experimental-options=false":
experimentalOptions = false;
break;
}
}
// Parse the arguments, now that we know whether experimental options are allowed
for (String arg : unrecognizedArgs) {
if (!parseCommonOption(defaultOptionPrefix, polyglotOptions, experimentalOptions, arg)) {
parsePolyglotOption(defaultOptionPrefix, polyglotOptions, experimentalOptions, arg);
}
}
}
Parses an option, returning success. The method is called to parse `arg` option from the commandline, not recognized by the application. The method may contribute to the `polyglotOptions` (in/out parameter, modifiable) to alter polyglot behaviour. If the option is recognized, the method must return true
. Params: - defaultOptionPrefix – default prefix for the option names, derived from the launching
application.
- polyglotOptions – options for polyglot engine
- experimentalOptions – true, if experimental options are explicitly allowed
- arg – argument to parse
Returns: true, if the option was recognized. Since: 20.0
/**
* Parses an option, returning success. The method is called to parse `arg` option from the
* commandline, not recognized by the application. The method may contribute to the
* `polyglotOptions` (in/out parameter, modifiable) to alter polyglot behaviour. If the option
* is recognized, the method must return {@code true}.
*
* @param defaultOptionPrefix default prefix for the option names, derived from the launching
* application.
* @param polyglotOptions options for polyglot engine
* @param experimentalOptions true, if experimental options are explicitly allowed
* @param arg argument to parse
* @return true, if the option was recognized.
* @since 20.0
*/
protected boolean parseCommonOption(String defaultOptionPrefix, Map<String, String> polyglotOptions, boolean experimentalOptions, String arg) {
switch (arg) {
case "--help":
help = true;
break;
case "--help:debug":
warn("--help:debug is deprecated, use --help:internal instead.");
helpInternal = true;
break;
case "--help:internal":
helpInternal = true;
break;
case "--help:expert":
helpExpert = true;
break;
case "--help:vm":
helpVM = true;
break;
case "--experimental-options":
case "--experimental-options=true":
case "--experimental-options=false":
// Ignore, these were already parsed before
break;
default:
return false;
}
return true;
}
Parses polyglot options and already-processed --jvm/--native/--vm.* options. For any other argument, it will abort(). For parameter description see parseCommonOption(String, Map<String,String>, boolean, String)
. /**
* Parses polyglot options and already-processed --jvm/--native/--vm.* options. For any other
* argument, it will abort(). For parameter description see
* {@link #parseCommonOption(java.lang.String, java.util.Map, boolean, java.lang.String)}.
*/
void parsePolyglotOption(String defaultOptionPrefix, Map<String, String> polyglotOptions, boolean experimentalOptions, String arg) {
if (arg.equals("--jvm")) {
if (isAOT()) {
throw abort("should not reach here: jvm option failed to switch to JVM");
}
return;
} else if (arg.equals("--native")) {
if (!isAOT()) {
throw abort("native options are not supported on the JVM");
}
return;
} else if (arg.startsWith("--vm.") && arg.length() > "--vm.".length()) {
/*
* Ignore those, they should already be applied by now. They can come from 2 source:
* from the Bash/Batch launchers, but those launchers remove all such arguments. Or from
* a native launcher which switched from native to jvm, in which case we reuse all
* original arguments for correctness and already applied the --vm options by passing
* them to the JVM directly.
*/
return;
}
// getLanguageId() or null?
if (arg.length() <= 2 || !arg.startsWith("--")) {
throw abortUnrecognizedArgument(arg);
}
int eqIdx = arg.indexOf('=');
String key;
String value;
if (eqIdx < 0) {
key = arg.substring(2);
value = null;
} else {
key = arg.substring(2, eqIdx);
value = arg.substring(eqIdx + 1);
}
if (value == null) {
value = "true";
}
int index = key.indexOf('.');
String group = key;
if (index >= 0) {
group = group.substring(0, index);
}
if ("log".equals(group)) {
if (key.endsWith(".level")) {
try {
Level.parse(value);
polyglotOptions.put(key, value);
} catch (IllegalArgumentException e) {
throw abort(String.format("Invalid log level %s specified. %s'", arg, e.getMessage()));
}
return;
} else if (key.equals("log.file")) {
logFile = Paths.get(value);
return;
}
}
OptionDescriptor descriptor = findOptionDescriptor(group, key);
if (descriptor == null) {
if (defaultOptionPrefix != null) {
descriptor = findOptionDescriptor(defaultOptionPrefix, defaultOptionPrefix + "." + key);
}
if (descriptor == null) {
throw abortUnrecognizedArgument(arg);
}
}
try {
descriptor.getKey().getType().convert(value);
} catch (IllegalArgumentException e) {
throw abort(String.format("Invalid argument %s specified. %s'", arg, e.getMessage()));
}
if (descriptor.isDeprecated()) {
String messageFormat = "Option '%s' is deprecated and might be removed from future versions.";
String deprecationMessage = descriptor.getDeprecationMessage();
String message;
if (deprecationMessage != null) {
message = String.format(messageFormat + "%n%s", descriptor.getName(), deprecationMessage);
} else {
message = String.format(messageFormat, descriptor.getName());
}
warn(message);
}
if (!experimentalOptions && descriptor.getStability() == OptionStability.EXPERIMENTAL) {
throw abort(String.format("Option '%s' is experimental and must be enabled via '--experimental-options'%n" +
"Do not use experimental options in production environments.", arg));
}
// use the full name of the found descriptor
polyglotOptions.put(descriptor.getName(), value);
}
private Set<String> collectAllArguments() {
Set<String> options = new LinkedHashSet<>();
collectArguments(options);
if (canPolyglot()) {
options.add("--polyglot");
}
if (!isStandalone()) {
options.add("--jvm");
}
options.add("--native");
options.add("--help");
options.add("--help:expert");
options.add("--help:internal");
options.add("--help:vm");
return options;
}
Returns the set of options that fuzzy match a given option name.
/**
* Returns the set of options that fuzzy match a given option name.
*/
private static List<String> fuzzyMatch(Set<String> arguments, String argument, float threshold) {
List<String> matches = new ArrayList<>();
for (String arg : arguments) {
float score = stringSimilarity(arg, argument);
if (score >= threshold) {
matches.add(arg);
}
}
return matches;
}
Compute string similarity based on Dice's coefficient.
Ported from str_similar() in globals.cpp.
/**
* Compute string similarity based on Dice's coefficient.
*
* Ported from str_similar() in globals.cpp.
*/
private static float stringSimilarity(String str1, String str2) {
int hit = 0;
for (int i = 0; i < str1.length() - 1; ++i) {
for (int j = 0; j < str2.length() - 1; ++j) {
if ((str1.charAt(i) == str2.charAt(j)) && (str1.charAt(i + 1) == str2.charAt(j + 1))) {
++hit;
break;
}
}
}
return 2.0f * hit / (str1.length() + str2.length());
}
Prints a line for a launcher option. Uses indentation set by setOptionIndent
to align option's description. If option name is too long, description is printed on the next line, indented. Params: - option – option name, including dash(es)
- description – description
Since: 20.0
/**
* Prints a line for a launcher option. Uses indentation set by {@link #setOptionIndent} to
* align option's description. If option name is too long, description is printed on the next
* line, indented.
*
* @param option option name, including dash(es)
* @param description description
* @since 20.0
*/
protected void launcherOption(String option, String description) {
printOption(option, description, 2, optionIndent);
}
private static String spaces(int length) {
return new String(new char[length]).replace('\0', ' ');
}
private static String wrap(String s) {
final int width = 120;
StringBuilder sb = new StringBuilder(s);
int cursor = 0;
while (cursor + width < sb.length()) {
int i = sb.lastIndexOf(" ", cursor + width);
if (i == -1 || i < cursor) {
i = sb.indexOf(" ", cursor + width);
}
if (i != -1) {
sb.replace(i, i + 1, System.lineSeparator());
cursor = i;
} else {
break;
}
}
return sb.toString();
}
private void printOption(String option, String description, int indentStart, int optionWidth) {
String indent = spaces(indentStart);
String desc = wrap(description != null ? description : "");
String nl = System.lineSeparator();
String[] descLines = desc.split(nl);
if (option.length() >= optionWidth && description != null) {
out.println(indent + option + nl + indent + spaces(optionWidth) + descLines[0]);
} else {
out.println(indent + option + spaces(optionWidth - option.length()) + descLines[0]);
}
for (int i = 1; i < descLines.length; i++) {
out.println(indent + spaces(optionWidth) + descLines[i]);
}
}
void printOption(PrintableOption option) {
printOption(option, 2);
}
void printOption(PrintableOption option, int indentation) {
printOption(option.option, option.description, indentation, optionIndent);
}
static final class PrintableOption implements Comparable<PrintableOption> {
final String option;
final String description;
protected PrintableOption(String option, String description) {
this.option = option;
this.description = description;
}
@Override
public int compareTo(PrintableOption o) {
return this.option.compareTo(o.option);
}
}
void printOptions(List<PrintableOption> options, String title, int indentation) {
Collections.sort(options);
out.println(title);
for (PrintableOption option : options) {
printOption(option, indentation);
}
}
protected enum OS {
Darwin,
Linux,
Solaris,
Windows;
private static OS findCurrent() {
final String name = System.getProperty("os.name");
if (name.equals("Linux")) {
return Linux;
}
if (name.equals("SunOS")) {
return Solaris;
}
if (name.equals("Mac OS X") || name.equals("Darwin")) {
return Darwin;
}
if (name.startsWith("Windows")) {
return Windows;
}
throw new IllegalArgumentException("unknown OS: " + name);
}
private static final OS current = findCurrent();
public static OS getCurrent() {
return current;
}
}
Prints a single line to the output stream, terminated with newline.
Params: - l – line text.
Since: 20.0
/**
* Prints a single line to the output stream, terminated with newline.
*
* @param l line text.
* @since 20.0
*/
protected final void println(String l) {
out.println(l);
}
Prints sequence of lines to the output stream. Each argument will be printed as a whole line,
terminated by a newline.
Params: - lines – lines
Since: 20.0
/**
* Prints sequence of lines to the output stream. Each argument will be printed as a whole line,
* terminated by a newline.
*
* @param lines lines
* @since 20.0
*/
protected final void println(String... lines) {
for (String l : lines) {
out.println(l);
}
}
private void printJvmHelp() {
println("JVM options:");
String classpathHelp = "Manage the classpath for Java libraries that you can access from guest languages ('" + File.pathSeparator + "' separated list)";
launcherOption("--vm.classpath=<path>[" + File.pathSeparator + "path...]", classpathHelp);
launcherOption("--vm.cp=<path>[" + File.pathSeparator + "path...]", classpathHelp);
launcherOption("--vm.D<name>=<value>", "Set a system property");
launcherOption("--vm.esa", "Enable system assertions");
launcherOption("--vm.ea[:<packagename>...|:<classname>]", "Enable assertions with specified granularity");
launcherOption("--vm.agentlib:<libname>[=<options>]", "Load native agent library <libname>");
launcherOption("--vm.agentpath:<pathname>[=<options>]", "Load native agent library by full pathname");
launcherOption("--vm.javaagent:<jarpath>[=<options>]", "Load Java programming language agent");
launcherOption("--vm.Xbootclasspath/a:<path>[" + File.pathSeparator + "path...]", "Append classpath entries to the JVM's boot classpath ('" + File.pathSeparator + "' separated list)");
launcherOption("--vm.Xmx<size>", "Set maximum Java heap size");
launcherOption("--vm.Xms<size>", "Set initial Java heap size");
launcherOption("--vm.Xss<size>", "Set java thread stack size");
}
private void printBasicNativeHelp() {
launcherOption("--vm.D<property>=<value>", "Sets a system property");
/* The default values are *copied* from com.oracle.svm.core.genscavenge.HeapPolicy */
launcherOption("--vm.Xmn<value>", "Sets the maximum size of the young generation, in bytes. Default: 256MB.");
launcherOption("--vm.Xmx<value>", "Sets the maximum size of the heap, in bytes. Default: MaximumHeapSizePercent * physical memory.");
launcherOption("--vm.Xms<value>", "Sets the minimum size of the heap, in bytes. Default: 2 * maximum young generation size.");
launcherOption("--vm.Xss<value>", "Sets the size of each thread stack, in bytes. Default: OS-dependent.");
}
private static final String CLASSPATH = System.getProperty("org.graalvm.launcher.classpath");
@SuppressWarnings("unused")
@Deprecated
protected final void maybeNativeExec(List<String> args, boolean isPolyglotLauncher, Map<String, String> polyglotOptions) {
maybeNativeExec(args, args, isPolyglotLauncher);
}
Possibly re-executes the launcher when JVM or polyglot mode is requested; call only if isAOT()
is true. If the result is to run native, then it applies VM options on the current process. The method parses the unrecognizedArgs
for --jvm/--native/--polyglot flags and --vm.* options. If JVM mode is requested, it execs a Java process configured with supported JVM parameters and system properties over this process - in this case, the method does not return (except errors). Params: - originalArgs – the original arguments from main(), unmodified.
- unrecognizedArgs – a subset of
originalArgs
that was not recognized by AbstractLanguageLauncher.preprocessArguments(List<String>, Map<String,String>)
. - isPolyglotLauncher – whether this is the
PolyglotLauncher
(bin/polyglot)
Since: 20.0
/**
* Possibly re-executes the launcher when JVM or polyglot mode is requested; call only if
* {@link #isAOT()} is true. If the result is to run native, then it applies VM options on the
* current process.
*
* The method parses the {@code unrecognizedArgs} for --jvm/--native/--polyglot flags and --vm.*
* options. If JVM mode is requested, it execs a Java process configured with supported JVM
* parameters and system properties over this process - in this case, the method does not return
* (except errors).
*
* @param originalArgs the original arguments from main(), unmodified.
* @param unrecognizedArgs a subset of {@code originalArgs} that was not recognized by
* {@link AbstractLanguageLauncher#preprocessArguments(List, Map)}.
* @param isPolyglotLauncher whether this is the {@link PolyglotLauncher} (bin/polyglot)
* @since 20.0
*/
protected final void maybeNativeExec(List<String> originalArgs, List<String> unrecognizedArgs, boolean isPolyglotLauncher) {
if (!IS_AOT) {
return;
}
maybeExec(originalArgs, unrecognizedArgs, isPolyglotLauncher, getDefaultVMType());
}
void maybeExec(List<String> originalArgs, List<String> unrecognizedArgs, boolean isPolyglotLauncher, VMType defaultVmType) {
assert isAOT();
VMType vmType = null;
boolean polyglot = false;
List<String> jvmArgs = new ArrayList<>();
List<String> applicationArgs = new ArrayList<>(originalArgs);
Iterator<String> iterator = unrecognizedArgs.iterator();
List<String> vmOptions = new ArrayList<>();
while (iterator.hasNext()) {
String arg = iterator.next();
if (arg.equals("--jvm")) {
if (vmType == VMType.Native) {
throw abort("'--jvm' and '--native' options can not be used together.");
}
if (isStandalone()) {
throw abort("'--jvm' is only supported when this launcher is part of a GraalVM.");
}
vmType = VMType.JVM;
iterator.remove();
} else if (arg.equals("--native")) {
if (vmType == VMType.JVM) {
throw abort("'--jvm' and '--native' options can not be used together.");
}
vmType = VMType.Native;
iterator.remove();
} else if (arg.startsWith("--vm.") && arg.length() > "--vm.".length()) {
String vmArg = arg.substring("--vm.".length());
if (vmArg.equals("classpath")) {
throw abort("'--vm.classpath' argument must be of the form '--vm.classpath=<classpath>', not two separate arguments");
}
if (vmArg.equals("cp")) {
throw abort("'--vm.cp' argument must be of the form '--vm.cp=<classpath>', not two separate arguments");
}
if (vmArg.startsWith("classpath=") || vmArg.startsWith("cp=")) {
int eqIndex = vmArg.indexOf('=');
jvmArgs.add('-' + vmArg.substring(0, eqIndex));
jvmArgs.add(vmArg.substring(eqIndex + 1));
} else {
vmOptions.add(vmArg);
}
iterator.remove();
} else if (arg.equals("--polyglot")) {
polyglot = true;
}
}
boolean isDefaultVMType = false;
if (vmType == null) {
vmType = defaultVmType;
isDefaultVMType = true;
}
if (vmType == VMType.JVM) {
for (String vmOption : vmOptions) {
jvmArgs.add('-' + vmOption);
}
if (!isPolyglotLauncher && polyglot) {
applicationArgs.add(0, "--polyglot");
}
assert !isStandalone();
executeJVM(nativeAccess == null ? System.getProperty("java.class.path") : nativeAccess.getClasspath(jvmArgs), jvmArgs, applicationArgs, Collections.emptyMap());
} else {
assert vmType == VMType.Native;
for (String vmOption : vmOptions) {
nativeAccess.setNativeOption(vmOption);
}
/*
* All options are processed, now we can run the startup hooks that can depend on the
* option values.
*/
VMRuntime.initialize();
if (!isPolyglotLauncher && polyglot) {
assert jvmArgs.isEmpty();
if (isStandalone()) {
throw abort("--polyglot option is only supported when this launcher is part of a GraalVM.");
}
executePolyglot(applicationArgs, Collections.emptyMap(), !isDefaultVMType);
}
}
}
@SuppressWarnings("unused")
@Deprecated
protected void executeJVM(String classpath, List<String> jvmArgs, List<String> remainingArgs, Map<String, String> polyglotOptions) {
executeJVM(classpath, jvmArgs, remainingArgs);
}
Called if a JVM has to be started instead of AOT binary. The method is only called in AOT
mode. Subclasses may override to apply different options or launch mechanism
Params: - classpath – class path to be used with the JVM
- jvmArgs – arguments for the VM
- remainingArgs – main arguments
/**
* Called if a JVM has to be started instead of AOT binary. The method is only called in AOT
* mode. Subclasses may override to apply different options or launch mechanism
*
* @param classpath class path to be used with the JVM
* @param jvmArgs arguments for the VM
* @param remainingArgs main arguments
*/
protected void executeJVM(String classpath, List<String> jvmArgs, List<String> remainingArgs) {
nativeAccess.execJVM(classpath, jvmArgs, remainingArgs);
}
@SuppressWarnings("unused")
@Deprecated
protected void executePolyglot(List<String> mainArgs, Map<String, String> polyglotOptions, boolean forceNative) {
executePolyglot(mainArgs, forceNative);
}
Called to execute the bin/polyglot launcher with the supplied options. Subclasses may
eventually override and implement in a different way.
Params: - mainArgs – program arguments
/**
* Called to execute the bin/polyglot launcher with the supplied options. Subclasses may
* eventually override and implement in a different way.
*
* @param mainArgs program arguments
*/
protected void executePolyglot(List<String> mainArgs, boolean forceNative) {
nativeAccess.executePolyglot(mainArgs, forceNative);
}
class Native {
// execve() to JVM/polyglot from native if needed.
// Only parses --jvm/--native to find the VMType and --vm.* to pass/set the VM options.
private WeakReference<OptionDescriptors> compilerOptionDescriptors;
private WeakReference<OptionDescriptors> vmOptionDescriptors;
private OptionDescriptors getCompilerOptions() {
OptionDescriptors descriptors = null;
if (compilerOptionDescriptors != null) {
descriptors = compilerOptionDescriptors.get();
}
if (descriptors == null) {
descriptors = RuntimeOptions.getOptions(EnumSet.of(OptionClass.Compiler));
compilerOptionDescriptors = new WeakReference<>(descriptors);
}
return descriptors;
}
private OptionDescriptors getVMOptions() {
OptionDescriptors descriptors = null;
if (vmOptionDescriptors != null) {
descriptors = vmOptionDescriptors.get();
}
if (descriptors == null) {
descriptors = RuntimeOptions.getOptions(EnumSet.of(OptionClass.VM));
vmOptionDescriptors = new WeakReference<>(descriptors);
}
return descriptors;
}
private void setNativeOption(String arg) {
if (arg.startsWith("Dgraal.")) {
setGraalStyleRuntimeOption(arg.substring("Dgraal.".length()));
} else if (arg.startsWith("D")) {
setSystemProperty(arg.substring("D".length()));
} else if (arg.startsWith("XX:")) {
setRuntimeOption(arg.substring("XX:".length()));
} else if (arg.startsWith("X")) {
if (isXOption(arg)) {
setXOption(arg.substring("X".length()));
} else {
throw abort("Unrecognized vm option: '--vm." + arg + "'. Some VM options may be only supported in --jvm mode.");
}
} else {
throw abort("Unrecognized vm option: '--vm." + arg + "'. Such arguments should start with '--vm.D', '--vm.XX:', or '--vm.X'");
}
}
private void setGraalStyleRuntimeOption(String arg) {
if (arg.startsWith("+") || arg.startsWith("-")) {
throw abort("Dgraal option must use <name>=<value> format, not +/- prefix");
}
int eqIdx = arg.indexOf('=');
String key;
String value;
if (eqIdx < 0) {
key = arg;
value = "";
} else {
key = arg.substring(0, eqIdx);
value = arg.substring(eqIdx + 1);
}
OptionDescriptor descriptor = getCompilerOptions().get(key);
if (descriptor == null) {
descriptor = getVMOptions().get(key);
if (descriptor != null) {
if (isBooleanOption(descriptor)) {
warn("VM options such as '%s' should be set with '--vm.XX:\u00b1%<s'.%n" +
"Support for setting them with '--vm.Dgraal.%<s=<value>' is deprecated and will be removed.%n", key);
} else {
warn("VM options such as '%s' should be set with '--vm.XX:%<s=<value>'.%n" +
"Support for setting them with '--vm.Dgraal.%<s=<value>' is deprecated and will be removed.%n", key);
}
}
}
if (descriptor == null) {
throw unknownOption(key);
}
try {
RuntimeOptions.set(key, descriptor.getKey().getType().convert(value));
} catch (IllegalArgumentException iae) {
throw abort("Invalid argument: '--vm." + arg + "': " + iae.getMessage());
}
}
public void setSystemProperty(String arg) {
int eqIdx = arg.indexOf('=');
String key;
String value;
if (eqIdx < 0) {
key = arg;
value = "";
} else {
key = arg.substring(0, eqIdx);
value = arg.substring(eqIdx + 1);
}
System.setProperty(key, value);
}
public void setRuntimeOption(String arg) {
int eqIdx = arg.indexOf('=');
String key;
Object value;
if (arg.startsWith("+") || arg.startsWith("-")) {
key = arg.substring(1);
if (eqIdx >= 0) {
throw abort("Invalid argument: '--vm." + arg + "': Use either +/- or =, but not both");
}
OptionDescriptor descriptor = getVMOptionDescriptor(key);
if (!isBooleanOption(descriptor)) {
throw abort("Invalid argument: " + key + " is not a boolean option, set it with --vm.XX:" + key + "=<value>.");
}
value = arg.startsWith("+");
} else if (eqIdx > 0) {
key = arg.substring(0, eqIdx);
OptionDescriptor descriptor = getVMOptionDescriptor(key);
if (isBooleanOption(descriptor)) {
throw abort("Boolean option '" + key + "' must be set with +/- prefix, not <name>=<value> format.");
}
try {
value = descriptor.getKey().getType().convert(arg.substring(eqIdx + 1));
} catch (IllegalArgumentException iae) {
throw abort("Invalid argument: '--vm." + arg + "': " + iae.getMessage());
}
} else {
throw abort("Invalid argument: '--vm." + arg + "'. Prefix boolean options with + or -, suffix other options with <name>=<value>");
}
RuntimeOptions.set(key, value);
}
private OptionDescriptor getVMOptionDescriptor(String key) {
OptionDescriptor descriptor = getVMOptions().get(key);
if (descriptor == null) {
descriptor = getCompilerOptions().get(key);
if (descriptor != null) {
warn("compiler options such as '%s' should be set with '--vm.Dgraal.%<s=<value>'.%n" +
"Support for setting them with '--vm.XX:...' is deprecated and will be removed.%n", key);
}
}
if (descriptor == null) {
throw unknownOption(key);
}
return descriptor;
}
/* Is an option that starts with an 'X' one of the recognized X options? */
private boolean isXOption(String arg) {
return (arg.startsWith("Xmn") || arg.startsWith("Xms") || arg.startsWith("Xmx") || arg.startsWith("Xss"));
}
/* Set a `-X` option, given something like "mx2g". */
private void setXOption(String arg) {
try {
RuntimeOptions.set(arg, null);
} catch (RuntimeException re) {
throw abort("Invalid argument: '--vm.X" + arg + "' does not specify a valid number.");
}
}
private boolean isBooleanOption(OptionDescriptor descriptor) {
return descriptor.getKey().getType().equals(OptionType.defaultType(Boolean.class));
}
private AbortException unknownOption(String key) {
throw abort("Unknown native option: " + key + ". Use --help:vm to list available options.");
}
private void printNativeHelp() {
System.out.println("Native VM options:");
SortedMap<String, OptionDescriptor> sortedOptions = new TreeMap<>();
for (OptionDescriptor descriptor : getVMOptions()) {
if (!descriptor.isDeprecated()) {
sortedOptions.put(descriptor.getName(), descriptor);
}
}
for (Entry<String, OptionDescriptor> entry : sortedOptions.entrySet()) {
OptionDescriptor descriptor = entry.getValue();
String helpMsg = descriptor.getHelp();
if (isBooleanOption(descriptor)) {
Boolean val = (Boolean) descriptor.getKey().getDefaultValue();
if (helpMsg.length() != 0) {
helpMsg += ' ';
}
if (val == null || !val) {
helpMsg += "Default: - (disabled).";
} else {
helpMsg += "Default: + (enabled).";
}
launcherOption("--vm.XX:\u00b1" + entry.getKey(), helpMsg);
} else {
Object def = descriptor.getKey().getDefaultValue();
if (def instanceof String) {
def = "\"" + def + "\"";
}
launcherOption("--vm.XX:" + entry.getKey() + "=" + def, helpMsg);
}
}
printCompilerOptions();
printBasicNativeHelp();
}
private void printCompilerOptions() {
System.out.println("Compiler options:");
SortedMap<String, OptionDescriptor> sortedOptions = new TreeMap<>();
for (OptionDescriptor descriptor : getCompilerOptions()) {
if (!descriptor.isDeprecated()) {
sortedOptions.put(descriptor.getName(), descriptor);
}
}
for (Entry<String, OptionDescriptor> entry : sortedOptions.entrySet()) {
OptionDescriptor descriptor = entry.getValue();
String helpMsg = descriptor.getHelp();
Object def = descriptor.getKey().getDefaultValue();
if (def instanceof String) {
def = '"' + (String) def + '"';
}
launcherOption("--vm.Dgraal." + entry.getKey() + "=" + def, helpMsg);
}
}
private void executePolyglot(List<String> args, boolean forceNative) {
List<String> command = new ArrayList<>(args.size() + 3);
Path executable = getGraalVMBinaryPath("polyglot");
if (forceNative) {
command.add("--native");
}
command.add("--use-launcher");
command.add(getMainClass());
command.addAll(args);
exec(executable, command);
}
private void execJVM(String classpath, List<String> jvmArgs, List<String> args) {
// TODO use String[] for command to avoid a copy later
List<String> command = new ArrayList<>(jvmArgs.size() + args.size() + 4);
Path executable = getGraalVMBinaryPath("java");
if (classpath != null) {
command.add("-classpath");
command.add(classpath);
}
command.addAll(jvmArgs);
command.add(getMainClass());
command.addAll(args);
exec(executable, command);
}
private String getClasspath(List<String> jvmArgs) {
assert isAOT();
assert CLASSPATH != null;
StringBuilder sb = new StringBuilder();
if (!CLASSPATH.isEmpty()) {
Path graalVMHome = getGraalVMHome();
if (graalVMHome == null) {
throw abort("Can not resolve classpath: could not get GraalVM home");
}
for (String entry : CLASSPATH.split(File.pathSeparator)) {
Path resolved = graalVMHome.resolve(entry);
if (isVerbose() && !Files.exists(resolved)) {
warn("%s does not exist", resolved);
}
sb.append(resolved);
sb.append(File.pathSeparatorChar);
}
}
String classpathFromArgs = null;
Iterator<String> iterator = jvmArgs.iterator();
while (iterator.hasNext()) {
String jvmArg = iterator.next();
if (jvmArg.equals("-cp") || jvmArg.equals("-classpath")) {
if (iterator.hasNext()) {
iterator.remove();
classpathFromArgs = iterator.next();
iterator.remove();
// no break, pick the last one
}
}
if (jvmArg.startsWith("-Djava.class.path=")) {
iterator.remove();
classpathFromArgs = jvmArg.substring("-Djava.class.path=".length());
}
}
if (classpathFromArgs != null) {
sb.append(classpathFromArgs);
sb.append(File.pathSeparatorChar);
}
if (sb.length() == 0) {
return null;
}
return sb.substring(0, sb.length() - 1);
}
private void exec(Path executable, List<String> command) {
assert isAOT();
if (isVerbose()) {
StringBuilder sb = formatExec(executable, command);
err.print(sb.toString());
}
String[] argv = new String[command.size() + 1];
int i = 0;
Path filename = executable.getFileName();
if (filename == null) {
throw abort(String.format("Cannot determine execute filename from path %s", filename));
}
argv[i++] = filename.toString();
for (String arg : command) {
argv[i++] = arg;
}
ProcessProperties.exec(executable, argv);
}
private StringBuilder formatExec(Path executable, List<String> command) {
StringBuilder sb = new StringBuilder("exec: ");
sb.append(executable);
for (String arg : command) {
sb.append(' ');
sb.append(ShellQuotes.quote(arg));
}
sb.append(System.lineSeparator());
return sb;
}
}
private static final class ShellQuotes {
private static final BitSet safeChars;
static {
safeChars = new BitSet();
safeChars.set('a', 'z' + 1);
safeChars.set('A', 'Z' + 1);
safeChars.set('+', ':' + 1); // +,-./0..9:
safeChars.set('@');
safeChars.set('%');
safeChars.set('_');
safeChars.set('=');
}
private static String quote(String str) {
if (str.isEmpty()) {
return "''";
}
for (int i = 0; i < str.length(); i++) {
if (!safeChars.get(str.charAt(i))) {
return "'" + str.replace("'", "'\"'\"'") + "'";
}
}
return str;
}
}
Creates a new log file. The method uses a supplemental lock file to determine the file is
still opened for output; in that case, it creates a different file, named `path'1, `path`2,
... until it finds a free name. Files not locked (actively written to) are overwritten.
Params: - path – the desired output for log
Throws: - IOException – in case of I/O error opening the file
Returns: the OutputStream for logging Since: 20.0
/**
* Creates a new log file. The method uses a supplemental lock file to determine the file is
* still opened for output; in that case, it creates a different file, named `path'1, `path`2,
* ... until it finds a free name. Files not locked (actively written to) are overwritten.
*
* @param path the desired output for log
* @return the OutputStream for logging
* @throws IOException in case of I/O error opening the file
* @since 20.0
*/
protected static OutputStream newLogStream(Path path) throws IOException {
Path usedPath = path;
Path lockFile = null;
FileChannel lockFileChannel = null;
for (int unique = 0;; unique++) {
StringBuilder lockFileNameBuilder = new StringBuilder();
lockFileNameBuilder.append(path.toString());
if (unique > 0) {
lockFileNameBuilder.append(unique);
usedPath = Paths.get(lockFileNameBuilder.toString());
}
lockFileNameBuilder.append(".lck");
lockFile = Paths.get(lockFileNameBuilder.toString());
Map.Entry<FileChannel, Boolean> openResult = openChannel(lockFile);
if (openResult != null) {
lockFileChannel = openResult.getKey();
if (lock(lockFileChannel, openResult.getValue())) {
break;
} else {
// Close and try next name
lockFileChannel.close();
}
}
}
assert lockFile != null && lockFileChannel != null;
boolean success = false;
try {
OutputStream stream = new LockableOutputStream(
new BufferedOutputStream(Files.newOutputStream(usedPath, WRITE, CREATE, APPEND)),
lockFile,
lockFileChannel);
success = true;
return stream;
} finally {
if (!success) {
LockableOutputStream.unlock(lockFile, lockFileChannel);
}
}
}
private static Map.Entry<FileChannel, Boolean> openChannel(Path path) throws IOException {
FileChannel channel = null;
for (int retries = 0; channel == null && retries < 2; retries++) {
try {
channel = FileChannel.open(path, CREATE_NEW, WRITE);
return new AbstractMap.SimpleImmutableEntry<>(channel, true);
} catch (FileAlreadyExistsException faee) {
// Maybe a FS race showing a zombie file, try to reuse it
if (Files.isRegularFile(path, LinkOption.NOFOLLOW_LINKS) && isParentWritable(path)) {
try {
channel = FileChannel.open(path, WRITE, APPEND);
return new AbstractMap.SimpleImmutableEntry<>(channel, false);
} catch (NoSuchFileException x) {
// FS Race, next try we should be able to create with CREATE_NEW
} catch (IOException x) {
return null;
}
} else {
return null;
}
}
}
return null;
}
private static boolean isParentWritable(Path path) {
Path parentPath = path.getParent();
if (parentPath == null && !path.isAbsolute()) {
parentPath = path.toAbsolutePath().getParent();
}
return parentPath != null && Files.isWritable(parentPath);
}
private static boolean lock(FileChannel lockFileChannel, boolean newFile) {
boolean available = false;
try {
available = lockFileChannel.tryLock() != null;
} catch (OverlappingFileLockException ofle) {
// VM already holds lock continue with available set to false
} catch (IOException ioe) {
// Locking not supported by OS
available = newFile;
}
return available;
}
private static final class LockableOutputStream extends OutputStream {
private final OutputStream delegate;
private final Path lockFile;
private final FileChannel lockFileChannel;
LockableOutputStream(OutputStream delegate, Path lockFile, FileChannel lockFileChannel) {
this.delegate = delegate;
this.lockFile = lockFile;
this.lockFileChannel = lockFileChannel;
}
@Override
public void write(int b) throws IOException {
delegate.write(b);
}
@Override
public void write(byte[] b) throws IOException {
delegate.write(b);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
delegate.write(b, off, len);
}
@Override
public void flush() throws IOException {
delegate.flush();
}
@Override
public void close() throws IOException {
try {
delegate.close();
} finally {
unlock(lockFile, lockFileChannel);
}
}
private static void unlock(Path lockFile, FileChannel lockFileChannel) {
try {
lockFileChannel.close();
} catch (IOException ioe) {
// Error while closing the channel, ignore.
}
try {
Files.delete(lockFile);
} catch (IOException ioe) {
// Error while deleting the lock file, ignore.
}
}
}
}