Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / tools / KickStart.java
bloba98f925005629f2363771c96f9edd4fb4f708e6a
1 // Copyright 2008 Google Inc. All Rights Reserved.
3 package com.google.appengine.tools;
5 import com.google.appengine.tools.admin.OutputPump;
6 import com.google.appengine.tools.development.DevAppServerMain;
7 import com.google.appengine.tools.info.SdkInfo;
9 import java.io.File;
10 import java.io.IOException;
11 import java.io.PrintWriter;
12 import java.util.ArrayList;
13 import java.util.List;
14 import java.util.logging.Logger;
16 /**
17 * Launches a process in an operating-system agnostic way. Helps us avoid
18 * idiosyncracies in scripts for different platforms. Currently this only
19 * works for DevAppServerMain.
21 * Takes a command line invocation like:
23 * <pre>
24 * java -cp ../lib/appengine-tools-api.jar com.google.appengine.tools.KickStart \
25 * --jvm_flag="-Dlog4j.configuration=log4j.props"
26 * com.google.appengine.tools.development.DevAppServerMain \
27 * --jvm_flag="-agentlib:jdwp=transport=dt_socket,server=y,address=7000"
28 * --address=localhost --port=5005 appDir
29 * </pre>
31 * and turns it into:
33 * <pre>
34 * java -cp &lt;an_absolute_path&gt;/lib/appengine-tools-api.jar \
35 * -Dlog4j.configuration=log4j.props \
36 * -agentlib:jdwp=transport=dt_socket,server=y,address=7000 \
37 * com.google.appengine.tools.development.DevAppServerMain \
38 * --address=localhost --port=5005 &lt;an_absolute_path&gt;/appDir
39 * </pre>
41 * while also setting its working directory (if appropriate).
42 * <p>
43 * All arguments between {@code com.google.appengine.tools.KickStart} and
44 * {@code com.google.appengine.tools.development.DevAppServerMain}, as well as
45 * all {@code --jvm_flag} arguments after {@code DevAppServerMain}, are consumed
46 * by KickStart. The remaining options after {@code DevAppServerMain} are
47 * given as arguments to DevAppServerMain, without interpretation by
48 * KickStart.
50 * At present, the only valid option to KickStart itself is:
51 * <DL>
52 * <DT>--jvm_flag=&lt;vm_arg&gt;</DT><DD>Passes &lt;vm_arg&gt; as a JVM
53 * argument for the child JVM. May be repeated.</DD>
54 * </DL>
55 * Additionally, if the --external_resource_dir argument is specified, we use it
56 * to set the working directory instead of the application war directory.
59 public class KickStart {
61 private static final Logger logger = Logger.getLogger(KickStart.class.getName());
63 private static final String EXTERNAL_RESOURCE_DIR_FLAG =
64 "--" + DevAppServerMain.EXTERNAL_RESOURCE_DIR_ARG;
65 private static final String EXTERNAL_RESOURCE_DIR_ERROR_MESSAGE =
66 EXTERNAL_RESOURCE_DIR_FLAG + "=<path> expected.";
68 private static final String GENERATE_WAR_FLAG = "--" + DevAppServerMain.GENERATE_WAR_ARG;
70 private static final String GENERATED_WAR_DIR_FLAG =
71 "--" + DevAppServerMain.GENERATED_WAR_DIR_ARG;
73 private static final String JVM_FLAG = "--jvm_flag";
74 private static final String JVM_FLAG_ERROR_MESSAGE =
75 JVM_FLAG + "=<flag> expected.\n" + JVM_FLAG + " may be repeated to supply multiple flags";
77 private static final String START_ON_FIRST_THREAD_FLAG = "--startOnFirstThread";
78 private static final String START_ON_FIRST_THREAD_ERROR_MESSAGE =
79 START_ON_FIRST_THREAD_FLAG + "=<boolean> expected";
81 private static final String SDK_ROOT_FLAG = "--sdk_root";
82 private static final String SDK_ROOT_ERROR_MESSAGE = SDK_ROOT_FLAG + "=<path> expected";
84 private Process serverProcess = null;
86 public static void main(String[] args) {
87 new KickStart(args);
90 private KickStart(String[] args) {
91 String entryClass = null;
93 ProcessBuilder builder = new ProcessBuilder();
94 String home = System.getProperty("java.home");
95 String javaExe = home + File.separator + "bin" + File.separator + "java";
97 List<String> jvmArgs = new ArrayList<String>();
98 ArrayList<String> appServerArgs = new ArrayList<String>();
99 List<String> command = builder.command();
100 command.add(javaExe);
102 boolean startOnFirstThread = System.getProperty("os.name").equalsIgnoreCase("Mac OS X");
103 String externalResourceDirArg = null;
104 boolean generateWar = false;
106 for (int i = 0; i < args.length; i++) {
107 if (args[i].startsWith(EXTERNAL_RESOURCE_DIR_FLAG)) {
108 externalResourceDirArg = extractValue(args[i], EXTERNAL_RESOURCE_DIR_ERROR_MESSAGE);
109 } else if (args[i].startsWith(GENERATED_WAR_DIR_FLAG)
110 || args[i].startsWith(GENERATE_WAR_FLAG)) {
111 generateWar = true;
113 if (args[i].startsWith(JVM_FLAG)) {
114 jvmArgs.add(extractValue(args[i], JVM_FLAG_ERROR_MESSAGE));
115 } else if (args[i].startsWith(START_ON_FIRST_THREAD_FLAG)) {
116 startOnFirstThread =
117 Boolean.valueOf(extractValue(args[i], START_ON_FIRST_THREAD_ERROR_MESSAGE));
118 } else if (entryClass == null) {
119 if (args[i].charAt(0) == '-') {
120 throw new IllegalArgumentException("Only --jvm_flag may precede classname, not "
121 + args[i]);
122 } else {
123 entryClass = args[i];
124 if (!entryClass.equals(DevAppServerMain.class.getName())) {
125 throw new IllegalArgumentException("KickStart only works for DevAppServerMain");
128 } else {
129 appServerArgs.add(args[i]);
133 if (entryClass == null) {
134 throw new IllegalArgumentException("missing entry classname");
137 if (externalResourceDirArg == null && generateWar) {
138 System.err.println(
139 "Generating a war directory requires " + "--" + EXTERNAL_RESOURCE_DIR_FLAG);
140 System.exit(1);
142 File newWorkingDir = newWorkingDir(externalResourceDirArg, args);
143 builder.directory(newWorkingDir);
145 if (startOnFirstThread) {
146 jvmArgs.add("-XstartOnFirstThread");
149 String classpath = System.getProperty("java.class.path");
150 StringBuffer newClassPath = new StringBuffer();
151 assert classpath != null : "classpath must not be null";
152 String[] paths = classpath.split(File.pathSeparator);
153 for (int i = 0; i < paths.length; ++i) {
154 newClassPath.append(new File(paths[i]).getAbsolutePath());
155 if (i != paths.length - 1) {
156 newClassPath.append(File.pathSeparator);
160 String sdkRoot = null;
162 List<String> absoluteAppServerArgs = new ArrayList<String>(appServerArgs.size());
164 for (int i = 0; i < appServerArgs.size(); ++i) {
165 String arg = appServerArgs.get(i);
166 if (arg.startsWith(SDK_ROOT_FLAG)) {
167 sdkRoot = new File(extractValue(arg, SDK_ROOT_ERROR_MESSAGE)).getAbsolutePath();
168 arg = SDK_ROOT_FLAG + "=" + sdkRoot;
169 } else if (arg.startsWith(EXTERNAL_RESOURCE_DIR_FLAG)) {
170 arg = EXTERNAL_RESOURCE_DIR_FLAG + "="
171 + new File(extractValue(arg, EXTERNAL_RESOURCE_DIR_ERROR_MESSAGE)).getAbsolutePath();
172 } else if (i == appServerArgs.size() - 1) {
173 if (!arg.startsWith("-")) {
174 File file = new File(arg);
175 if (file.exists()) {
176 arg = new File(arg).getAbsolutePath();
180 absoluteAppServerArgs.add(arg);
183 if (sdkRoot == null) {
184 sdkRoot = SdkInfo.getSdkRoot().getAbsolutePath();
187 String agentJar = sdkRoot + "/lib/agent/appengine-agent.jar";
188 agentJar = agentJar.replace('/', File.separatorChar);
189 jvmArgs.add("-javaagent:" + agentJar);
191 String jdkOverridesJar = sdkRoot + "/lib/override/appengine-dev-jdk-overrides.jar";
192 jdkOverridesJar = jdkOverridesJar.replace('/', File.separatorChar);
193 jvmArgs.add("-Xbootclasspath/p:" + jdkOverridesJar);
195 command.addAll(jvmArgs);
196 command.add("-classpath");
197 command.add(newClassPath.toString());
198 command.add(entryClass);
199 command.add("--property=kickstart.user.dir=" + System.getProperty("user.dir"));
200 command.addAll(absoluteAppServerArgs);
202 logger.fine("Executing " + command);
204 Runtime.getRuntime().addShutdownHook(new Thread() {
205 @Override
206 public void run() {
207 if (serverProcess != null) {
208 serverProcess.destroy();
213 try {
214 serverProcess = builder.start();
215 } catch (IOException e) {
216 throw new RuntimeException("Unable to start the process", e);
219 new Thread(new OutputPump(serverProcess.getInputStream(),
220 new PrintWriter(System.out, true))).start();
221 new Thread(new OutputPump(serverProcess.getErrorStream(),
222 new PrintWriter(System.err, true))).start();
224 try {
225 serverProcess.waitFor();
226 } catch (InterruptedException e) {
229 serverProcess.destroy();
230 serverProcess = null;
233 private static String extractValue(String argument, String errorMessage) {
234 int indexOfEqualSign = argument.indexOf('=');
235 if (indexOfEqualSign == -1) {
236 throw new IllegalArgumentException(errorMessage);
238 return argument.substring(argument.indexOf('=') + 1);
242 * Encapsulates the logic to determine the working directory that should be set for the dev
243 * appserver process. If one is explicitly specified it will be used. Otherwise the last
244 * command-line argument will be used.
246 * @param workingDirectoryArg An explicitly specified path. If not {@code null} then it will be
247 * used.
248 * @param args The command-line arguments. If {@code workingDirectory} is {@code null} then the
249 * last command-line argument will be used as the working directory.
250 * @return The working directory to use. If the path to an existing directory was not specified
251 * then we exist with a failure.
253 private static File newWorkingDir(String workingDirectoryArg, String[] args) {
254 String workingDirPath;
255 if (workingDirectoryArg != null) {
256 workingDirPath = workingDirectoryArg;
257 } else {
258 if (args.length < 2 || args[args.length - 1].startsWith("-")) {
259 DevAppServerMain.printHelp(System.out);
260 System.exit(1);
262 workingDirPath = args[args.length - 1];
264 File newDir = new File(workingDirPath);
265 if (!newDir.isDirectory()) {
266 if (workingDirectoryArg != null) {
267 System.err.println(workingDirectoryArg + " is not an existing directory.");
268 System.exit(1);
269 } else {
270 DevAppServerMain.validateWarPath(newDir);
273 return newDir;