in lib/tools_api/src/main/java/com/google/appengine/tools/KickStart.java [121:290]
private KickStart(String[] args) {
String entryClass = null;
ProcessBuilder builder = new ProcessBuilder();
String home = JAVA_HOME.value();
String javaExe = home + File.separator + "bin" + File.separator + "java";
List<String> jvmArgs = new ArrayList<>();
ArrayList<String> appServerArgs = new ArrayList<>();
List<String> command = builder.command();
command.add(javaExe);
boolean startOnFirstThread = Ascii.equalsIgnoreCase(OS_NAME.value(), "Mac OS X");
boolean testMode = false;
for (int i = 0; i < args.length; i++) {
// This section is for flags that either we don't care about and we
// pass on to DevAppServerMain, or we do care about and we don't pass
// on.
if (args[i].startsWith(JVM_FLAG)) {
jvmArgs.add(extractValue(args[i], JVM_FLAG_ERROR_MESSAGE));
} else if (args[i].startsWith(SDK_ROOT_FLAG)) {
String sdkRoot = new File(extractValue(args[i], SDK_ROOT_ERROR_MESSAGE)).getAbsolutePath();
System.setProperty("appengine.sdk.root", sdkRoot);
jvmArgs.add("-Dappengine.sdk.root=" + sdkRoot);
} else if (args[i].startsWith(START_ON_FIRST_THREAD_FLAG)) {
startOnFirstThread =
Boolean.parseBoolean(extractValue(args[i], START_ON_FIRST_THREAD_ERROR_MESSAGE));
} else if (args[i].equals("--test_mode")) {
testMode = true;
} else if (entryClass == null) {
if (args[i].charAt(0) == '-') {
throw new IllegalArgumentException(
"This argument may not precede the classname: " + args[i]);
} else {
entryClass = args[i];
if (!entryClass.equals(DevAppServerMain.class.getName()) && !testMode) {
throw new IllegalArgumentException("KickStart only works for DevAppServerMain");
}
}
} else {
appServerArgs.add(args[i]);
}
}
if (entryClass == null) {
throw new IllegalArgumentException("missing entry classname");
}
File newWorkingDir = newWorkingDir(appServerArgs.toArray(new String[0]));
builder.directory(newWorkingDir);
if (startOnFirstThread) {
// N.B.: If we're on Mac OS X, add
// -XstartOnFirstThread to suppress the addition of an app to
// the Dock even though we're going to initialize AWT (to work
// around a subsequent crash in the stub implementation of the
// Images API).
//
// For more details, see http://b/issue?id=1709075.
jvmArgs.add("-XstartOnFirstThread");
}
if (!JAVA_SPECIFICATION_VERSION.value().equals("1.8")) {
// Java11 or later need more flags:
jvmArgs.add("--add-opens");
jvmArgs.add("java.base/java.net=ALL-UNNAMED");
jvmArgs.add("--add-opens");
jvmArgs.add("java.base/sun.net.www.protocol.http=ALL-UNNAMED");
jvmArgs.add("--add-opens");
jvmArgs.add("java.base/sun.net.www.protocol.https=ALL-UNNAMED");
}
// Whatever classpath we were invoked with might have been relative.
// We make all paths in the classpath absolute.
String classpath = JAVA_CLASS_PATH.value();
if (classpath == null) {
throw new IllegalArgumentException("classpath must not be null");
}
StringBuilder newClassPath = new StringBuilder();
List<String> paths = Splitter.onPattern(File.pathSeparator).splitToList(classpath);
for (int i = 0; i < paths.size(); ++i) {
newClassPath.append(new File(paths.get(i)).getAbsolutePath());
if (i != paths.size() - 1) {
newClassPath.append(File.pathSeparator);
}
}
// User may have erroneously not included the webapp dir or got the args wrong. The first arg is
// the name of the DevAppServerMain class, so there should be at least 2 args. The last arg
// should be the app dir
if (appServerArgs.isEmpty() || Iterables.getLast(appServerArgs).startsWith("-")) {
new DevAppServerMain().printHelp(System.out);
System.exit(1);
}
Path workingDir = Paths.get(Iterables.getLast(appServerArgs));
new DevAppServerMain().validateWarPath(workingDir.toFile());
String appDir = null;
List<String> absoluteAppServerArgs = new ArrayList<>(appServerArgs.size());
// Make any of the appserver arguments that need to be absolute, absolute.
// This currently includes sdk_root, the external resource dir,
// and the application root (last arg)
for (int i = 0; i < appServerArgs.size(); ++i) {
String arg = appServerArgs.get(i);
if (i == appServerArgs.size() - 1) {
// The last argument may be the app root
if (!arg.startsWith("-")) {
File file = new File(arg);
if (file.exists()) {
arg = new File(arg).getAbsolutePath();
appDir = arg;
}
}
}
absoluteAppServerArgs.add(arg);
}
AppEnvironment appEnvironment = readAppEnvironment(appDir);
String encoding = appEnvironment.encoding;
if (encoding == null) {
encoding = "UTF-8";
}
jvmArgs.add("-Dfile.encoding=" + encoding);
// Build up the command
command.addAll(jvmArgs);
command.add("-classpath");
command.add(newClassPath.toString());
command.add(entryClass);
// Pass the current working directory so relative files can be interpreted the natural way.
command.add("--property=kickstart.user.dir=" + USER_DIR.value());
command.addAll(absoluteAppServerArgs);
// Setup environment variables.
String gaeEnv = "localdev";
builder.environment().put("GAE_ENV", gaeEnv);
builder.environment().put("GAE_RUNTIME", gaeRuntime);
builder.environment().put("GAE_SERVICE", appEnvironment.serviceName);
builder.environment().put("GAE_INSTANCE", UUID.randomUUID().toString());
builder.inheritIO();
logger.fine("Executing " + command);
System.out.println("Executing " + command);
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
if (serverProcess != null) {
serverProcess.destroy();
}
}
});
try {
serverProcess = builder.start();
} catch (IOException e) {
throw new RuntimeException("Unable to start the process", e);
}
try {
serverProcess.waitFor();
} catch (InterruptedException e) {
// If we're interrupted, we just quit.
}
serverProcess.destroy();
serverProcess = null;
}