8201274: Launch Single-File Source-Code Programs
Reviewed-by: mcimadamore, jlahoda, ksrini, mchung, ihse, alanb
This commit is contained in:
parent
628aec8c75
commit
fe24730ed9
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.
|
||||
# Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
#
|
||||
# This code is free software; you can redistribute it and/or modify it
|
||||
@ -32,6 +32,7 @@ $(eval $(call SetupCompileProperties,COMPILE_PROPERTIES, \
|
||||
$(JAVAC_VERSION)))
|
||||
|
||||
$(eval $(call SetupParseProperties,PARSE_PROPERTIES, \
|
||||
com/sun/tools/javac/resources/compiler.properties))
|
||||
com/sun/tools/javac/resources/compiler.properties \
|
||||
com/sun/tools/javac/resources/launcher.properties))
|
||||
|
||||
all: $(COMPILE_PROPERTIES) $(PARSE_PROPERTIES)
|
||||
|
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
|
||||
# Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
#
|
||||
# This code is free software; you can redistribute it and/or modify it
|
||||
@ -38,7 +38,8 @@ module.names = java.compiler \
|
||||
jdk.jshell
|
||||
|
||||
langtools.resource.includes = \
|
||||
com/sun/tools/javac/resources/compiler.properties
|
||||
com/sun/tools/javac/resources/compiler.properties \
|
||||
com/sun/tools/javac/resources/launcher.properties
|
||||
|
||||
# Version info -- override as needed
|
||||
jdk.version = 9
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -502,12 +502,13 @@ public final class LauncherHelper {
|
||||
}
|
||||
|
||||
// From src/share/bin/java.c:
|
||||
// enum LaunchMode { LM_UNKNOWN = 0, LM_CLASS, LM_JAR, LM_MODULE }
|
||||
// enum LaunchMode { LM_UNKNOWN = 0, LM_CLASS, LM_JAR, LM_MODULE, LM_SOURCE }
|
||||
|
||||
private static final int LM_UNKNOWN = 0;
|
||||
private static final int LM_CLASS = 1;
|
||||
private static final int LM_JAR = 2;
|
||||
private static final int LM_MODULE = 3;
|
||||
private static final int LM_SOURCE = 4;
|
||||
|
||||
static void abort(Throwable t, String msgKey, Object... args) {
|
||||
if (msgKey != null) {
|
||||
@ -538,13 +539,21 @@ public final class LauncherHelper {
|
||||
*
|
||||
* @return the application's main class
|
||||
*/
|
||||
@SuppressWarnings("fallthrough")
|
||||
public static Class<?> checkAndLoadMain(boolean printToStderr,
|
||||
int mode,
|
||||
String what) {
|
||||
initOutput(printToStderr);
|
||||
|
||||
Class<?> mainClass = (mode == LM_MODULE) ? loadModuleMainClass(what)
|
||||
: loadMainClass(mode, what);
|
||||
Class<?> mainClass = null;
|
||||
switch (mode) {
|
||||
case LM_MODULE: case LM_SOURCE:
|
||||
mainClass = loadModuleMainClass(what);
|
||||
break;
|
||||
default:
|
||||
mainClass = loadMainClass(mode, what);
|
||||
break;
|
||||
}
|
||||
|
||||
// record the real main class for UI purposes
|
||||
// neither method above can return null, they will abort()
|
||||
|
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
|
||||
# Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
#
|
||||
# This code is free software; you can redistribute it and/or modify it
|
||||
@ -25,13 +25,17 @@
|
||||
|
||||
# Translators please note do not translate the options themselves
|
||||
java.launcher.opt.header = Usage: {0} [options] <mainclass> [args...]\n\
|
||||
\ (to execute a class)\n or {0} [options] -jar <jarfile> [args...]\n\
|
||||
\ (to execute a class)\n\
|
||||
\ or {0} [options] -jar <jarfile> [args...]\n\
|
||||
\ (to execute a jar file)\n\
|
||||
\ or {0} [options] -m <module>[/<mainclass>] [args...]\n\
|
||||
\ {0} [options] --module <module>[/<mainclass>] [args...]\n\
|
||||
\ (to execute the main class in a module)\n\n\
|
||||
\ Arguments following the main class, -jar <jarfile>, -m or --module\n\
|
||||
\ <module>/<mainclass> are passed as the arguments to main class.\n\n\
|
||||
\ (to execute the main class in a module)\n\
|
||||
\ or {0} [options] <sourcefile> [args]\n\
|
||||
\ (to execute a single source-file program)\n\n\
|
||||
\ Arguments following the main class, source file, -jar <jarfile>,\n\
|
||||
\ -m or --module <module>/<mainclass> are passed as the arguments to\n\
|
||||
\ main class.\n\n\
|
||||
\ where options include:\n\n
|
||||
|
||||
java.launcher.opt.vmselect =\ {0}\t to select the "{1}" VM\n
|
||||
@ -114,7 +118,7 @@ java.launcher.opt.footer = \
|
||||
\ -disable-@files\n\
|
||||
\ prevent further argument file expansion\n\
|
||||
\ --enable-preview\n\
|
||||
\ allow classes to depend on preview features of this release
|
||||
\ allow classes to depend on preview features of this release\n\
|
||||
\To specify an argument for a long option, you can use --<name>=<value> or\n\
|
||||
\--<name> <value>.\n
|
||||
|
||||
@ -176,7 +180,9 @@ java.launcher.X.usage=\n\
|
||||
\ --patch-module <module>=<file>({0}<file>)*\n\
|
||||
\ override or augment a module with classes and resources\n\
|
||||
\ in JAR files or directories.\n\
|
||||
\ --disable-@files disable further argument file expansion\n\n\
|
||||
\ --disable-@files disable further argument file expansion\n\
|
||||
\ --source <version>\n\
|
||||
\ set the version of the source in source-file mode.\n\n\
|
||||
These extra options are subject to change without notice.\n
|
||||
|
||||
# Translators please note do not translate the options themselves
|
||||
|
@ -183,7 +183,7 @@ main(int argc, char **argv)
|
||||
}
|
||||
// Iterate the rest of command line
|
||||
for (i = 1; i < argc; i++) {
|
||||
JLI_List argsInFile = JLI_PreprocessArg(argv[i]);
|
||||
JLI_List argsInFile = JLI_PreprocessArg(argv[i], JNI_TRUE);
|
||||
if (NULL == argsInFile) {
|
||||
JLI_List_add(args, JLI_StringDup(argv[i]));
|
||||
} else {
|
||||
|
@ -79,6 +79,11 @@ static size_t argsCount = 1;
|
||||
static jboolean stopExpansion = JNI_FALSE;
|
||||
static jboolean relaunch = JNI_FALSE;
|
||||
|
||||
/*
|
||||
* Prototypes for internal functions.
|
||||
*/
|
||||
static jboolean expand(JLI_List args, const char *str, const char *var_name);
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
JLI_InitArgProcessing(jboolean hasJavaArgs, jboolean disableArgFile) {
|
||||
// No expansion for relaunch
|
||||
@ -376,9 +381,22 @@ static JLI_List expandArgFile(const char *arg) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
/*
|
||||
* expand a string into a list of words separated by whitespace.
|
||||
*/
|
||||
static JLI_List expandArg(const char *arg) {
|
||||
JLI_List rv;
|
||||
|
||||
/* arbitrarily pick 8, seems to be a reasonable number of arguments */
|
||||
rv = JLI_List_new(8);
|
||||
|
||||
expand(rv, arg, NULL);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
JNIEXPORT JLI_List JNICALL
|
||||
JLI_PreprocessArg(const char *arg)
|
||||
{
|
||||
JLI_PreprocessArg(const char *arg, jboolean expandSourceOpt) {
|
||||
JLI_List rv;
|
||||
|
||||
if (firstAppArgIndex > 0) {
|
||||
@ -392,6 +410,12 @@ JLI_PreprocessArg(const char *arg)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (expandSourceOpt
|
||||
&& JLI_StrCCmp(arg, "--source") == 0
|
||||
&& JLI_StrChr(arg, ' ') != NULL) {
|
||||
return expandArg(arg);
|
||||
}
|
||||
|
||||
if (arg[0] != '@') {
|
||||
checkArg(arg);
|
||||
return NULL;
|
||||
@ -435,9 +459,6 @@ int isTerminalOpt(char *arg) {
|
||||
JNIEXPORT jboolean JNICALL
|
||||
JLI_AddArgsFromEnvVar(JLI_List args, const char *var_name) {
|
||||
char *env = getenv(var_name);
|
||||
char *p, *arg;
|
||||
char quote;
|
||||
JLI_List argsInFile;
|
||||
|
||||
if (firstAppArgIndex == 0) {
|
||||
// Not 'java', return
|
||||
@ -453,44 +474,64 @@ JLI_AddArgsFromEnvVar(JLI_List args, const char *var_name) {
|
||||
}
|
||||
|
||||
JLI_ReportMessage(ARG_INFO_ENVVAR, var_name, env);
|
||||
return expand(args, env, var_name);
|
||||
}
|
||||
|
||||
/*
|
||||
* Expand a string into a list of args.
|
||||
* If the string is the result of looking up an environment variable,
|
||||
* var_name should be set to the name of that environment variable,
|
||||
* for use if needed in error messages.
|
||||
*/
|
||||
|
||||
static jboolean expand(JLI_List args, const char *str, const char *var_name) {
|
||||
jboolean inEnvVar = (var_name != NULL);
|
||||
|
||||
char *p, *arg;
|
||||
char quote;
|
||||
JLI_List argsInFile;
|
||||
|
||||
// This is retained until the process terminates as it is saved as the args
|
||||
p = JLI_MemAlloc(JLI_StrLen(env) + 1);
|
||||
while (*env != '\0') {
|
||||
while (*env != '\0' && isspace(*env)) {
|
||||
env++;
|
||||
p = JLI_MemAlloc(JLI_StrLen(str) + 1);
|
||||
while (*str != '\0') {
|
||||
while (*str != '\0' && isspace(*str)) {
|
||||
str++;
|
||||
}
|
||||
|
||||
// Trailing space
|
||||
if (*env == '\0') {
|
||||
if (*str == '\0') {
|
||||
break;
|
||||
}
|
||||
|
||||
arg = p;
|
||||
while (*env != '\0' && !isspace(*env)) {
|
||||
if (*env == '"' || *env == '\'') {
|
||||
quote = *env++;
|
||||
while (*env != quote && *env != '\0') {
|
||||
*p++ = *env++;
|
||||
while (*str != '\0' && !isspace(*str)) {
|
||||
if (inEnvVar && (*str == '"' || *str == '\'')) {
|
||||
quote = *str++;
|
||||
while (*str != quote && *str != '\0') {
|
||||
*p++ = *str++;
|
||||
}
|
||||
|
||||
if (*env == '\0') {
|
||||
if (*str == '\0') {
|
||||
JLI_ReportMessage(ARG_ERROR8, var_name);
|
||||
exit(1);
|
||||
}
|
||||
env++;
|
||||
str++;
|
||||
} else {
|
||||
*p++ = *env++;
|
||||
*p++ = *str++;
|
||||
}
|
||||
}
|
||||
|
||||
*p++ = '\0';
|
||||
|
||||
argsInFile = JLI_PreprocessArg(arg);
|
||||
argsInFile = JLI_PreprocessArg(arg, JNI_FALSE);
|
||||
|
||||
if (NULL == argsInFile) {
|
||||
if (isTerminalOpt(arg)) {
|
||||
JLI_ReportMessage(ARG_ERROR9, arg, var_name);
|
||||
if (inEnvVar) {
|
||||
JLI_ReportMessage(ARG_ERROR9, arg, var_name);
|
||||
} else {
|
||||
JLI_ReportMessage(ARG_ERROR15, arg);
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
JLI_List_add(args, arg);
|
||||
@ -501,7 +542,11 @@ JLI_AddArgsFromEnvVar(JLI_List args, const char *var_name) {
|
||||
for (idx = 0; idx < cnt; idx++) {
|
||||
arg = argsInFile->elements[idx];
|
||||
if (isTerminalOpt(arg)) {
|
||||
JLI_ReportMessage(ARG_ERROR10, arg, argFile, var_name);
|
||||
if (inEnvVar) {
|
||||
JLI_ReportMessage(ARG_ERROR10, arg, argFile, var_name);
|
||||
} else {
|
||||
JLI_ReportMessage(ARG_ERROR16, arg, argFile);
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
JLI_List_add(args, arg);
|
||||
@ -517,11 +562,15 @@ JLI_AddArgsFromEnvVar(JLI_List args, const char *var_name) {
|
||||
* caught now.
|
||||
*/
|
||||
if (firstAppArgIndex != NOT_FOUND) {
|
||||
JLI_ReportMessage(ARG_ERROR11, var_name);
|
||||
if (inEnvVar) {
|
||||
JLI_ReportMessage(ARG_ERROR11, var_name);
|
||||
} else {
|
||||
JLI_ReportMessage(ARG_ERROR17);
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
|
||||
assert (*env == '\0' || isspace(*env));
|
||||
assert (*str == '\0' || isspace(*str));
|
||||
}
|
||||
|
||||
return JNI_TRUE;
|
||||
@ -642,7 +691,7 @@ int main(int argc, char** argv) {
|
||||
|
||||
if (argc > 1) {
|
||||
for (i = 0; i < argc; i++) {
|
||||
JLI_List tokens = JLI_PreprocessArg(argv[i]);
|
||||
JLI_List tokens = JLI_PreprocessArg(argv[i], JNI_FALSE);
|
||||
if (NULL != tokens) {
|
||||
for (j = 0; j < tokens->size; j++) {
|
||||
printf("Token[%lu]: <%s>\n", (unsigned long) j, tokens->elements[j]);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2005, 2014, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2005, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -51,6 +51,11 @@
|
||||
#define ARG_ERROR10 "Error: Option %s in %s is not allowed in environment variable %s"
|
||||
#define ARG_ERROR11 "Error: Cannot specify main class in environment variable %s"
|
||||
#define ARG_ERROR12 "Error: %s requires module name"
|
||||
#define ARG_ERROR13 "Error: %s requires source version"
|
||||
#define ARG_ERROR14 "Error: Option %s is not allowed with --source"
|
||||
#define ARG_ERROR15 "Error: Option %s is not allowed in this context"
|
||||
#define ARG_ERROR16 "Error: Option %s in %s is not allowed in this context"
|
||||
#define ARG_ERROR17 "Error: Cannot specify main class in this context"
|
||||
|
||||
#define JVM_ERROR1 "Error: Could not create the Java Virtual Machine.\n" GEN_ERROR
|
||||
#define JVM_ERROR2 "Error: Could not detach main thread.\n" JNI_ERROR
|
||||
|
@ -172,6 +172,9 @@ static int KnownVMIndex(const char* name);
|
||||
static void FreeKnownVMs();
|
||||
static jboolean IsWildCardEnabled();
|
||||
|
||||
|
||||
#define SOURCE_LAUNCHER_MAIN_ENTRY "jdk.compiler/com.sun.tools.javac.launcher.Main"
|
||||
|
||||
/*
|
||||
* This reports error. VM will not be created and no usage is printed.
|
||||
*/
|
||||
@ -214,7 +217,7 @@ static jlong initialHeapSize = 0; /* inital heap size */
|
||||
* Entry point.
|
||||
*/
|
||||
JNIEXPORT int JNICALL
|
||||
JLI_Launch(int argc, char ** argv, /* main argc, argc */
|
||||
JLI_Launch(int argc, char ** argv, /* main argc, argv */
|
||||
int jargc, const char** jargv, /* java args */
|
||||
int appclassc, const char** appclassv, /* app classpath */
|
||||
const char* fullversion, /* full version defined */
|
||||
@ -317,8 +320,7 @@ JLI_Launch(int argc, char ** argv, /* main argc, argc */
|
||||
/* Parse command line options; if the return value of
|
||||
* ParseArguments is false, the program should exit.
|
||||
*/
|
||||
if (!ParseArguments(&argc, &argv, &mode, &what, &ret, jrepath))
|
||||
{
|
||||
if (!ParseArguments(&argc, &argv, &mode, &what, &ret, jrepath)) {
|
||||
return(ret);
|
||||
}
|
||||
|
||||
@ -585,7 +587,8 @@ IsLauncherOption(const char* name) {
|
||||
return IsClassPathOption(name) ||
|
||||
IsLauncherMainOption(name) ||
|
||||
JLI_StrCmp(name, "--describe-module") == 0 ||
|
||||
JLI_StrCmp(name, "-d") == 0;
|
||||
JLI_StrCmp(name, "-d") == 0 ||
|
||||
JLI_StrCmp(name, "--source") == 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -626,6 +629,29 @@ IsWhiteSpaceOption(const char* name) {
|
||||
IsLauncherOption(name);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if it is OK to set the mode.
|
||||
* If the mode was previously set, and should not be changed,
|
||||
* a fatal error is reported.
|
||||
*/
|
||||
static int
|
||||
checkMode(int mode, int newMode, const char *arg) {
|
||||
if (mode == LM_SOURCE) {
|
||||
JLI_ReportErrorMessage(ARG_ERROR14, arg);
|
||||
exit(1);
|
||||
}
|
||||
return newMode;
|
||||
}
|
||||
|
||||
/*
|
||||
* Test if an arg identifies a source file.
|
||||
*/
|
||||
jboolean
|
||||
IsSourceFile(const char *arg) {
|
||||
struct stat st;
|
||||
return (JLI_HasSuffix(arg, ".java") && stat(arg, &st) == 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks the command line options to find which JVM type was
|
||||
* specified. If no command line option was given for the JVM type,
|
||||
@ -1230,7 +1256,8 @@ GetOpt(int *pargc, char ***pargv, char **poption, char **pvalue) {
|
||||
value = equals+1;
|
||||
if (JLI_StrCCmp(arg, "--describe-module=") == 0 ||
|
||||
JLI_StrCCmp(arg, "--module=") == 0 ||
|
||||
JLI_StrCCmp(arg, "--class-path=") == 0) {
|
||||
JLI_StrCCmp(arg, "--class-path=") == 0||
|
||||
JLI_StrCCmp(arg, "--source=") == 0) {
|
||||
kind = LAUNCHER_OPTION_WITH_ARGUMENT;
|
||||
} else {
|
||||
kind = VM_LONG_OPTION;
|
||||
@ -1274,17 +1301,28 @@ ParseArguments(int *pargc, char ***pargv,
|
||||
*/
|
||||
if (JLI_StrCmp(arg, "-jar") == 0) {
|
||||
ARG_CHECK(argc, ARG_ERROR2, arg);
|
||||
mode = LM_JAR;
|
||||
mode = checkMode(mode, LM_JAR, arg);
|
||||
} else if (JLI_StrCmp(arg, "--module") == 0 ||
|
||||
JLI_StrCCmp(arg, "--module=") == 0 ||
|
||||
JLI_StrCmp(arg, "-m") == 0) {
|
||||
REPORT_ERROR (has_arg, ARG_ERROR5, arg);
|
||||
SetMainModule(value);
|
||||
mode = LM_MODULE;
|
||||
mode = checkMode(mode, LM_MODULE, arg);
|
||||
if (has_arg) {
|
||||
*pwhat = value;
|
||||
break;
|
||||
}
|
||||
} else if (JLI_StrCmp(arg, "--source") == 0 ||
|
||||
JLI_StrCCmp(arg, "--source=") == 0) {
|
||||
REPORT_ERROR (has_arg, ARG_ERROR13, arg);
|
||||
mode = LM_SOURCE;
|
||||
if (has_arg) {
|
||||
const char *prop = "-Djdk.internal.javac.source=";
|
||||
size_t size = JLI_StrLen(prop) + JLI_StrLen(value) + 1;
|
||||
char *propValue = (char *)JLI_MemAlloc(size);
|
||||
JLI_Snprintf(propValue, size, "%s%s", prop, value);
|
||||
AddOption(propValue, NULL);
|
||||
}
|
||||
} else if (JLI_StrCmp(arg, "--class-path") == 0 ||
|
||||
JLI_StrCCmp(arg, "--class-path=") == 0 ||
|
||||
JLI_StrCmp(arg, "-classpath") == 0 ||
|
||||
@ -1435,12 +1473,25 @@ ParseArguments(int *pargc, char ***pargv,
|
||||
if (!_have_classpath) {
|
||||
SetClassPath(".");
|
||||
}
|
||||
mode = LM_CLASS;
|
||||
mode = IsSourceFile(arg) ? LM_SOURCE : LM_CLASS;
|
||||
} else if (mode == LM_CLASS && IsSourceFile(arg)) {
|
||||
/* override LM_CLASS mode if given a source file */
|
||||
mode = LM_SOURCE;
|
||||
}
|
||||
|
||||
if (argc >= 0) {
|
||||
*pargc = argc;
|
||||
*pargv = argv;
|
||||
if (mode == LM_SOURCE) {
|
||||
AddOption("--add-modules=ALL-DEFAULT", NULL);
|
||||
*pwhat = SOURCE_LAUNCHER_MAIN_ENTRY;
|
||||
// adjust (argc, argv) so that the name of the source file
|
||||
// is included in the args passed to the source launcher
|
||||
// main entry class
|
||||
*pargc = argc + 1;
|
||||
*pargv = argv - 1;
|
||||
} else {
|
||||
if (argc >= 0) {
|
||||
*pargc = argc;
|
||||
*pargv = argv;
|
||||
}
|
||||
}
|
||||
|
||||
*pmode = mode;
|
||||
|
@ -230,11 +230,12 @@ enum LaunchMode { // cf. sun.launcher.LauncherHelper
|
||||
LM_UNKNOWN = 0,
|
||||
LM_CLASS,
|
||||
LM_JAR,
|
||||
LM_MODULE
|
||||
LM_MODULE,
|
||||
LM_SOURCE
|
||||
};
|
||||
|
||||
static const char *launchModeNames[]
|
||||
= { "Unknown", "Main class", "JAR file", "Module" };
|
||||
= { "Unknown", "Main class", "JAR file", "Module", "Source" };
|
||||
|
||||
typedef struct {
|
||||
int argc;
|
||||
|
@ -84,6 +84,16 @@ JLI_MemFree(void *ptr)
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
jboolean
|
||||
JLI_HasSuffix(const char *s1, const char *s2)
|
||||
{
|
||||
char *p = JLI_StrRChr(s1, '.');
|
||||
if (p == NULL || *p == '\0') {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
return (JLI_StrCaseCmp(p, s2) == 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* debug helpers we use
|
||||
*/
|
||||
|
@ -51,7 +51,8 @@ JLI_StringDup(const char *s1);
|
||||
JNIEXPORT void JNICALL
|
||||
JLI_MemFree(void *ptr);
|
||||
|
||||
int JLI_StrCCmp(const char *s1, const char* s2);
|
||||
int JLI_StrCCmp(const char *s1, const char *s2);
|
||||
jboolean JLI_HasSuffix(const char *s1, const char *s2);
|
||||
|
||||
typedef struct {
|
||||
char *arg;
|
||||
@ -158,7 +159,7 @@ JNIEXPORT void JNICALL
|
||||
JLI_InitArgProcessing(jboolean hasJavaArgs, jboolean disableArgFile);
|
||||
|
||||
JNIEXPORT JLI_List JNICALL
|
||||
JLI_PreprocessArg(const char *arg);
|
||||
JLI_PreprocessArg(const char *arg, jboolean expandSourceOpt);
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
JLI_AddArgsFromEnvVar(JLI_List args, const char *var_name);
|
||||
|
@ -246,7 +246,7 @@ JLI_CmdToArgs(char* cmdline) {
|
||||
// iterate through rest of command line
|
||||
while (src != NULL) {
|
||||
src = next_arg(src, arg, &wildcard);
|
||||
argsInFile = JLI_PreprocessArg(arg);
|
||||
argsInFile = JLI_PreprocessArg(arg, JNI_TRUE);
|
||||
if (argsInFile != NULL) {
|
||||
// resize to accommodate another Arg
|
||||
cnt = argsInFile->size;
|
||||
|
@ -0,0 +1,550 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package com.sun.tools.javac.launcher;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import javax.lang.model.SourceVersion;
|
||||
import javax.lang.model.element.NestingKind;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.tools.FileObject;
|
||||
import javax.tools.ForwardingJavaFileManager;
|
||||
import javax.tools.JavaFileManager;
|
||||
import javax.tools.JavaFileManager.Location;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.SimpleJavaFileObject;
|
||||
import javax.tools.StandardJavaFileManager;
|
||||
import javax.tools.StandardLocation;
|
||||
|
||||
import com.sun.source.util.JavacTask;
|
||||
import com.sun.source.util.TaskEvent;
|
||||
import com.sun.source.util.TaskListener;
|
||||
import com.sun.tools.javac.api.JavacTool;
|
||||
import com.sun.tools.javac.code.Source;
|
||||
import com.sun.tools.javac.resources.LauncherProperties.Errors;
|
||||
import com.sun.tools.javac.util.JCDiagnostic.Error;
|
||||
|
||||
import jdk.internal.misc.VM;
|
||||
|
||||
import static javax.tools.JavaFileObject.Kind.SOURCE;
|
||||
|
||||
/**
|
||||
* Compiles a source file, and executes the main method it contains.
|
||||
*
|
||||
* <p><b>This is NOT part of any supported API.
|
||||
* If you write code that depends on this, you do so at your own
|
||||
* risk. This code and its internal interfaces are subject to change
|
||||
* or deletion without notice.</b></p>
|
||||
*/
|
||||
public class Main {
|
||||
/**
|
||||
* An exception used to report errors.
|
||||
*/
|
||||
public class Fault extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
Fault(Error error) {
|
||||
super(Main.this.getMessage(error));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles a source file, and executes the main method it contains.
|
||||
*
|
||||
* <p>This is normally invoked from the Java launcher, either when
|
||||
* the {@code --source} option is used, or when the first argument
|
||||
* that is not part of a runtime option ends in {@code .java}.
|
||||
*
|
||||
* <p>The first entry in the {@code args} array is the source file
|
||||
* to be compiled and run; all subsequent entries are passed as
|
||||
* arguments to the main method of the first class found in the file.
|
||||
*
|
||||
* <p>If any problem occurs before executing the main class, it will
|
||||
* be reported to the standard error stream, and the the JVM will be
|
||||
* terminated by calling {@code System.exit} with a non-zero return code.
|
||||
*
|
||||
* @param args the arguments
|
||||
* @throws Throwable if the main method throws an exception
|
||||
*/
|
||||
public static void main(String... args) throws Throwable {
|
||||
try {
|
||||
new Main(System.err).run(VM.getRuntimeArguments(), args);
|
||||
} catch (Fault f) {
|
||||
System.err.println(f.getMessage());
|
||||
System.exit(1);
|
||||
} catch (InvocationTargetException e) {
|
||||
// leave VM to handle the stacktrace, in the standard manner
|
||||
throw e.getTargetException();
|
||||
}
|
||||
}
|
||||
|
||||
/** Stream for reporting errors, such as compilation errors. */
|
||||
private PrintWriter out;
|
||||
|
||||
/**
|
||||
* Creates an instance of this class, providing a stream to which to report
|
||||
* any errors.
|
||||
*
|
||||
* @param out the stream
|
||||
*/
|
||||
public Main(PrintStream out) {
|
||||
this(new PrintWriter(new OutputStreamWriter(out), true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of this class, providing a stream to which to report
|
||||
* any errors.
|
||||
*
|
||||
* @param out the stream
|
||||
*/
|
||||
public Main(PrintWriter out) {
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles a source file, and executes the main method it contains.
|
||||
*
|
||||
* <p>The first entry in the {@code args} array is the source file
|
||||
* to be compiled and run; all subsequent entries are passed as
|
||||
* arguments to the main method of the first class found in the file.
|
||||
*
|
||||
* <p>Options for {@code javac} are obtained by filtering the runtime arguments.
|
||||
*
|
||||
* <p>If the main method throws an exception, it will be propagated in an
|
||||
* {@code InvocationTargetException}. In that case, the stack trace of the
|
||||
* target exception will be truncated such that the main method will be the
|
||||
* last entry on the stack. In other words, the stack frames leading up to the
|
||||
* invocation of the main method will be removed.
|
||||
*
|
||||
* @param runtimeArgs the runtime arguments
|
||||
* @param args the arguments
|
||||
* @throws Fault if a problem is detected before the main method can be executed
|
||||
* @throws InvocationTargetException if the main method throws an exception
|
||||
*/
|
||||
public void run(String[] runtimeArgs, String[] args) throws Fault, InvocationTargetException {
|
||||
Path file = getFile(args);
|
||||
|
||||
Context context = new Context();
|
||||
String mainClassName = compile(file, getJavacOpts(runtimeArgs), context);
|
||||
|
||||
String[] appArgs = Arrays.copyOfRange(args, 1, args.length);
|
||||
execute(mainClassName, appArgs, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path for the filename found in the first of an array of arguments.
|
||||
*
|
||||
* @param args the array
|
||||
* @return the path
|
||||
* @throws Fault if there is a problem determining the path, or if the file does not exist
|
||||
*/
|
||||
private Path getFile(String[] args) throws Fault {
|
||||
if (args.length == 0) {
|
||||
// should not happen when invoked from launcher
|
||||
throw new Fault(Errors.NoArgs);
|
||||
}
|
||||
Path file;
|
||||
try {
|
||||
file = Paths.get(args[0]);
|
||||
} catch (InvalidPathException e) {
|
||||
throw new Fault(Errors.InvalidFilename(args[0]));
|
||||
}
|
||||
if (!Files.exists(file)) {
|
||||
// should not happen when invoked from launcher
|
||||
throw new Fault(Errors.FileNotFound(file));
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a source file, ignoring the first line if it is not a Java source file and
|
||||
* it begins with {@code #!}.
|
||||
*
|
||||
* <p>If it is not a Java source file, and if the first two bytes are {@code #!},
|
||||
* indicating a "magic number" of an executable text file, the rest of the first line
|
||||
* up to but not including the newline is ignored. All characters after the first two are
|
||||
* read in the {@link Charset#defaultCharset default platform encoding}.
|
||||
*
|
||||
* @param file the file
|
||||
* @return a file object containing the content of the file
|
||||
* @throws Fault if an error occurs while reading the file
|
||||
*/
|
||||
private JavaFileObject readFile(Path file) throws Fault {
|
||||
// use a BufferedInputStream to guarantee that we can use mark and reset.
|
||||
try (BufferedInputStream in = new BufferedInputStream(Files.newInputStream(file))) {
|
||||
boolean ignoreFirstLine;
|
||||
if (file.getFileName().toString().endsWith(".java")) {
|
||||
ignoreFirstLine = false;
|
||||
} else {
|
||||
in.mark(2);
|
||||
ignoreFirstLine = (in.read() == '#') && (in.read() == '!');
|
||||
if (!ignoreFirstLine) {
|
||||
in.reset();
|
||||
}
|
||||
}
|
||||
try (BufferedReader r = new BufferedReader(new InputStreamReader(in, Charset.defaultCharset()))) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (ignoreFirstLine) {
|
||||
r.readLine();
|
||||
sb.append("\n"); // preserve line numbers
|
||||
}
|
||||
char[] buf = new char[1024];
|
||||
int n;
|
||||
while ((n = r.read(buf, 0, buf.length)) != -1) {
|
||||
sb.append(buf, 0, n);
|
||||
}
|
||||
return new SimpleJavaFileObject(file.toUri(), SOURCE) {
|
||||
@Override
|
||||
public String getName() {
|
||||
return file.toString();
|
||||
}
|
||||
@Override
|
||||
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
|
||||
return sb;
|
||||
}
|
||||
@Override
|
||||
public boolean isNameCompatible(String simpleName, JavaFileObject.Kind kind) {
|
||||
// reject package-info and module-info; accept other names
|
||||
return (kind == JavaFileObject.Kind.SOURCE)
|
||||
&& SourceVersion.isIdentifier(simpleName);
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JavacSourceLauncher[" + file + "]";
|
||||
}
|
||||
};
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new Fault(Errors.CantReadFile(file, e));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the subset of the runtime arguments that are relevant to {@code javac}.
|
||||
* Generally, the relevant options are those for setting paths and for configuring the
|
||||
* module system.
|
||||
*
|
||||
* @param runtimeArgs the runtime arguments
|
||||
* @return the subset of the runtime arguments
|
||||
**/
|
||||
private List<String> getJavacOpts(String... runtimeArgs) throws Fault {
|
||||
List<String> javacOpts = new ArrayList<>();
|
||||
|
||||
String sourceOpt = System.getProperty("jdk.internal.javac.source");
|
||||
if (sourceOpt != null) {
|
||||
Source source = Source.lookup(sourceOpt);
|
||||
if (source == null) {
|
||||
throw new Fault(Errors.InvalidValueForSource(sourceOpt));
|
||||
}
|
||||
javacOpts.addAll(List.of("--release", sourceOpt));
|
||||
}
|
||||
|
||||
for (int i = 0; i < runtimeArgs.length; i++) {
|
||||
String arg = runtimeArgs[i];
|
||||
String opt = arg, value = null;
|
||||
if (arg.startsWith("--")) {
|
||||
int eq = arg.indexOf('=');
|
||||
if (eq > 0) {
|
||||
opt = arg.substring(0, eq);
|
||||
value = arg.substring(eq + 1);
|
||||
}
|
||||
}
|
||||
switch (opt) {
|
||||
// The following options all expect a value, either in the following
|
||||
// position, or after '=', for options beginning "--".
|
||||
case "--class-path": case "-classpath": case "-cp":
|
||||
case "--module-path": case "-p":
|
||||
case "--add-exports":
|
||||
case "--add-modules":
|
||||
case "--limit-modules":
|
||||
case "--patch-module":
|
||||
case "--upgrade-module-path":
|
||||
if (value == null) {
|
||||
if (i== runtimeArgs.length - 1) {
|
||||
// should not happen when invoked from launcher
|
||||
throw new Fault(Errors.NoValueForOption(opt));
|
||||
}
|
||||
value = runtimeArgs[++i];
|
||||
}
|
||||
if (opt.equals("--add-modules") && value.equals("ALL-DEFAULT")) {
|
||||
// this option is only supported at run time;
|
||||
// it is not required or supported at compile time
|
||||
break;
|
||||
}
|
||||
javacOpts.add(opt);
|
||||
javacOpts.add(value);
|
||||
break;
|
||||
case "--enable-preview":
|
||||
javacOpts.add(opt);
|
||||
if (sourceOpt == null) {
|
||||
throw new Fault(Errors.EnablePreviewRequiresSource);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// ignore all other runtime args
|
||||
}
|
||||
}
|
||||
|
||||
// add implicit options
|
||||
javacOpts.add("-proc:none");
|
||||
|
||||
return javacOpts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles a source file, placing the class files in a map in memory.
|
||||
* Any messages generated during compilation will be written to the stream
|
||||
* provided when this object was created.
|
||||
*
|
||||
* @param file the source file
|
||||
* @param javacOpts compilation options for {@code javac}
|
||||
* @param context the context for the compilation
|
||||
* @return the name of the first class found in the source file
|
||||
* @throws Fault if any compilation errors occur, or if no class was found
|
||||
*/
|
||||
private String compile(Path file, List<String> javacOpts, Context context) throws Fault {
|
||||
JavaFileObject fo = readFile(file);
|
||||
|
||||
JavacTool javaCompiler = JavacTool.create();
|
||||
StandardJavaFileManager stdFileMgr = javaCompiler.getStandardFileManager(null, null, null);
|
||||
try {
|
||||
stdFileMgr.setLocation(StandardLocation.SOURCE_PATH, Collections.emptyList());
|
||||
} catch (IOException e) {
|
||||
throw new java.lang.Error("unexpected exception from file manager", e);
|
||||
}
|
||||
JavaFileManager fm = context.getFileManager(stdFileMgr);
|
||||
JavacTask t = javaCompiler.getTask(out, fm, null, javacOpts, null, List.of(fo));
|
||||
MainClassListener l = new MainClassListener(t);
|
||||
Boolean ok = t.call();
|
||||
if (!ok) {
|
||||
throw new Fault(Errors.CompilationFailed);
|
||||
}
|
||||
if (l.mainClass == null) {
|
||||
throw new Fault(Errors.NoClass);
|
||||
}
|
||||
String mainClassName = l.mainClass.getQualifiedName().toString();
|
||||
return mainClassName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the {@code main} method of a specified class, using a class loader that
|
||||
* will load recently compiled classes from memory.
|
||||
*
|
||||
* @param mainClassName the class to be executed
|
||||
* @param appArgs the arguments for the {@code main} method
|
||||
* @param context the context for the class to be executed
|
||||
* @throws Fault if there is a problem finding or invoking the {@code main} method
|
||||
* @throws InvocationTargetException if the {@code main} method throws an exception
|
||||
*/
|
||||
private void execute(String mainClassName, String[] appArgs, Context context)
|
||||
throws Fault, InvocationTargetException {
|
||||
ClassLoader cl = context.getClassLoader(ClassLoader.getSystemClassLoader());
|
||||
try {
|
||||
Class<?> appClass = Class.forName(mainClassName, true, cl);
|
||||
if (appClass.getClassLoader() != cl) {
|
||||
throw new Fault(Errors.UnexpectedClass(mainClassName));
|
||||
}
|
||||
Method main = appClass.getDeclaredMethod("main", String[].class);
|
||||
int PUBLIC_STATIC = Modifier.PUBLIC | Modifier.STATIC;
|
||||
if ((main.getModifiers() & PUBLIC_STATIC) != PUBLIC_STATIC) {
|
||||
throw new Fault(Errors.MainNotPublicStatic);
|
||||
}
|
||||
if (!main.getReturnType().equals(void.class)) {
|
||||
throw new Fault(Errors.MainNotVoid);
|
||||
}
|
||||
main.setAccessible(true);
|
||||
main.invoke(0, (Object) appArgs);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new Fault(Errors.CantFindClass(mainClassName));
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new Fault(Errors.CantFindMainMethod(mainClassName));
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new Fault(Errors.CantAccessMainMethod(mainClassName));
|
||||
} catch (InvocationTargetException e) {
|
||||
// remove stack frames for source launcher
|
||||
int invocationFrames = e.getStackTrace().length;
|
||||
Throwable target = e.getTargetException();
|
||||
StackTraceElement[] targetTrace = target.getStackTrace();
|
||||
target.setStackTrace(Arrays.copyOfRange(targetTrace, 0, targetTrace.length - invocationFrames));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private static final String bundleName = "com.sun.tools.javac.resources.launcher";
|
||||
private ResourceBundle resourceBundle = null;
|
||||
private String errorPrefix;
|
||||
|
||||
/**
|
||||
* Returns a localized string from a resource bundle.
|
||||
*
|
||||
* @param error the error for which to get the localized text
|
||||
* @return the localized string
|
||||
*/
|
||||
private String getMessage(Error error) {
|
||||
String key = error.key();
|
||||
Object[] args = error.getArgs();
|
||||
try {
|
||||
if (resourceBundle == null) {
|
||||
resourceBundle = ResourceBundle.getBundle(bundleName);
|
||||
errorPrefix = resourceBundle.getString("launcher.error");
|
||||
}
|
||||
String resource = resourceBundle.getString(key);
|
||||
String message = MessageFormat.format(resource, args);
|
||||
return errorPrefix + message;
|
||||
} catch (MissingResourceException e) {
|
||||
return "Cannot access resource; " + key + Arrays.toString(args);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A listener to detect the first class found in a compilation.
|
||||
*/
|
||||
static class MainClassListener implements TaskListener {
|
||||
TypeElement mainClass;
|
||||
|
||||
MainClassListener(JavacTask t) {
|
||||
t.addTaskListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void started(TaskEvent ev) {
|
||||
if (ev.getKind() == TaskEvent.Kind.ANALYZE && mainClass == null) {
|
||||
TypeElement te = ev.getTypeElement();
|
||||
if (te.getNestingKind() == NestingKind.TOP_LEVEL) {
|
||||
mainClass = te;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An object to encapulate the set of in-memory classes, such that
|
||||
* they can be written by a file manager and subsequently used by
|
||||
* a class loader.
|
||||
*/
|
||||
private static class Context {
|
||||
private Map<String, byte[]> inMemoryClasses = new HashMap<>();
|
||||
|
||||
JavaFileManager getFileManager(StandardJavaFileManager delegate) {
|
||||
return new MemoryFileManager(inMemoryClasses, delegate);
|
||||
}
|
||||
|
||||
ClassLoader getClassLoader(ClassLoader parent) {
|
||||
return new MemoryClassLoader(inMemoryClasses, parent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An in-memory file manager.
|
||||
*
|
||||
* <p>Class files (of kind {@link JavaFileObject.Kind#CLASS CLASS} written to
|
||||
* {@link StandardLocation#CLASS_OUTPUT} will be written to an in-memory cache.
|
||||
* All other file manager operations will be delegated to a specified file manager.
|
||||
*/
|
||||
private static class MemoryFileManager extends ForwardingJavaFileManager<JavaFileManager> {
|
||||
private final Map<String, byte[]> map;
|
||||
|
||||
MemoryFileManager(Map<String, byte[]> map, JavaFileManager delegate) {
|
||||
super(delegate);
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaFileObject getJavaFileForOutput(Location location, String className,
|
||||
JavaFileObject.Kind kind, FileObject sibling) throws IOException {
|
||||
if (location == StandardLocation.CLASS_OUTPUT && kind == JavaFileObject.Kind.CLASS) {
|
||||
return createInMemoryClassFile(className);
|
||||
} else {
|
||||
return super.getJavaFileForOutput(location, className, kind, sibling);
|
||||
}
|
||||
}
|
||||
|
||||
private JavaFileObject createInMemoryClassFile(String className) {
|
||||
URI uri = URI.create("memory:///" + className.replace('.', '/') + ".class");
|
||||
return new SimpleJavaFileObject(uri, JavaFileObject.Kind.CLASS) {
|
||||
@Override
|
||||
public OutputStream openOutputStream() {
|
||||
return new ByteArrayOutputStream() {
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
super.close();
|
||||
map.put(className, toByteArray());
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An in-memory classloader, that uses an in-memory cache written by {@link MemoryFileManager}.
|
||||
*
|
||||
* <p>The classloader uses the standard parent-delegation model, just providing
|
||||
* {@code findClass} to find classes in the in-memory cache.
|
||||
*/
|
||||
private static class MemoryClassLoader extends ClassLoader {
|
||||
private final Map<String, byte[]> map;
|
||||
|
||||
MemoryClassLoader(Map<String, byte[]> map, ClassLoader parent) {
|
||||
super(parent);
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> findClass(String name) throws ClassNotFoundException {
|
||||
byte[] bytes = map.get(name);
|
||||
if (bytes == null) {
|
||||
throw new ClassNotFoundException(name);
|
||||
}
|
||||
return defineClass(name, bytes, 0, bytes.length);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,136 @@
|
||||
#
|
||||
# Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
#
|
||||
# This code is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 2 only, as
|
||||
# published by the Free Software Foundation. Oracle designates this
|
||||
# particular file as subject to the "Classpath" exception as provided
|
||||
# by Oracle in the LICENSE file that accompanied this code.
|
||||
#
|
||||
# This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
# version 2 for more details (a copy is included in the LICENSE file that
|
||||
# accompanied this code).
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License version
|
||||
# 2 along with this work; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
# or visit www.oracle.com if you need additional information or have any
|
||||
# questions.
|
||||
#
|
||||
|
||||
# Messages in this file which use "placeholders" for values (e.g. {0}, {1})
|
||||
# are preceded by a stylized comment describing the type of the corresponding
|
||||
# values.
|
||||
# The simple types currently in use are:
|
||||
#
|
||||
# annotation annotation compound
|
||||
# boolean true or false
|
||||
# diagnostic a sub-message; see compiler.misc.*
|
||||
# fragment similar to 'message segment', but with more specific type
|
||||
# modifier a Java modifier; e.g. public, private, protected
|
||||
# file a file URL
|
||||
# file object a file URL - similar to 'file' but typically used for source/class files, hence more specific
|
||||
# flag a Flags.Flag instance
|
||||
# name a name, typically a Java identifier
|
||||
# number an integer
|
||||
# option name the name of a command line option
|
||||
# source version a source version number, such as 1.5, 1.6, 1.7
|
||||
# string a general string
|
||||
# symbol the name of a declared type
|
||||
# symbol kind the kind of a symbol (i.e. method, variable)
|
||||
# kind name an informative description of the kind of a declaration; see compiler.misc.kindname.*
|
||||
# token the name of a non-terminal in source code; see compiler.misc.token.*
|
||||
# type a Java type; e.g. int, X, X<T>
|
||||
# object a Java object (unspecified)
|
||||
# unused the value is not used in this message
|
||||
#
|
||||
# The following compound types are also used:
|
||||
#
|
||||
# collection of X a comma-separated collection of items; e.g. collection of type
|
||||
# list of X a comma-separated list of items; e.g. list of type
|
||||
# set of X a comma-separated set of items; e.g. set of modifier
|
||||
#
|
||||
# These may be composed:
|
||||
#
|
||||
# list of type or message segment
|
||||
#
|
||||
# The following type aliases are supported:
|
||||
#
|
||||
# message segment --> diagnostic or fragment
|
||||
# file name --> file, path or file object
|
||||
#
|
||||
# Custom comments are supported in parenthesis i.e.
|
||||
#
|
||||
# number (classfile major version)
|
||||
#
|
||||
# These comments are used internally in order to generate an enum-like class declaration containing
|
||||
# a method/field for each of the diagnostic keys listed here. Those methods/fields can then be used
|
||||
# by javac code to build diagnostics in a type-safe fashion.
|
||||
#
|
||||
# In addition, these comments are verified by the jtreg test test/tools/javac/diags/MessageInfo,
|
||||
# using info derived from the collected set of examples in test/tools/javac/diags/examples.
|
||||
# MessageInfo can also be run as a standalone utility providing more facilities
|
||||
# for manipulating this file. For more details, see MessageInfo.java.
|
||||
|
||||
## All errors are preceded by this string.
|
||||
launcher.error=\
|
||||
error:\u0020
|
||||
|
||||
launcher.err.no.args=\
|
||||
no filename
|
||||
|
||||
# 0: string
|
||||
launcher.err.invalid.filename=\
|
||||
invalid filename: {0}
|
||||
|
||||
# 0: path
|
||||
launcher.err.file.not.found=\
|
||||
file not found: {0}
|
||||
|
||||
launcher.err.compilation.failed=\
|
||||
compilation failed
|
||||
|
||||
launcher.err.no.class=\
|
||||
no class declared in file
|
||||
|
||||
launcher.err.main.not.public.static=\
|
||||
''main'' method is not declared ''public static''
|
||||
|
||||
launcher.err.main.not.void=\
|
||||
''main'' method is not declared with a return type of ''void''
|
||||
|
||||
# 0: string
|
||||
launcher.err.cant.find.class=\
|
||||
can''t find class: {0}
|
||||
|
||||
# 0: string
|
||||
launcher.err.unexpected.class=\
|
||||
class found on application class path: {0}
|
||||
|
||||
# 0: string
|
||||
launcher.err.cant.find.main.method=\
|
||||
can''t find main(String[]) method in class: {0}
|
||||
|
||||
# 0: string
|
||||
launcher.err.cant.access.main.method=\
|
||||
can''t access main method in class: {0}
|
||||
|
||||
# 0: path, 1: object
|
||||
launcher.err.cant.read.file=\
|
||||
error reading file {0}: {1}
|
||||
|
||||
# 0: string
|
||||
launcher.err.no.value.for.option=\
|
||||
no value given for option: {0}
|
||||
|
||||
# 0: string
|
||||
launcher.err.invalid.value.for.source=\
|
||||
invalid value for --source option: {0}
|
||||
|
||||
launcher.err.enable.preview.requires.source=\
|
||||
--enable-preview must be used with --source
|
315
test/jdk/tools/launcher/SourceMode.java
Normal file
315
test/jdk/tools/launcher/SourceMode.java
Normal file
@ -0,0 +1,315 @@
|
||||
/*
|
||||
* Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8192920
|
||||
* @summary Test source mode
|
||||
* @modules jdk.compiler
|
||||
* @run main SourceMode
|
||||
*/
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class SourceMode extends TestHelper {
|
||||
|
||||
public static void main(String... args) throws Exception {
|
||||
new SourceMode().run(args);
|
||||
}
|
||||
|
||||
// java Simple.java 1 2 3
|
||||
@Test
|
||||
void testSimpleJava() throws IOException {
|
||||
Path file = getSimpleFile("Simple.java", false);
|
||||
TestResult tr = doExec(javaCmd, file.toString(), "1", "2", "3");
|
||||
if (!tr.isOK())
|
||||
error(tr, "Bad exit code: " + tr.exitValue);
|
||||
if (!tr.contains("[1, 2, 3]"))
|
||||
error(tr, "Expected output not found");
|
||||
System.out.println(tr.testOutput);
|
||||
}
|
||||
|
||||
// java --source 10 simple 1 2 3
|
||||
@Test
|
||||
void testSimple() throws IOException {
|
||||
Path file = getSimpleFile("simple", false);
|
||||
TestResult tr = doExec(javaCmd, "--source", "10", file.toString(), "1", "2", "3");
|
||||
if (!tr.isOK())
|
||||
error(tr, "Bad exit code: " + tr.exitValue);
|
||||
if (!tr.contains("[1, 2, 3]"))
|
||||
error(tr, "Expected output not found");
|
||||
System.out.println(tr.testOutput);
|
||||
}
|
||||
|
||||
// execSimple 1 2 3
|
||||
@Test
|
||||
void testExecSimple() throws IOException {
|
||||
if (isWindows) // Will not work without cygwin, pass silently
|
||||
return;
|
||||
Path file = setExecutable(getSimpleFile("execSimple", true));
|
||||
TestResult tr = doExec(file.toAbsolutePath().toString(), "1", "2", "3");
|
||||
if (!tr.isOK())
|
||||
error(tr, "Bad exit code: " + tr.exitValue);
|
||||
if (!tr.contains("[1, 2, 3]"))
|
||||
error(tr, "Expected output not found");
|
||||
System.out.println(tr.testOutput);
|
||||
}
|
||||
|
||||
// java @simpleJava.at (contains Simple.java 1 2 3)
|
||||
@Test
|
||||
void testSimpleJavaAtFile() throws IOException {
|
||||
Path file = getSimpleFile("Simple.java", false);
|
||||
Path atFile = Paths.get("simpleJava.at");
|
||||
createFile(atFile.toFile(), List.of(file + " 1 2 3"));
|
||||
TestResult tr = doExec(javaCmd, "@" + atFile);
|
||||
if (!tr.isOK())
|
||||
error(tr, "Bad exit code: " + tr.exitValue);
|
||||
if (!tr.contains("[1, 2, 3]"))
|
||||
error(tr, "Expected output not found");
|
||||
System.out.println(tr.testOutput);
|
||||
}
|
||||
|
||||
// java @simple.at (contains --source 10 simple 1 2 3)
|
||||
@Test
|
||||
void testSimpleAtFile() throws IOException {
|
||||
Path file = getSimpleFile("simple", false);
|
||||
Path atFile = Paths.get("simple.at");
|
||||
createFile(atFile.toFile(), List.of("--source 10 " + file + " 1 2 3"));
|
||||
TestResult tr = doExec(javaCmd, "@" + atFile);
|
||||
if (!tr.isOK())
|
||||
error(tr, "Bad exit code: " + tr.exitValue);
|
||||
if (!tr.contains("[1, 2, 3]"))
|
||||
error(tr, "Expected output not found");
|
||||
System.out.println(tr.testOutput);
|
||||
}
|
||||
|
||||
// java -cp classes Main.java 1 2 3
|
||||
@Test
|
||||
void testClasspath() throws IOException {
|
||||
Path base = Files.createDirectories(Paths.get("testClasspath"));
|
||||
Path otherJava = base.resolve("Other.java");
|
||||
createFile(otherJava.toFile(), List.of(
|
||||
"public class Other {",
|
||||
" public static String join(String[] args) {",
|
||||
" return String.join(\"-\", args);",
|
||||
" }",
|
||||
"}"
|
||||
));
|
||||
Path classes = Files.createDirectories(base.resolve("classes"));
|
||||
Path mainJava = base.resolve("Main.java");
|
||||
createFile(mainJava.toFile(), List.of(
|
||||
"class Main {",
|
||||
" public static void main(String[] args) {",
|
||||
" System.out.println(Other.join(args));",
|
||||
" }}"
|
||||
));
|
||||
compile("-d", classes.toString(), otherJava.toString());
|
||||
TestResult tr = doExec(javaCmd, "-cp", classes.toString(),
|
||||
mainJava.toString(), "1", "2", "3");
|
||||
if (!tr.isOK())
|
||||
error(tr, "Bad exit code: " + tr.exitValue);
|
||||
if (!tr.contains("1-2-3"))
|
||||
error(tr, "Expected output not found");
|
||||
System.out.println(tr.testOutput);
|
||||
}
|
||||
|
||||
// java --add-exports=... Export.java --help
|
||||
@Test
|
||||
void testAddExports() throws IOException {
|
||||
Path exportJava = Paths.get("Export.java");
|
||||
createFile(exportJava.toFile(), List.of(
|
||||
"public class Export {",
|
||||
" public static void main(String[] args) {",
|
||||
" new com.sun.tools.javac.main.Main(\"demo\").compile(args);",
|
||||
" }",
|
||||
"}"
|
||||
));
|
||||
// verify access fails without --add-exports
|
||||
TestResult tr1 = doExec(javaCmd, exportJava.toString(), "--help");
|
||||
if (tr1.isOK())
|
||||
error(tr1, "Compilation succeeded unexpectedly");
|
||||
// verify access succeeds with --add-exports
|
||||
TestResult tr2 = doExec(javaCmd,
|
||||
"--add-exports", "jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED",
|
||||
exportJava.toString(), "--help");
|
||||
if (!tr2.isOK())
|
||||
error(tr2, "Bad exit code: " + tr2.exitValue);
|
||||
if (!(tr2.contains("demo") && tr2.contains("Usage")))
|
||||
error(tr2, "Expected output not found");
|
||||
}
|
||||
|
||||
// java -cp ... HelloWorld.java (for a class "java" in package "HelloWorld")
|
||||
@Test
|
||||
void testClassNamedJava() throws IOException {
|
||||
Path base = Files.createDirectories(Paths.get("testClassNamedJava"));
|
||||
Path src = Files.createDirectories(base.resolve("src"));
|
||||
Path srcfile = src.resolve("java.java");
|
||||
createFile(srcfile.toFile(), List.of(
|
||||
"package HelloWorld;",
|
||||
"class java {",
|
||||
" public static void main(String... args) {",
|
||||
" System.out.println(HelloWorld.java.class.getName());",
|
||||
" }",
|
||||
"}"
|
||||
));
|
||||
Path classes = base.resolve("classes");
|
||||
compile("-d", classes.toString(), srcfile.toString());
|
||||
TestResult tr =
|
||||
doExec(javaCmd, "-cp", classes.toString(), "HelloWorld.java");
|
||||
if (!tr.isOK())
|
||||
error(tr, "Command failed");
|
||||
if (!tr.contains("HelloWorld.java"))
|
||||
error(tr, "Expected output not found");
|
||||
}
|
||||
|
||||
// java --source
|
||||
@Test
|
||||
void testSourceNoArg() throws IOException {
|
||||
TestResult tr = doExec(javaCmd, "--source");
|
||||
if (tr.isOK())
|
||||
error(tr, "Command succeeded unexpectedly");
|
||||
System.err.println(tr);
|
||||
if (!tr.contains("--source requires source version"))
|
||||
error(tr, "Expected output not found");
|
||||
}
|
||||
|
||||
// java --source 10 -jar simple.jar
|
||||
@Test
|
||||
void testSourceJarConflict() throws IOException {
|
||||
Path base = Files.createDirectories(Paths.get("testSourceJarConflict"));
|
||||
Path file = getSimpleFile("Simple.java", false);
|
||||
Path classes = Files.createDirectories(base.resolve("classes"));
|
||||
compile("-d", classes.toString(), file.toString());
|
||||
Path simpleJar = base.resolve("simple.jar");
|
||||
createJar("cf", simpleJar.toString(), "-C", classes.toString(), ".");
|
||||
TestResult tr =
|
||||
doExec(javaCmd, "--source", "10", "-jar", simpleJar.toString());
|
||||
if (tr.isOK())
|
||||
error(tr, "Command succeeded unexpectedly");
|
||||
if (!tr.contains("Option -jar is not allowed with --source"))
|
||||
error(tr, "Expected output not found");
|
||||
}
|
||||
|
||||
// java --source 10 -m jdk.compiler
|
||||
@Test
|
||||
void testSourceModuleConflict() throws IOException {
|
||||
TestResult tr = doExec(javaCmd, "--source", "10", "-m", "jdk.compiler");
|
||||
if (tr.isOK())
|
||||
error(tr, "Command succeeded unexpectedly");
|
||||
if (!tr.contains("Option -m is not allowed with --source"))
|
||||
error(tr, "Expected output not found");
|
||||
}
|
||||
|
||||
// #!.../java --source 10 -version
|
||||
@Test
|
||||
void testTerminalOptionInShebang() throws IOException {
|
||||
if (isWindows) // Will not work without cygwin, pass silently
|
||||
return;
|
||||
Path base = Files.createDirectories(
|
||||
Paths.get("testTerminalOptionInShebang"));
|
||||
Path bad = base.resolve("bad");
|
||||
createFile(bad.toFile(), List.of(
|
||||
"#!" + javaCmd + " --source 10 -version"));
|
||||
setExecutable(bad);
|
||||
TestResult tr = doExec(bad.toString());
|
||||
if (!tr.contains("Option -version is not allowed in this context"))
|
||||
error(tr, "Expected output not found");
|
||||
}
|
||||
|
||||
// #!.../java --source 10 @bad.at (contains -version)
|
||||
@Test
|
||||
void testTerminalOptionInShebangAtFile() throws IOException {
|
||||
if (isWindows) // Will not work without cygwin, pass silently
|
||||
return;
|
||||
// Use a short directory name, to avoid line length limitations
|
||||
Path base = Files.createDirectories(Paths.get("testBadAtFile"));
|
||||
Path bad_at = base.resolve("bad.at");
|
||||
createFile(bad_at.toFile(), List.of("-version"));
|
||||
Path bad = base.resolve("bad");
|
||||
createFile(bad.toFile(), List.of(
|
||||
"#!" + javaCmd + " --source 10 @" + bad_at));
|
||||
setExecutable(bad);
|
||||
TestResult tr = doExec(bad.toString());
|
||||
System.err.println("JJG JJG " + tr);
|
||||
if (!tr.contains("Option -version in @testBadAtFile/bad.at is "
|
||||
+ "not allowed in this context"))
|
||||
error(tr, "Expected output not found");
|
||||
}
|
||||
|
||||
// #!.../java --source 10 HelloWorld
|
||||
@Test
|
||||
void testMainClassInShebang() throws IOException {
|
||||
if (isWindows) // Will not work without cygwin, pass silently
|
||||
return;
|
||||
Path base = Files.createDirectories(Paths.get("testMainClassInShebang"));
|
||||
Path bad = base.resolve("bad");
|
||||
createFile(bad.toFile(), List.of(
|
||||
"#!" + javaCmd + " --source 10 HelloWorld"));
|
||||
setExecutable(bad);
|
||||
TestResult tr = doExec(bad.toString());
|
||||
if (!tr.contains("Cannot specify main class in this context"))
|
||||
error(tr, "Expected output not found");
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
private Map<String,String> getLauncherDebugEnv() {
|
||||
return Map.of("_JAVA_LAUNCHER_DEBUG", "1");
|
||||
}
|
||||
|
||||
private Path getSimpleFile(String name, boolean shebang) throws IOException {
|
||||
Path file = Paths.get(name);
|
||||
if (!Files.exists(file)) {
|
||||
createFile(file.toFile(), List.of(
|
||||
(shebang ? "#!" + javaCmd + " --source 10" : ""),
|
||||
"public class Simple {",
|
||||
" public static void main(String[] args) {",
|
||||
" System.out.println(java.util.Arrays.toString(args));",
|
||||
" }}"));
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
private Path setExecutable(Path file) throws IOException {
|
||||
Set<PosixFilePermission> perms = Files.getPosixFilePermissions(file);
|
||||
perms.add(PosixFilePermission.OWNER_EXECUTE);
|
||||
Files.setPosixFilePermissions(file, perms);
|
||||
return file;
|
||||
}
|
||||
|
||||
private void error(TestResult tr, String message) {
|
||||
System.err.println(tr);
|
||||
throw new RuntimeException(message);
|
||||
}
|
||||
}
|
@ -118,7 +118,8 @@ public class CheckResourceKeys {
|
||||
void findDeadKeys(Set<String> codeStrings, Set<String> resourceKeys) {
|
||||
String[] prefixes = {
|
||||
"compiler.err.", "compiler.warn.", "compiler.note.", "compiler.misc.",
|
||||
"javac."
|
||||
"javac.",
|
||||
"launcher.err."
|
||||
};
|
||||
for (String rk: resourceKeys) {
|
||||
// some keys are used directly, without a prefix.
|
||||
@ -395,7 +396,7 @@ public class CheckResourceKeys {
|
||||
Set<String> getResourceKeys() {
|
||||
Module jdk_compiler = ModuleLayer.boot().findModule("jdk.compiler").get();
|
||||
Set<String> results = new TreeSet<String>();
|
||||
for (String name : new String[]{"javac", "compiler"}) {
|
||||
for (String name : new String[]{"javac", "compiler", "launcher"}) {
|
||||
ResourceBundle b =
|
||||
ResourceBundle.getBundle("com.sun.tools.javac.resources." + name, jdk_compiler);
|
||||
results.addAll(b.keySet());
|
||||
|
542
test/langtools/tools/javac/launcher/SourceLauncherTest.java
Normal file
542
test/langtools/tools/javac/launcher/SourceLauncherTest.java
Normal file
@ -0,0 +1,542 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8192920
|
||||
* @summary Test source launcher
|
||||
* @library /tools/lib
|
||||
* @modules jdk.compiler/com.sun.tools.javac.api
|
||||
* jdk.compiler/com.sun.tools.javac.launcher
|
||||
* jdk.compiler/com.sun.tools.javac.main
|
||||
* @build toolbox.JavaTask toolbox.JavacTask toolbox.TestRunner toolbox.ToolBox
|
||||
* @run main SourceLauncherTest
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import com.sun.tools.javac.launcher.Main;
|
||||
|
||||
import toolbox.JavaTask;
|
||||
import toolbox.JavacTask;
|
||||
import toolbox.Task;
|
||||
import toolbox.TestRunner;
|
||||
import toolbox.TestRunner;
|
||||
import toolbox.ToolBox;
|
||||
|
||||
public class SourceLauncherTest extends TestRunner {
|
||||
public static void main(String... args) throws Exception {
|
||||
SourceLauncherTest t = new SourceLauncherTest();
|
||||
t.runTests(m -> new Object[] { Paths.get(m.getName()) });
|
||||
}
|
||||
|
||||
SourceLauncherTest() {
|
||||
super(System.err);
|
||||
tb = new ToolBox();
|
||||
System.err.println("version: " + thisVersion);
|
||||
}
|
||||
|
||||
private final ToolBox tb;
|
||||
private static final String thisVersion = System.getProperty("java.specification.version");
|
||||
|
||||
/*
|
||||
* Positive tests.
|
||||
*/
|
||||
|
||||
@Test
|
||||
public void testHelloWorld(Path base) throws IOException {
|
||||
tb.writeJavaFiles(base,
|
||||
"import java.util.Arrays;\n" +
|
||||
"class HelloWorld {\n" +
|
||||
" public static void main(String... args) {\n" +
|
||||
" System.out.println(\"Hello World! \" + Arrays.toString(args));\n" +
|
||||
" }\n" +
|
||||
"}");
|
||||
testSuccess(base.resolve("HelloWorld.java"), "Hello World! [1, 2, 3]\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHelloWorldInPackage(Path base) throws IOException {
|
||||
tb.writeJavaFiles(base,
|
||||
"package hello;\n" +
|
||||
"import java.util.Arrays;\n" +
|
||||
"class World {\n" +
|
||||
" public static void main(String... args) {\n" +
|
||||
" System.out.println(\"Hello World! \" + Arrays.toString(args));\n" +
|
||||
" }\n" +
|
||||
"}");
|
||||
testSuccess(base.resolve("hello").resolve("World.java"), "Hello World! [1, 2, 3]\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHelloWorldWithAux(Path base) throws IOException {
|
||||
tb.writeJavaFiles(base,
|
||||
"import java.util.Arrays;\n" +
|
||||
"class HelloWorld {\n" +
|
||||
" public static void main(String... args) {\n" +
|
||||
" Aux.write(args);\n" +
|
||||
" }\n" +
|
||||
"}\n" +
|
||||
"class Aux {\n" +
|
||||
" static void write(String... args) {\n" +
|
||||
" System.out.println(\"Hello World! \" + Arrays.toString(args));\n" +
|
||||
" }\n" +
|
||||
"}");
|
||||
testSuccess(base.resolve("HelloWorld.java"), "Hello World! [1, 2, 3]\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHelloWorldWithShebang(Path base) throws IOException {
|
||||
tb.writeJavaFiles(base,
|
||||
"#!/usr/bin/java --source 11\n" +
|
||||
"import java.util.Arrays;\n" +
|
||||
"class HelloWorld {\n" +
|
||||
" public static void main(String... args) {\n" +
|
||||
" System.out.println(\"Hello World! \" + Arrays.toString(args));\n" +
|
||||
" }\n" +
|
||||
"}");
|
||||
Files.copy(base.resolve("HelloWorld.java"), base.resolve("HelloWorld"));
|
||||
testSuccess(base.resolve("HelloWorld"), "Hello World! [1, 2, 3]\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoAnnoProcessing(Path base) throws IOException {
|
||||
Path annoSrc = base.resolve("annoSrc");
|
||||
tb.writeJavaFiles(annoSrc,
|
||||
"import java.util.*;\n" +
|
||||
"import javax.annotation.processing.*;\n" +
|
||||
"import javax.lang.model.element.*;\n" +
|
||||
"@SupportedAnnotationTypes(\"*\")\n" +
|
||||
"public class AnnoProc extends AbstractProcessor {\n" +
|
||||
" public boolean process(Set<? extends TypeElement> annos, RoundEnvironment rEnv) {\n" +
|
||||
" throw new Error(\"Annotation processor should not be invoked\");\n" +
|
||||
" }\n" +
|
||||
"}\n");
|
||||
Path annoClasses = Files.createDirectories(base.resolve("classes"));
|
||||
new JavacTask(tb)
|
||||
.outdir(annoClasses)
|
||||
.files(annoSrc.resolve("AnnoProc.java").toString())
|
||||
.run();
|
||||
Path serviceFile = annoClasses.resolve("META-INF").resolve("services")
|
||||
.resolve("javax.annotation.processing.Processor");
|
||||
tb.writeFile(serviceFile, "AnnoProc");
|
||||
|
||||
Path mainSrc = base.resolve("mainSrc");
|
||||
tb.writeJavaFiles(mainSrc,
|
||||
"import java.util.Arrays;\n" +
|
||||
"class HelloWorld {\n" +
|
||||
" public static void main(String... args) {\n" +
|
||||
" System.out.println(\"Hello World! \" + Arrays.toString(args));\n" +
|
||||
" }\n" +
|
||||
"}");
|
||||
|
||||
List<String> javacArgs = List.of("-classpath", annoClasses.toString());
|
||||
List<String> classArgs = List.of("1", "2", "3");
|
||||
String expect = "Hello World! [1, 2, 3]\n";
|
||||
Result r = run(mainSrc.resolve("HelloWorld.java"), javacArgs, classArgs);
|
||||
checkEqual("stdout", r.stdOut, expect);
|
||||
checkEmpty("stderr", r.stdErr);
|
||||
checkNull("exception", r.exception);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnablePreview(Path base) throws IOException {
|
||||
tb.writeJavaFiles(base,
|
||||
"import java.util.Arrays;\n" +
|
||||
"class HelloWorld {\n" +
|
||||
" public static void main(String... args) {\n" +
|
||||
" System.out.println(\"Hello World! \" + Arrays.toString(args));\n" +
|
||||
" }\n" +
|
||||
"}");
|
||||
|
||||
String log = new JavaTask(tb)
|
||||
.vmOptions("--enable-preview", "--source", thisVersion)
|
||||
.className(base.resolve("HelloWorld.java").toString())
|
||||
.classArgs("1", "2", "3")
|
||||
.run(Task.Expect.SUCCESS)
|
||||
.getOutput(Task.OutputKind.STDOUT);
|
||||
checkEqual("stdout", log.trim(), "Hello World! [1, 2, 3]");
|
||||
}
|
||||
|
||||
void testSuccess(Path file, String expect) throws IOException {
|
||||
Result r = run(file, Collections.emptyList(), List.of("1", "2", "3"));
|
||||
checkEqual("stdout", r.stdOut, expect);
|
||||
checkEmpty("stderr", r.stdErr);
|
||||
checkNull("exception", r.exception);
|
||||
}
|
||||
|
||||
/*
|
||||
* Negative tests: such as cannot find or execute main method.
|
||||
*/
|
||||
|
||||
@Test
|
||||
public void testHelloWorldWithShebangJava(Path base) throws IOException {
|
||||
tb.writeJavaFiles(base,
|
||||
"#!/usr/bin/java --source 11\n" +
|
||||
"import java.util.Arrays;\n" +
|
||||
"class HelloWorld {\n" +
|
||||
" public static void main(String... args) {\n" +
|
||||
" System.out.println(\"Hello World! \" + Arrays.toString(args));\n" +
|
||||
" }\n" +
|
||||
"}");
|
||||
Path file = base.resolve("HelloWorld.java");
|
||||
testError(file,
|
||||
file + ":1: error: illegal character: '#'\n" +
|
||||
"#!/usr/bin/java --source 11\n" +
|
||||
"^\n" +
|
||||
file + ":1: error: class, interface, or enum expected\n" +
|
||||
"#!/usr/bin/java --source 11\n" +
|
||||
" ^\n" +
|
||||
"2 errors\n",
|
||||
"error: compilation failed");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoClass(Path base) throws IOException {
|
||||
Files.createDirectories(base);
|
||||
Path file = base.resolve("NoClass.java");
|
||||
Files.write(file, List.of("package p;"));
|
||||
testError(file, "", "error: no class declared in file");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrongClass(Path base) throws IOException {
|
||||
Path src = base.resolve("src");
|
||||
Path file = src.resolve("WrongClass.java");
|
||||
tb.writeJavaFiles(src, "class WrongClass { }");
|
||||
Path classes = Files.createDirectories(base.resolve("classes"));
|
||||
new JavacTask(tb)
|
||||
.outdir(classes)
|
||||
.files(file)
|
||||
.run();
|
||||
String log = new JavaTask(tb)
|
||||
.classpath(classes.toString())
|
||||
.className(file.toString())
|
||||
.run(Task.Expect.FAIL)
|
||||
.getOutput(Task.OutputKind.STDERR);
|
||||
checkEqual("stderr", log.trim(),
|
||||
"error: class found on application class path: WrongClass");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSyntaxErr(Path base) throws IOException {
|
||||
tb.writeJavaFiles(base, "class SyntaxErr {");
|
||||
Path file = base.resolve("SyntaxErr.java");
|
||||
testError(file,
|
||||
file + ":1: error: reached end of file while parsing\n" +
|
||||
"class SyntaxErr {\n" +
|
||||
" ^\n" +
|
||||
"1 error\n",
|
||||
"error: compilation failed");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoSourceOnClassPath(Path base) throws IOException {
|
||||
Path auxSrc = base.resolve("auxSrc");
|
||||
tb.writeJavaFiles(auxSrc,
|
||||
"public class Aux {\n" +
|
||||
" static final String MESSAGE = \"Hello World\";\n" +
|
||||
"}\n");
|
||||
|
||||
Path mainSrc = base.resolve("mainSrc");
|
||||
tb.writeJavaFiles(mainSrc,
|
||||
"import java.util.Arrays;\n" +
|
||||
"class HelloWorld {\n" +
|
||||
" public static void main(String... args) {\n" +
|
||||
" System.out.println(Aux.MESSAGE + Arrays.toString(args));\n" +
|
||||
" }\n" +
|
||||
"}");
|
||||
|
||||
List<String> javacArgs = List.of("-classpath", auxSrc.toString());
|
||||
List<String> classArgs = List.of("1", "2", "3");
|
||||
String expectStdErr =
|
||||
"testNoSourceOnClassPath/mainSrc/HelloWorld.java:4: error: cannot find symbol\n" +
|
||||
" System.out.println(Aux.MESSAGE + Arrays.toString(args));\n" +
|
||||
" ^\n" +
|
||||
" symbol: variable Aux\n" +
|
||||
" location: class HelloWorld\n" +
|
||||
"1 error\n";
|
||||
Result r = run(mainSrc.resolve("HelloWorld.java"), javacArgs, classArgs);
|
||||
checkEmpty("stdout", r.stdOut);
|
||||
checkEqual("stderr", r.stdErr, expectStdErr);
|
||||
checkFault("exception", r.exception, "error: compilation failed");
|
||||
|
||||
}
|
||||
|
||||
// For any source file that is invoked through the OS shebang mechanism, invalid shebang
|
||||
// lines will be caught and handled by the OS, before the launcher is even invoked.
|
||||
// However, if such a file is passed directly to the launcher, perhaps using the --source
|
||||
// option, a well-formed shebang line will be removed but a badly-formed one will be not be
|
||||
// removed and will cause compilation errors.
|
||||
@Test
|
||||
public void testBadShebang(Path base) throws IOException {
|
||||
tb.writeJavaFiles(base,
|
||||
"#/usr/bin/java --source 11\n" +
|
||||
"import java.util.Arrays;\n" +
|
||||
"class HelloWorld {\n" +
|
||||
" public static void main(String... args) {\n" +
|
||||
" System.out.println(\"Hello World! \" + Arrays.toString(args));\n" +
|
||||
" }\n" +
|
||||
"}");
|
||||
Path file = base.resolve("HelloWorld.java");
|
||||
testError(file,
|
||||
file + ":1: error: illegal character: '#'\n" +
|
||||
"#/usr/bin/java --source 11\n" +
|
||||
"^\n" +
|
||||
file + ":1: error: class, interface, or enum expected\n" +
|
||||
"#/usr/bin/java --source 11\n" +
|
||||
" ^\n" +
|
||||
"2 errors\n",
|
||||
"error: compilation failed");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadSourceOpt(Path base) throws IOException {
|
||||
Files.createDirectories(base);
|
||||
Path file = base.resolve("DummyClass.java");
|
||||
Files.write(file, List.of("class DummyClass { }"));
|
||||
Properties sysProps = System.getProperties();
|
||||
Properties p = new Properties(sysProps);
|
||||
p.setProperty("jdk.internal.javac.source", "<BAD>");
|
||||
System.setProperties(p);
|
||||
try {
|
||||
testError(file, "", "error: invalid value for --source option: <BAD>");
|
||||
} finally {
|
||||
System.setProperties(sysProps);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnablePreviewNoSource(Path base) throws IOException {
|
||||
tb.writeJavaFiles(base,
|
||||
"import java.util.Arrays;\n" +
|
||||
"class HelloWorld {\n" +
|
||||
" public static void main(String... args) {\n" +
|
||||
" System.out.println(\"Hello World! \" + Arrays.toString(args));\n" +
|
||||
" }\n" +
|
||||
"}");
|
||||
|
||||
String log = new JavaTask(tb)
|
||||
.vmOptions("--enable-preview")
|
||||
.className(base.resolve("HelloWorld.java").toString())
|
||||
.run(Task.Expect.FAIL)
|
||||
.getOutput(Task.OutputKind.STDERR);
|
||||
checkEqual("stderr", log.trim(),
|
||||
"error: --enable-preview must be used with --source");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoMain(Path base) throws IOException {
|
||||
tb.writeJavaFiles(base, "class NoMain { }");
|
||||
testError(base.resolve("NoMain.java"), "",
|
||||
"error: can't find main(String[]) method in class: NoMain");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMainBadParams(Path base) throws IOException {
|
||||
tb.writeJavaFiles(base,
|
||||
"class BadParams { public static void main() { } }");
|
||||
testError(base.resolve("BadParams.java"), "",
|
||||
"error: can't find main(String[]) method in class: BadParams");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMainNotPublic(Path base) throws IOException {
|
||||
tb.writeJavaFiles(base,
|
||||
"class NotPublic { static void main(String... args) { } }");
|
||||
testError(base.resolve("NotPublic.java"), "",
|
||||
"error: 'main' method is not declared 'public static'");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMainNotStatic(Path base) throws IOException {
|
||||
tb.writeJavaFiles(base,
|
||||
"class NotStatic { public void main(String... args) { } }");
|
||||
testError(base.resolve("NotStatic.java"), "",
|
||||
"error: 'main' method is not declared 'public static'");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMainNotVoid(Path base) throws IOException {
|
||||
tb.writeJavaFiles(base,
|
||||
"class NotVoid { public static int main(String... args) { return 0; } }");
|
||||
testError(base.resolve("NotVoid.java"), "",
|
||||
"error: 'main' method is not declared with a return type of 'void'");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClassInModule(Path base) throws IOException {
|
||||
tb.writeJavaFiles(base, "package java.net; class InModule { }");
|
||||
Path file = base.resolve("java").resolve("net").resolve("InModule.java");
|
||||
testError(file,
|
||||
file + ":1: error: package exists in another module: java.base\n" +
|
||||
"package java.net; class InModule { }\n" +
|
||||
"^\n" +
|
||||
"1 error\n",
|
||||
"error: compilation failed");
|
||||
}
|
||||
|
||||
void testError(Path file, String expectStdErr, String expectFault) throws IOException {
|
||||
Result r = run(file, Collections.emptyList(), List.of("1", "2", "3"));
|
||||
checkEmpty("stdout", r.stdOut);
|
||||
checkEqual("stderr", r.stdErr, expectStdErr);
|
||||
checkFault("exception", r.exception, expectFault);
|
||||
}
|
||||
|
||||
/*
|
||||
* Tests in which main throws an exception.
|
||||
*/
|
||||
@Test
|
||||
public void testTargetException1(Path base) throws IOException {
|
||||
tb.writeJavaFiles(base,
|
||||
"import java.util.Arrays;\n" +
|
||||
"class Thrower {\n" +
|
||||
" public static void main(String... args) {\n" +
|
||||
" throwWhenZero(Integer.parseInt(args[0]));\n" +
|
||||
" }\n" +
|
||||
" static void throwWhenZero(int arg) {\n" +
|
||||
" if (arg == 0) throw new Error(\"zero!\");\n" +
|
||||
" throwWhenZero(arg - 1);\n" +
|
||||
" }\n" +
|
||||
"}");
|
||||
Path file = base.resolve("Thrower.java");
|
||||
Result r = run(file, Collections.emptyList(), List.of("3"));
|
||||
checkEmpty("stdout", r.stdOut);
|
||||
checkEmpty("stderr", r.stdErr);
|
||||
checkTrace("exception", r.exception,
|
||||
"java.lang.Error: zero!",
|
||||
"at Thrower.throwWhenZero(Thrower.java:7)",
|
||||
"at Thrower.throwWhenZero(Thrower.java:8)",
|
||||
"at Thrower.throwWhenZero(Thrower.java:8)",
|
||||
"at Thrower.throwWhenZero(Thrower.java:8)",
|
||||
"at Thrower.main(Thrower.java:4)");
|
||||
}
|
||||
|
||||
Result run(Path file, List<String> runtimeArgs, List<String> appArgs) {
|
||||
List<String> args = new ArrayList<>();
|
||||
args.add(file.toString());
|
||||
args.addAll(appArgs);
|
||||
|
||||
PrintStream prev = System.out;
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try (PrintStream out = new PrintStream(baos, true)) {
|
||||
System.setOut(out);
|
||||
StringWriter sw = new StringWriter();
|
||||
try (PrintWriter err = new PrintWriter(sw, true)) {
|
||||
Main m = new Main(err);
|
||||
m.run(toArray(runtimeArgs), toArray(args));
|
||||
return new Result(baos.toString(), sw.toString(), null);
|
||||
} catch (Throwable t) {
|
||||
return new Result(baos.toString(), sw.toString(), t);
|
||||
}
|
||||
} finally {
|
||||
System.setOut(prev);
|
||||
}
|
||||
}
|
||||
|
||||
void checkEqual(String name, String found, String expect) {
|
||||
expect = expect.replace("\n", tb.lineSeparator);
|
||||
out.println(name + ": " + found);
|
||||
out.println(name + ": " + found);
|
||||
if (!expect.equals(found)) {
|
||||
error("Unexpected output; expected: " + expect);
|
||||
}
|
||||
}
|
||||
|
||||
void checkEmpty(String name, String found) {
|
||||
out.println(name + ": " + found);
|
||||
if (!found.isEmpty()) {
|
||||
error("Unexpected output; expected empty string");
|
||||
}
|
||||
}
|
||||
|
||||
void checkNull(String name, Throwable found) {
|
||||
out.println(name + ": " + found);
|
||||
if (found != null) {
|
||||
error("Unexpected exception; expected null");
|
||||
}
|
||||
}
|
||||
|
||||
void checkFault(String name, Throwable found, String expect) {
|
||||
expect = expect.replace("\n", tb.lineSeparator);
|
||||
out.println(name + ": " + found);
|
||||
if (found == null) {
|
||||
error("No exception thrown; expected Main.Fault");
|
||||
} else {
|
||||
if (!(found instanceof Main.Fault)) {
|
||||
error("Unexpected exception; expected Main.Fault");
|
||||
}
|
||||
if (!(found.getMessage().equals(expect))) {
|
||||
error("Unexpected detail message; expected: " + expect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void checkTrace(String name, Throwable found, String... expect) {
|
||||
if (!(found instanceof InvocationTargetException)) {
|
||||
error("Unexpected exception; expected InvocationTargetException");
|
||||
out.println("Found:");
|
||||
found.printStackTrace(out);
|
||||
}
|
||||
StringWriter sw = new StringWriter();
|
||||
try (PrintWriter pw = new PrintWriter(sw)) {
|
||||
((InvocationTargetException) found).getTargetException().printStackTrace(pw);
|
||||
}
|
||||
String trace = sw.toString();
|
||||
out.println(name + ":\n" + trace);
|
||||
String[] traceLines = trace.trim().split("[\r\n]+\\s+");
|
||||
try {
|
||||
tb.checkEqual(List.of(traceLines), List.of(expect));
|
||||
} catch (Error e) {
|
||||
error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
String[] toArray(List<String> list) {
|
||||
return list.toArray(new String[list.size()]);
|
||||
}
|
||||
|
||||
class Result {
|
||||
private final String stdOut;
|
||||
private final String stdErr;
|
||||
private final Throwable exception;
|
||||
|
||||
Result(String stdOut, String stdErr, Throwable exception) {
|
||||
this.stdOut = stdOut;
|
||||
this.stdErr = stdErr;
|
||||
this.exception = exception;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user