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
;
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
;
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:
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
37 * java -cp <an_absolute_path>/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 <an_absolute_path>/appDir
44 * while also setting its working directory (if appropriate).
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
53 * At present, the only valid option to KickStart itself is:
55 * <DT>--jvm_flag=<vm_arg></DT><DD>Passes <vm_arg> as a JVM
56 * argument for the child JVM. May be repeated.</DD>
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
) {
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
)) {
118 } else if (args
[i
].startsWith(NO_JAVA_AGENT_FLAG
)) {
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
)) {
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 "
131 entryClass
= args
[i
];
132 if (!entryClass
.equals(DevAppServerMain
.class.getName())) {
133 throw new IllegalArgumentException("KickStart only works for DevAppServerMain");
137 appServerArgs
.add(args
[i
]);
141 if (entryClass
== null) {
142 throw new IllegalArgumentException("missing entry classname");
145 if (externalResourceDirArg
== null && generateWar
) {
147 "Generating a war directory requires " + "--" + EXTERNAL_RESOURCE_DIR_FLAG
);
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
);
184 arg
= new File(arg
).getAbsolutePath();
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
);
201 jvmArgs
.add("-D--enable_all_permissions=true");
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"));
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() {
230 if (serverProcess
!= null) {
231 serverProcess
.destroy();
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();
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
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
;
281 if (args
.length
< 2 || args
[args
.length
- 1].startsWith("-")) {
282 DevAppServerMain
.printHelp(System
.out
);
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.");
293 DevAppServerMain
.validateWarPath(newDir
);
299 static boolean isVMRuntime(String appDir
) {
300 File f
= new File(appDir
, "WEB-INF/appengine-web.xml");
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());