App Engine SDK 1.8.4 release.
[gae.git] / java / src / main / com / google / appengine / tools / KickStart.java
blob2301cde2731187a5759146c3cea820fa51b5ce11
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;
8 import com.google.apphosting.utils.config.AppEngineConfigException;
9 import com.google.apphosting.utils.config.AppEngineWebXml;
10 import com.google.apphosting.utils.config.AppEngineWebXmlReader;
12 import java.io.File;
13 import java.io.IOException;
14 import java.io.PrintWriter;
15 import java.util.ArrayList;
16 import java.util.List;
17 import java.util.logging.Logger;
19 /**
20 * Launches a process in an operating-system agnostic way. Helps us avoid
21 * idiosyncracies in scripts for different platforms. Currently this only
22 * works for DevAppServerMain.
24 * Takes a command line invocation like:
26 * <pre>
27 * java -cp ../lib/appengine-tools-api.jar com.google.appengine.tools.KickStart \
28 * --jvm_flag="-Dlog4j.configuration=log4j.props"
29 * com.google.appengine.tools.development.DevAppServerMain \
30 * --jvm_flag="-agentlib:jdwp=transport=dt_socket,server=y,address=7000"
31 * --address=localhost --port=5005 appDir
32 * </pre>
34 * and turns it into:
36 * <pre>
37 * java -cp &lt;an_absolute_path&gt;/lib/appengine-tools-api.jar \
38 * -Dlog4j.configuration=log4j.props \
39 * -agentlib:jdwp=transport=dt_socket,server=y,address=7000 \
40 * com.google.appengine.tools.development.DevAppServerMain \
41 * --address=localhost --port=5005 &lt;an_absolute_path&gt;/appDir
42 * </pre>
44 * while also setting its working directory (if appropriate).
45 * <p>
46 * All arguments between {@code com.google.appengine.tools.KickStart} and
47 * {@code com.google.appengine.tools.development.DevAppServerMain}, as well as
48 * all {@code --jvm_flag} arguments after {@code DevAppServerMain}, are consumed
49 * by KickStart. The remaining options after {@code DevAppServerMain} are
50 * given as arguments to DevAppServerMain, without interpretation by
51 * KickStart.
53 * At present, the only valid option to KickStart itself is:
54 * <DL>
55 * <DT>--jvm_flag=&lt;vm_arg&gt;</DT><DD>Passes &lt;vm_arg&gt; as a JVM
56 * argument for the child JVM. May be repeated.</DD>
57 * </DL>
58 * Additionally, if the --external_resource_dir argument is specified, we use it
59 * to set the working directory instead of the application war directory.
62 public class KickStart {
64 private static final Logger logger = Logger.getLogger(KickStart.class.getName());
66 private static final String EXTERNAL_RESOURCE_DIR_FLAG =
67 "--" + DevAppServerMain.EXTERNAL_RESOURCE_DIR_ARG;
68 private static final String EXTERNAL_RESOURCE_DIR_ERROR_MESSAGE =
69 EXTERNAL_RESOURCE_DIR_FLAG + "=<path> expected.";
71 private static final String GENERATE_WAR_FLAG = "--" + DevAppServerMain.GENERATE_WAR_ARG;
73 private static final String GENERATED_WAR_DIR_FLAG =
74 "--" + DevAppServerMain.GENERATED_WAR_DIR_ARG;
76 private static final String NO_JAVA_AGENT_FLAG = "--no_java_agent";
78 private static final String JVM_FLAG = "--jvm_flag";
79 private static final String JVM_FLAG_ERROR_MESSAGE =
80 JVM_FLAG + "=<flag> expected.\n" + JVM_FLAG + " may be repeated to supply multiple flags";
82 private static final String START_ON_FIRST_THREAD_FLAG = "--startOnFirstThread";
83 private static final String START_ON_FIRST_THREAD_ERROR_MESSAGE =
84 START_ON_FIRST_THREAD_FLAG + "=<boolean> expected";
86 private static final String SDK_ROOT_FLAG = "--sdk_root";
87 private static final String SDK_ROOT_ERROR_MESSAGE = SDK_ROOT_FLAG + "=<path> expected";
89 private Process serverProcess = null;
91 public static void main(String[] args) {
92 new KickStart(args);
95 private KickStart(String[] args) {
96 String entryClass = null;
98 ProcessBuilder builder = new ProcessBuilder();
99 String home = System.getProperty("java.home");
100 String javaExe = home + File.separator + "bin" + File.separator + "java";
102 List<String> jvmArgs = new ArrayList<String>();
103 ArrayList<String> appServerArgs = new ArrayList<String>();
104 List<String> command = builder.command();
105 command.add(javaExe);
107 boolean startOnFirstThread = System.getProperty("os.name").equalsIgnoreCase("Mac OS X");
108 String externalResourceDirArg = null;
109 boolean generateWar = false;
110 boolean noJavaAgent = false;
112 for (int i = 0; i < args.length; i++) {
113 if (args[i].startsWith(EXTERNAL_RESOURCE_DIR_FLAG)) {
114 externalResourceDirArg = extractValue(args[i], EXTERNAL_RESOURCE_DIR_ERROR_MESSAGE);
115 } else if (args[i].startsWith(GENERATED_WAR_DIR_FLAG)
116 || args[i].startsWith(GENERATE_WAR_FLAG)) {
117 generateWar = true;
118 } else if (args[i].startsWith(NO_JAVA_AGENT_FLAG)) {
119 noJavaAgent = true;
121 if (args[i].startsWith(JVM_FLAG)) {
122 jvmArgs.add(extractValue(args[i], JVM_FLAG_ERROR_MESSAGE));
123 } else if (args[i].startsWith(START_ON_FIRST_THREAD_FLAG)) {
124 startOnFirstThread =
125 Boolean.valueOf(extractValue(args[i], START_ON_FIRST_THREAD_ERROR_MESSAGE));
126 } else if (entryClass == null) {
127 if (args[i].charAt(0) == '-') {
128 throw new IllegalArgumentException("Only --jvm_flag may precede classname, not "
129 + args[i]);
130 } else {
131 entryClass = args[i];
132 if (!entryClass.equals(DevAppServerMain.class.getName())) {
133 throw new IllegalArgumentException("KickStart only works for DevAppServerMain");
136 } else {
137 appServerArgs.add(args[i]);
141 if (entryClass == null) {
142 throw new IllegalArgumentException("missing entry classname");
145 if (externalResourceDirArg == null && generateWar) {
146 System.err.println(
147 "Generating a war directory requires " + "--" + EXTERNAL_RESOURCE_DIR_FLAG);
148 System.exit(1);
150 File newWorkingDir = newWorkingDir(externalResourceDirArg, args);
151 builder.directory(newWorkingDir);
153 if (startOnFirstThread) {
154 jvmArgs.add("-XstartOnFirstThread");
157 String classpath = System.getProperty("java.class.path");
158 StringBuffer newClassPath = new StringBuffer();
159 assert classpath != null : "classpath must not be null";
160 String[] paths = classpath.split(File.pathSeparator);
161 for (int i = 0; i < paths.length; ++i) {
162 newClassPath.append(new File(paths[i]).getAbsolutePath());
163 if (i != paths.length - 1) {
164 newClassPath.append(File.pathSeparator);
168 String sdkRoot = null;
169 String appDir = null;
170 List<String> absoluteAppServerArgs = new ArrayList<String>(appServerArgs.size());
172 for (int i = 0; i < appServerArgs.size(); ++i) {
173 String arg = appServerArgs.get(i);
174 if (arg.startsWith(SDK_ROOT_FLAG)) {
175 sdkRoot = new File(extractValue(arg, SDK_ROOT_ERROR_MESSAGE)).getAbsolutePath();
176 arg = SDK_ROOT_FLAG + "=" + sdkRoot;
177 } else if (arg.startsWith(EXTERNAL_RESOURCE_DIR_FLAG)) {
178 arg = EXTERNAL_RESOURCE_DIR_FLAG + "="
179 + new File(extractValue(arg, EXTERNAL_RESOURCE_DIR_ERROR_MESSAGE)).getAbsolutePath();
180 } else if (i == appServerArgs.size() - 1) {
181 if (!arg.startsWith("-")) {
182 File file = new File(arg);
183 if (file.exists()) {
184 arg = new File(arg).getAbsolutePath();
185 appDir = arg;
189 absoluteAppServerArgs.add(arg);
192 if (sdkRoot == null) {
193 sdkRoot = SdkInfo.getSdkRoot().getAbsolutePath();
195 boolean isVM = false;
196 if (appDir != null) {
197 isVM = isVMRuntime(appDir);
199 if (isVM) {
200 noJavaAgent = true;
201 jvmArgs.add("-D--enable_all_permissions=true");
204 if (!noJavaAgent) {
205 String agentJar = sdkRoot + "/lib/agent/appengine-agent.jar";
206 agentJar = agentJar.replace('/', File.separatorChar);
207 jvmArgs.add("-javaagent:" + agentJar);
210 String jdkOverridesJar = sdkRoot + "/lib/override/appengine-dev-jdk-overrides.jar";
211 jdkOverridesJar = jdkOverridesJar.replace('/', File.separatorChar);
212 jvmArgs.add("-Xbootclasspath/p:" + jdkOverridesJar);
214 command.addAll(jvmArgs);
215 command.add("-classpath");
216 command.add(newClassPath.toString());
217 command.add(entryClass);
218 command.add("--property=kickstart.user.dir=" + System.getProperty("user.dir"));
219 if (isVM) {
220 command.add("--no_java_agent");
222 command.addAll(absoluteAppServerArgs);
224 logger.fine("Executing " + command);
225 System.out.println("Executing " + command);
227 Runtime.getRuntime().addShutdownHook(new Thread() {
228 @Override
229 public void run() {
230 if (serverProcess != null) {
231 serverProcess.destroy();
236 try {
237 serverProcess = builder.start();
238 } catch (IOException e) {
239 throw new RuntimeException("Unable to start the process", e);
242 new Thread(new OutputPump(serverProcess.getInputStream(),
243 new PrintWriter(System.out, true))).start();
244 new Thread(new OutputPump(serverProcess.getErrorStream(),
245 new PrintWriter(System.err, true))).start();
247 try {
248 serverProcess.waitFor();
249 } catch (InterruptedException e) {
252 serverProcess.destroy();
253 serverProcess = null;
256 private static String extractValue(String argument, String errorMessage) {
257 int indexOfEqualSign = argument.indexOf('=');
258 if (indexOfEqualSign == -1) {
259 throw new IllegalArgumentException(errorMessage);
261 return argument.substring(argument.indexOf('=') + 1);
265 * Encapsulates the logic to determine the working directory that should be set for the dev
266 * appserver process. If one is explicitly specified it will be used. Otherwise the last
267 * command-line argument will be used.
269 * @param workingDirectoryArg An explicitly specified path. If not {@code null} then it will be
270 * used.
271 * @param args The command-line arguments. If {@code workingDirectory} is {@code null} then the
272 * last command-line argument will be used as the working directory.
273 * @return The working directory to use. If the path to an existing directory was not specified
274 * then we exist with a failure.
276 private static File newWorkingDir(String workingDirectoryArg, String[] args) {
277 String workingDirPath;
278 if (workingDirectoryArg != null) {
279 workingDirPath = workingDirectoryArg;
280 } else {
281 if (args.length < 2 || args[args.length - 1].startsWith("-")) {
282 DevAppServerMain.printHelp(System.out);
283 System.exit(1);
285 workingDirPath = args[args.length - 1];
287 File newDir = new File(workingDirPath);
288 if (!newDir.isDirectory()) {
289 if (workingDirectoryArg != null) {
290 System.err.println(workingDirectoryArg + " is not an existing directory.");
291 System.exit(1);
292 } else {
293 DevAppServerMain.validateWarPath(newDir);
296 return newDir;
299 static boolean isVMRuntime(String appDir) {
300 File f = new File(appDir, "WEB-INF/appengine-web.xml");
301 if (!f.exists()) {
302 return false;
305 try {
306 AppEngineWebXmlReader reader = new AppEngineWebXmlReader(appDir);
307 AppEngineWebXml appEngineWebXml = reader.readAppEngineWebXml();
308 return appEngineWebXml.getUseVm();
309 } catch (AppEngineConfigException e) {
310 System.err.println("Error reading: " + f.getAbsolutePath());
311 return false;