blob: be7a71c0d068c84e086ce0b0cd9b3ec4ab589356 [file] [log] [blame]
Stuart McCullochf3173222012-06-07 21:57:32 +00001package aQute.bnd.build;
2
3import java.io.*;
4import java.lang.ref.*;
Stuart McCulloch2a0afd62012-09-06 18:28:06 +00005import java.net.*;
Stuart McCullochf3173222012-06-07 21:57:32 +00006import java.util.*;
7import java.util.concurrent.*;
8import java.util.concurrent.locks.*;
9import java.util.jar.*;
10
11import javax.naming.*;
12
13import aQute.bnd.maven.support.*;
Stuart McCulloch42151ee2012-07-16 13:43:38 +000014import aQute.bnd.osgi.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000015import aQute.bnd.service.*;
16import aQute.bnd.service.action.*;
17import aQute.lib.deployer.*;
Stuart McCulloch2a0afd62012-09-06 18:28:06 +000018import aQute.lib.hex.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000019import aQute.lib.io.*;
Stuart McCulloch2a0afd62012-09-06 18:28:06 +000020import aQute.lib.settings.*;
Stuart McCulloch1a890552012-06-29 19:23:09 +000021import aQute.service.reporter.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000022
23public class Workspace extends Processor {
24 public static final String BUILDFILE = "build.bnd";
25 public static final String CNFDIR = "cnf";
26 public static final String BNDDIR = "bnd";
27 public static final String CACHEDIR = "cache";
28
Stuart McCulloch4482c702012-06-15 13:27:53 +000029 static Map<File,WeakReference<Workspace>> cache = newHashMap();
30 final Map<String,Project> models = newHashMap();
31 final Map<String,Action> commands = newMap();
Stuart McCullochf3173222012-06-07 21:57:32 +000032 final File buildDir;
33 final Maven maven = new Maven(Processor.getExecutor());
Stuart McCulloch1b98aa02012-06-18 11:15:15 +000034 private boolean offline = true;
Stuart McCulloch2a0afd62012-09-06 18:28:06 +000035 Settings settings = new Settings();
Stuart McCullochf3173222012-06-07 21:57:32 +000036
37 /**
38 * This static method finds the workspace and creates a project (or returns
39 * an existing project)
40 *
41 * @param projectDir
42 * @return
43 */
44 public static Project getProject(File projectDir) throws Exception {
45 projectDir = projectDir.getAbsoluteFile();
46 assert projectDir.isDirectory();
47
48 Workspace ws = getWorkspace(projectDir.getParentFile());
49 return ws.getProject(projectDir.getName());
50 }
51
52 public static Workspace getWorkspace(File parent) throws Exception {
53 File workspaceDir = parent.getAbsoluteFile();
54
55 // the cnf directory can actually be a
56 // file that redirects
57 while (workspaceDir.isDirectory()) {
58 File test = new File(workspaceDir, CNFDIR);
59
60 if (!test.exists())
61 test = new File(workspaceDir, BNDDIR);
62
63 if (test.isDirectory())
64 break;
65
66 if (test.isFile()) {
67 String redirect = IO.collect(test).trim();
68 test = getFile(test.getParentFile(), redirect).getAbsoluteFile();
69 workspaceDir = test;
70 }
71 if (!test.exists())
72 throw new IllegalArgumentException("No Workspace found from: " + parent);
73 }
74
75 synchronized (cache) {
76 WeakReference<Workspace> wsr = cache.get(workspaceDir);
77 Workspace ws;
78 if (wsr == null || (ws = wsr.get()) == null) {
79 ws = new Workspace(workspaceDir);
80 cache.put(workspaceDir, new WeakReference<Workspace>(ws));
81 }
82 return ws;
83 }
84 }
85
86 public Workspace(File dir) throws Exception {
87 dir = dir.getAbsoluteFile();
Stuart McCulloch2929e2d2012-08-07 10:57:21 +000088 if (!dir.exists() && !dir.mkdirs()) {
89 throw new IOException("Could not create directory " + dir);
90 }
Stuart McCullochf3173222012-06-07 21:57:32 +000091 assert dir.isDirectory();
92
93 File buildDir = new File(dir, BNDDIR).getAbsoluteFile();
94 if (!buildDir.isDirectory())
95 buildDir = new File(dir, CNFDIR).getAbsoluteFile();
96
97 this.buildDir = buildDir;
98
99 File buildFile = new File(buildDir, BUILDFILE).getAbsoluteFile();
100 if (!buildFile.isFile())
101 warning("No Build File in " + dir);
102
103 setProperties(buildFile, dir);
104 propertiesChanged();
105
106 }
107
108 public Project getProject(String bsn) throws Exception {
109 synchronized (models) {
110 Project project = models.get(bsn);
111 if (project != null)
112 return project;
113
114 File projectDir = getFile(bsn);
115 project = new Project(this, projectDir);
116 if (!project.isValid())
117 return null;
118
119 models.put(bsn, project);
120 return project;
121 }
122 }
123
124 public boolean isPresent(String name) {
125 return models.containsKey(name);
126 }
127
128 public Collection<Project> getCurrentProjects() {
129 return models.values();
130 }
131
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000132 @Override
Stuart McCullochf3173222012-06-07 21:57:32 +0000133 public boolean refresh() {
134 if (super.refresh()) {
135 for (Project project : getCurrentProjects()) {
136 project.propertiesChanged();
137 }
138 return true;
139 }
140 return false;
141 }
142
Stuart McCulloch4482c702012-06-15 13:27:53 +0000143 @Override
144 public void propertiesChanged() {
Stuart McCullochf3173222012-06-07 21:57:32 +0000145 super.propertiesChanged();
146 File extDir = new File(this.buildDir, "ext");
147 File[] extensions = extDir.listFiles();
148 if (extensions != null) {
149 for (File extension : extensions) {
150 String extensionName = extension.getName();
151 if (extensionName.endsWith(".bnd")) {
152 extensionName = extensionName.substring(0, extensionName.length() - ".bnd".length());
153 try {
154 doIncludeFile(extension, false, getProperties(), "ext." + extensionName);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000155 }
156 catch (Exception e) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000157 error("PropertiesChanged: " + e.getMessage());
158 }
159 }
160 }
161 }
162 }
163
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000164 public String _workspace(@SuppressWarnings("unused")
165 String args[]) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000166 return getBase().getAbsolutePath();
167 }
168
169 public void addCommand(String menu, Action action) {
170 commands.put(menu, action);
171 }
172
173 public void removeCommand(String menu) {
174 commands.remove(menu);
175 }
176
Stuart McCulloch4482c702012-06-15 13:27:53 +0000177 public void fillActions(Map<String,Action> all) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000178 all.putAll(commands);
179 }
180
181 public Collection<Project> getAllProjects() throws Exception {
182 List<Project> projects = new ArrayList<Project>();
183 for (File file : getBase().listFiles()) {
184 if (new File(file, Project.BNDFILE).isFile())
185 projects.add(getProject(file));
186 }
187 return projects;
188 }
189
190 /**
191 * Inform any listeners that we changed a file (created/deleted/changed).
192 *
193 * @param f
194 * The changed file
195 */
196 public void changedFile(File f) {
197 List<BndListener> listeners = getPlugins(BndListener.class);
198 for (BndListener l : listeners)
199 try {
Stuart McCulloch1b98aa02012-06-18 11:15:15 +0000200 offline = false;
Stuart McCullochf3173222012-06-07 21:57:32 +0000201 l.changed(f);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000202 }
203 catch (Exception e) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000204 e.printStackTrace();
205 }
206 }
207
208 public void bracket(boolean begin) {
209 List<BndListener> listeners = getPlugins(BndListener.class);
210 for (BndListener l : listeners)
211 try {
212 if (begin)
213 l.begin();
214 else
215 l.end();
Stuart McCulloch4482c702012-06-15 13:27:53 +0000216 }
217 catch (Exception e) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000218 // who cares?
219 }
220 }
221
Stuart McCullochf3173222012-06-07 21:57:32 +0000222 /**
Stuart McCulloch4482c702012-06-15 13:27:53 +0000223 * Signal a BndListener plugin. We ran an infinite bug loop :-(
Stuart McCullochf3173222012-06-07 21:57:32 +0000224 */
Stuart McCulloch4482c702012-06-15 13:27:53 +0000225 final ThreadLocal<Reporter> signalBusy = new ThreadLocal<Reporter>();
226
Stuart McCullochf3173222012-06-07 21:57:32 +0000227 public void signal(Reporter reporter) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000228 if (signalBusy.get() != null)
Stuart McCullochf3173222012-06-07 21:57:32 +0000229 return;
Stuart McCulloch4482c702012-06-15 13:27:53 +0000230
Stuart McCullochf3173222012-06-07 21:57:32 +0000231 signalBusy.set(reporter);
232 try {
233 List<BndListener> listeners = getPlugins(BndListener.class);
234 for (BndListener l : listeners)
235 try {
236 l.signal(this);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000237 }
238 catch (Exception e) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000239 // who cares?
240 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000241 }
242 catch (Exception e) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000243 // Ignore
Stuart McCulloch4482c702012-06-15 13:27:53 +0000244 }
245 finally {
Stuart McCullochf3173222012-06-07 21:57:32 +0000246 signalBusy.set(null);
247 }
248 }
249
Stuart McCulloch4482c702012-06-15 13:27:53 +0000250 @Override
251 public void signal() {
Stuart McCullochf3173222012-06-07 21:57:32 +0000252 signal(this);
253 }
254
Stuart McCulloch2b3253e2012-06-17 20:38:35 +0000255 void copy(InputStream in, OutputStream out) throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +0000256 byte data[] = new byte[10000];
257 int size = in.read(data);
258 while (size > 0) {
259 out.write(data, 0, size);
260 size = in.read(data);
261 }
262 }
263
264 class CachedFileRepo extends FileRepo {
265 final Lock lock = new ReentrantLock();
266 boolean inited;
267
268 CachedFileRepo() {
269 super("cache", getFile(buildDir, CACHEDIR), false);
270 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000271
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000272 @Override
Stuart McCullochf3173222012-06-07 21:57:32 +0000273 public String toString() {
274 return "bnd-cache";
275 }
276
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000277 @Override
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000278 protected boolean init() throws Exception {
Stuart McCullochf3173222012-06-07 21:57:32 +0000279 if (lock.tryLock(50, TimeUnit.SECONDS) == false)
Stuart McCulloch4482c702012-06-15 13:27:53 +0000280 throw new TimeLimitExceededException("Cached File Repo is locked and can't acquire it");
Stuart McCullochf3173222012-06-07 21:57:32 +0000281 try {
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000282 if (super.init()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000283 inited = true;
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000284 if (!root.exists() && !root.mkdirs()) {
285 throw new IOException("Could not create cache directory " + root);
286 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000287 if (!root.isDirectory())
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000288 throw new IllegalArgumentException("Cache directory " + root + " not a directory");
Stuart McCullochf3173222012-06-07 21:57:32 +0000289
290 InputStream in = getClass().getResourceAsStream(EMBEDDED_REPO);
291 if (in != null)
292 unzip(in, root);
293 else {
Stuart McCullochf3173222012-06-07 21:57:32 +0000294 error("Couldn't find embedded-repo.jar in bundle ");
295 }
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000296 return true;
297 } else
298 return false;
Stuart McCulloch4482c702012-06-15 13:27:53 +0000299 }
300 finally {
Stuart McCullochf3173222012-06-07 21:57:32 +0000301 lock.unlock();
302 }
303 }
304
305 void unzip(InputStream in, File dir) throws Exception {
306 try {
307 JarInputStream jin = new JarInputStream(in);
308 JarEntry jentry = jin.getNextJarEntry();
309 while (jentry != null) {
310 if (!jentry.isDirectory()) {
311 File dest = Processor.getFile(dir, jentry.getName());
Stuart McCulloch4482c702012-06-15 13:27:53 +0000312 if (!dest.isFile() || dest.lastModified() < jentry.getTime() || jentry.getTime() == 0) {
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000313 File dp = dest.getParentFile();
314 if (!dp.exists() && !dp.mkdirs()) {
315 throw new IOException("Could not create directory " + dp);
316 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000317 FileOutputStream out = new FileOutputStream(dest);
318 try {
319 copy(jin, out);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000320 }
321 finally {
Stuart McCullochf3173222012-06-07 21:57:32 +0000322 out.close();
323 }
324 }
325 }
326 jentry = jin.getNextJarEntry();
327 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000328 }
329 finally {
Stuart McCullochf3173222012-06-07 21:57:32 +0000330 in.close();
331 }
332 }
333 }
334
335 public List<RepositoryPlugin> getRepositories() {
336 return getPlugins(RepositoryPlugin.class);
337 }
338
339 public Collection<Project> getBuildOrder() throws Exception {
340 List<Project> result = new ArrayList<Project>();
341 for (Project project : getAllProjects()) {
342 Collection<Project> dependsOn = project.getDependson();
343 getBuildOrder(dependsOn, result);
344 if (!result.contains(project)) {
345 result.add(project);
346 }
347 }
348 return result;
349 }
350
351 private void getBuildOrder(Collection<Project> dependsOn, List<Project> result) throws Exception {
352 for (Project project : dependsOn) {
353 Collection<Project> subProjects = project.getDependson();
354 for (Project subProject : subProjects) {
355 if (!result.contains(subProject)) {
356 result.add(subProject);
357 }
358 }
359 if (!result.contains(project)) {
360 result.add(project);
361 }
362 }
363 }
364
365 public static Workspace getWorkspace(String path) throws Exception {
366 File file = IO.getFile(new File(""), path);
367 return getWorkspace(file);
368 }
369
370 public Maven getMaven() {
371 return maven;
372 }
373
Stuart McCulloch4482c702012-06-15 13:27:53 +0000374 @Override
375 protected void setTypeSpecificPlugins(Set<Object> list) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000376 super.setTypeSpecificPlugins(list);
377 list.add(maven);
378 list.add(new CachedFileRepo());
379 }
380
Stuart McCulloch1b98aa02012-06-18 11:15:15 +0000381 /**
382 * Return if we're in offline mode. Offline mode is defined as an
383 * environment where nobody tells us the resources are out of date (refresh
384 * or changed). This is currently defined as having bndlisteners.
385 *
386 * @return
387 */
388 public boolean isOffline() {
389 return offline;
390 }
391
392 public Workspace setOffline(boolean on) {
393 this.offline = on;
394 return this;
395 }
Stuart McCulloch2a0afd62012-09-06 18:28:06 +0000396
397 /**
398 * Provide access to the global settings of this machine.
399 *
400 * @throws Exception
401 * @throws UnknownHostException
402 */
403
404 public String _global(String[] args) throws Exception {
405 Macro.verifyCommand(args, "${global;<name>[;<default>]}, get a global setting from ~/.bnd/settings.json", null,
406 2, 3);
407
408 String key = args[1];
409 if (key.equals("key.public"))
410 return Hex.toHexString(settings.getPublicKey());
411 if (key.equals("key.private"))
412 return Hex.toHexString(settings.getPrivateKey());
413
414 String s = settings.get(key);
415 if (s != null)
416 return s;
417
418 if (args.length == 3)
419 return args[2];
420
421 return null;
422 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000423}