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
;
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
;
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:
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
34 * java -cp <an_absolute_path>/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 <an_absolute_path>/appDir
41 * while also setting its working directory (if appropriate).
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
50 * At present, the only valid option to KickStart itself is:
52 * <DT>--jvm_flag=<vm_arg></DT><DD>Passes <vm_arg> as a JVM
53 * argument for the child JVM. May be repeated.</DD>
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
) {
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
)) {
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
)) {
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 "
123 entryClass
= args
[i
];
124 if (!entryClass
.equals(DevAppServerMain
.class.getName())) {
125 throw new IllegalArgumentException("KickStart only works for DevAppServerMain");
129 appServerArgs
.add(args
[i
]);
133 if (entryClass
== null) {
134 throw new IllegalArgumentException("missing entry classname");
137 if (externalResourceDirArg
== null && generateWar
) {
139 "Generating a war directory requires " + "--" + EXTERNAL_RESOURCE_DIR_FLAG
);
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
);
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() {
207 if (serverProcess
!= null) {
208 serverProcess
.destroy();
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();
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
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
;
258 if (args
.length
< 2 || args
[args
.length
- 1].startsWith("-")) {
259 DevAppServerMain
.printHelp(System
.out
);
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.");
270 DevAppServerMain
.validateWarPath(newDir
);