private KickStart()

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;
  }