blob: 1b7bac0a3e86c806a5d832b62b34ad407ab0df61 [file] [log] [blame]
Stuart McCullochbb014372012-06-07 21:57:32 +00001package aQute.bnd.build;
2
3import java.io.*;
4import java.util.*;
5import java.util.Map.Entry;
6import java.util.concurrent.*;
7import java.util.jar.*;
8
9import aQute.bnd.service.RepositoryPlugin.Strategy;
10import aQute.lib.osgi.*;
11import aQute.libg.command.*;
12import aQute.libg.generics.*;
13import aQute.libg.header.*;
14
15/**
16 * A Project Launcher is a base class to be extended by launchers. Launchers are
17 * JARs that launch a framework and install a number of bundles and then run the
18 * framework. A launcher jar must specify a Launcher-Class manifest header. This
19 * class is instantiated and cast to a LauncherPlugin. This plug in is then
20 * asked to provide a ProjectLauncher. This project launcher is then used by the
21 * project to run the code. Launchers must extend this class.
22 *
23 */
24public abstract class ProjectLauncher {
25 private final Project project;
26 private long timeout = 0;
27 private final List<String> classpath = new ArrayList<String>();
28 private List<String> runbundles = Create.list();
29 private final List<String> runvm = new ArrayList<String>();
30 private Map<String, String> runproperties;
31 private Command java;
32 private Parameters runsystempackages;
33 private final List<String> activators = Create.list();
34 private File storageDir;
35 private final List<String> warnings = Create.list();
36 private final List<String> errors = Create.list();
37
38 private boolean trace;
39 private boolean keep;
40 private int framework;
41
42 public final static int SERVICES = 10111;
43 public final static int NONE = 20123;
44
45 // MUST BE ALIGNED WITH LAUNCHER
46 public final static int OK = 0;
47 public final static int WARNING = -1;
48 public final static int ERROR = -2;
49 public final static int TIMEDOUT = -3;
50 public final static int UPDATE_NEEDED = -4;
51 public final static int CANCELED = -5;
52 public final static int DUPLICATE_BUNDLE = -6;
53 public final static int RESOLVE_ERROR = -7;
54 public final static int ACTIVATOR_ERROR = -8;
55 public final static int CUSTOM_LAUNCHER = -128;
56
57 public final static String EMBEDDED_ACTIVATOR = "Embedded-Activator";
58
59 public ProjectLauncher(Project project) throws Exception {
60 this.project = project;
61
62 updateFromProject();
63 }
64
65 /**
66 * Collect all the aspect from the project and set the local fields from
67 * them. Should be called
68 *
69 * @throws Exception
70 */
71 protected void updateFromProject() throws Exception {
72 // pkr: could not use this because this is killing the runtests.
73 // project.refresh();
74 runbundles.clear();
75 Collection<Container> run = project.getRunbundles();
76
77 for (Container container : run) {
78 File file = container.getFile();
79 if (file != null && (file.isFile() || file.isDirectory())) {
80 runbundles.add(file.getAbsolutePath());
81 } else {
82 warning("Bundle file \"%s\" does not exist", file);
83 }
84 }
85
86 if (project.getRunBuilds()) {
87 File[] builds = project.build();
88 if (builds != null)
89 for (File file : builds)
90 runbundles.add(file.getAbsolutePath());
91 }
92
93 Collection<Container> runpath = project.getRunpath();
94 runsystempackages = project.getParameters(Constants.RUNSYSTEMPACKAGES);
95 framework = getRunframework(project.getProperty(Constants.RUNFRAMEWORK));
96 trace = Processor.isTrue(project.getProperty(Constants.RUNTRACE));
97
98 timeout = Processor.getDuration(project.getProperty(Constants.RUNTIMEOUT), 0);
99 trace = Processor.isTrue(project.getProperty(Constants.RUNTRACE));
100
101 // For backward compatibility with bndtools launcher
102 List<Container> fws = project.getBundles(Strategy.HIGHEST, project.getProperty("-runfw"),
103 "-runfw");
104 runpath.addAll(fws);
105
106 for (Container c : runpath) {
107 addClasspath(c);
108 }
109
110 runvm.addAll(project.getRunVM());
111 runproperties = project.getRunProperties();
112
113 storageDir = project.getRunStorage();
114 if (storageDir == null) {
115 storageDir = new File(project.getTarget(), "fw");
116 }
117 }
118
119 private int getRunframework(String property) {
120 if (Constants.RUNFRAMEWORK_NONE.equalsIgnoreCase(property))
121 return NONE;
122 else if (Constants.RUNFRAMEWORK_SERVICES.equalsIgnoreCase(property))
123 return SERVICES;
124
125 return SERVICES;
126 }
127
128 public void addClasspath(Container container) throws Exception {
129 if (container.getError() != null) {
130 project.error("Cannot launch because %s has reported %s", container.getProject(),
131 container.getError());
132 } else {
133 Collection<Container> members = container.getMembers();
134 for (Container m : members) {
135 String path = m.getFile().getAbsolutePath();
136 if (!classpath.contains(path)) {
137 classpath.add(path);
138
139 Manifest manifest = m.getManifest();
140
141 if (manifest != null) {
142 Parameters exports = project.parseHeader(manifest.getMainAttributes()
143 .getValue(Constants.EXPORT_PACKAGE));
144 for (Entry<String, Attrs> e : exports.entrySet()) {
145 if (!runsystempackages.containsKey(e.getKey()))
146 runsystempackages.put(e.getKey(), e.getValue());
147 }
148
149 // Allow activators on the runpath. They are called
150 // after
151 // the framework is completely initialized wit the
152 // system
153 // context.
154 String activator = manifest.getMainAttributes()
155 .getValue(EMBEDDED_ACTIVATOR);
156 if (activator != null)
157 activators.add(activator);
158 }
159 }
160 }
161 }
162 }
163
164 public void addRunBundle(String f) {
165 runbundles.add(f);
166 }
167
168 public Collection<String> getRunBundles() {
169 return runbundles;
170 }
171
172 public void addRunVM(String arg) {
173 runvm.add(arg);
174 }
175
176 public List<String> getRunpath() {
177 return classpath;
178 }
179
180 public Collection<String> getClasspath() {
181 return classpath;
182 }
183
184 public Collection<String> getRunVM() {
185 return runvm;
186 }
187
188 public Collection<String> getArguments() {
189 return Collections.emptySet();
190 }
191
192 public Map<String, String> getRunProperties() {
193 return runproperties;
194 }
195
196 public File getStorageDir() {
197 return storageDir;
198 }
199
200 public abstract String getMainTypeName();
201
202 public abstract void update() throws Exception;
203
204 public int launch() throws Exception {
205 prepare();
206 java = new Command();
207 java.add(project.getProperty("java", "java"));
208 java.add("-cp");
209 java.add(Processor.join(getClasspath(), File.pathSeparator));
210 java.addAll(getRunVM());
211 java.add(getMainTypeName());
212 java.addAll(getArguments());
213 if (timeout != 0)
214 java.setTimeout(timeout + 1000, TimeUnit.MILLISECONDS);
215
216 try {
217 int result = java.execute((InputStream)null, System.err, System.err);
218 if (result == Integer.MIN_VALUE)
219 return TIMEDOUT;
220 reportResult(result);
221 return result;
222 } finally {
223 cleanup();
224 }
225 }
226
227 /**
228 * Is called after the process exists. Can you be used to cleanup
229 * the properties file.
230 */
231
232 public void cleanup() {
233 // do nothing by default
234 }
235
236 protected void reportResult(int result) {
237 switch (result) {
238 case OK:
239 project.trace("Command terminated normal %s", java);
240 break;
241 case TIMEDOUT:
242 project.error("Launch timedout: %s", java);
243 break;
244
245 case ERROR:
246 project.error("Launch errored: %s", java);
247 break;
248
249 case WARNING:
250 project.warning("Launch had a warning %s", java);
251 break;
252 default:
253 project.error("Exit code remote process %d: %s", result, java);
254 break;
255 }
256 }
257
258 public void setTimeout(long timeout, TimeUnit unit) {
259 this.timeout = unit.convert(timeout, TimeUnit.MILLISECONDS);
260 }
261
262 public long getTimeout() {
263 return this.timeout;
264 }
265
266 public void cancel() {
267 java.cancel();
268 }
269
270 public Map<String, ? extends Map<String, String>> getSystemPackages() {
271 return runsystempackages.asMapMap();
272 }
273
274 public void setKeep(boolean keep) {
275 this.keep = keep;
276 }
277
278 public boolean isKeep() {
279 return keep;
280 }
281
282 public void setTrace(boolean level) {
283 this.trace = level;
284 }
285
286 public boolean getTrace() {
287 return this.trace;
288 }
289
290 /**
291 * Should be called when all the changes to the launchers are set. Will
292 * calculate whatever is necessary for the launcher.
293 *
294 * @throws Exception
295 */
296 public abstract void prepare() throws Exception;
297
298 public Project getProject() {
299 return project;
300 }
301
302 public boolean addActivator(String e) {
303 return activators.add(e);
304 }
305
306 public Collection<String> getActivators() {
307 return Collections.unmodifiableCollection(activators);
308 }
309
310 /**
311 * Either NONE or SERVICES to indicate how the remote end launches. NONE
312 * means it should not use the classpath to run a framework. This likely
313 * requires some dummy framework support. SERVICES means it should load the
314 * framework from the claspath.
315 *
316 * @return
317 */
318 public int getRunFramework() {
319 return framework;
320 }
321
322 public void setRunFramework(int n) {
323 assert n == NONE || n == SERVICES;
324 this.framework = n;
325 }
326
327 /**
328 * Add the specification for a set of bundles the runpath if it does not
329 * already is included. This can be used by subclasses to ensure the proper
330 * jars are on the classpath.
331 *
332 * @param defaultSpec
333 * The default spec for default jars
334 */
335 public void addDefault(String defaultSpec) throws Exception {
336 Collection<Container> deflts = project.getBundles(Strategy.HIGHEST, defaultSpec, null);
337 for (Container c : deflts)
338 addClasspath(c);
339 }
340
341 /**
342 * Create a self executable.
343 */
344
345 public Jar executable() throws Exception {
346 throw new UnsupportedOperationException();
347 }
348
349 public void clear() {
350 errors.clear();
351 warnings.clear();
352 }
353
354 public List<String> getErrors() {
355 return Collections.unmodifiableList(errors);
356 }
357
358 public List<String> getWarnings() {
359 return Collections.unmodifiableList(warnings);
360 }
361
362 protected void error(String message, Object... args) {
363 String formatted = String.format(message, args);
364 errors.add(formatted);
365 }
366
367 protected void warning(String message, Object... args) {
368 String formatted = String.format(message, args);
369 warnings.add(formatted);
370 }
371}