blob: c274ca549f78a44c08cffedb586d08707f3022c3 [file] [log] [blame]
Stuart McCulloch26e7a5a2011-10-17 10:31:43 +00001package aQute.bnd.maven;
2
3import java.io.*;
4import java.net.*;
5import java.util.*;
6import java.util.concurrent.*;
7import java.util.jar.*;
8import java.util.regex.*;
9
10import aQute.bnd.maven.support.*;
11import aQute.bnd.maven.support.Pom.*;
12import aQute.bnd.settings.*;
13import aQute.lib.collections.*;
14import aQute.lib.io.*;
15import aQute.lib.osgi.*;
16import aQute.libg.command.*;
17import aQute.libg.header.*;
18
19public class MavenCommand extends Processor {
20 final Settings settings = new Settings();
21 File temp;
22
23 public MavenCommand() {
24 }
25
26 public MavenCommand(Processor p) {
27 super(p);
28 }
29
30 /**
31 * maven deploy [-url repo] [-passphrase passphrase] [-homedir homedir]
32 * [-keyname keyname] bundle ...
33 *
34 * @param args
35 * @param i
36 * @throws Exception
37 */
38 public void run(String args[], int i) throws Exception {
39 temp = new File("maven-bundle");
40
41 if (i >= args.length) {
42 help();
43 return;
44 }
45
46 while (i < args.length && args[i].startsWith("-")) {
47 String option = args[i];
48 trace("option " + option);
49 if (option.equals("-temp"))
50 temp = getFile(args[++i]);
51 else {
52 help();
53 error("Invalid option " + option);
54 }
55 i++;
56 }
57
58 String cmd = args[i++];
59
60 trace("temp dir " + temp);
61 IO.delete(temp);
62 temp.mkdirs();
63 if (!temp.isDirectory())
64 throw new IOException("Cannot create temp directory");
65
66 if (cmd.equals("settings"))
67 settings();
68 else if (cmd.equals("help"))
69 help();
70 else if (cmd.equals("bundle"))
71 bundle(args, i);
72 else if (cmd.equals("view"))
73 view(args, i);
74 else
75 error("No such command %s, type help", cmd);
76 }
77
78 private void help() {
79 System.err.println("Usage:\n");
80 System.err
81 .println(" maven \n" //
82 + " [-temp <dir>] use as temp directory\n" //
83 + " settings show maven settings\n" //
84 + " bundle turn a bundle into a maven bundle\n" //
85 + " [-properties <file>] provide properties, properties starting with javadoc are options for javadoc, like javadoc-tag=...\n"
86 + " [-javadoc <file|url>] where to find the javadoc (zip/dir), otherwise generated\n" //
87 + " [-source <file|url>] where to find the source (zip/dir), otherwise from OSGI-OPT/src\n" //
88 + " [-scm <url>] required scm in pom, otherwise from Bundle-SCM\n" //
89 + " [-url <url>] required project url in pom\n" //
90 + " [-bsn bsn] overrides bsn\n" //
91 + " [-version <version>] overrides version\n" //
92 + " [-developer <email>] developer email\n" //
93 + " [-nodelete] do not delete temp files\n" //
94 + " [-passphrase <gpgp passphrase>] signer password\n"//
95 + " <file|url> ");
96 }
97
98 /**
99 * Show the maven settings
100 *
101 * @throws FileNotFoundException
102 * @throws Exception
103 */
104 private void settings() throws FileNotFoundException, Exception {
105 File userHome = new File(System.getProperty("user.home"));
106 File m2 = new File(userHome, ".m2");
107 if (!m2.isDirectory()) {
108 error("There is no m2 directory at %s", userHome);
109 return;
110 }
111 File settings = new File(m2, "settings.xml");
112 if (!settings.isFile()) {
113 error("There is no settings file at '%s'", settings.getAbsolutePath());
114 return;
115 }
116
117 FileReader rdr = new FileReader(settings);
118
119 LineCollection lc = new LineCollection(new BufferedReader(rdr));
120 while (lc.hasNext()) {
121 System.out.println(lc.next());
122 }
123 }
124
125 /**
126 * Create a maven bundle.
127 *
128 * @param args
129 * @param i
130 * @throws Exception
131 */
132 private void bundle(String args[], int i) throws Exception {
133 List<String> developers = new ArrayList<String>();
134 Properties properties = new Properties();
135
136 String scm = null;
137 String passphrase = null;
138 String javadoc = null;
139 String source = null;
140 String output = "bundle.jar";
141 String url = null;
142 String artifact = null;
143 String group = null;
144 String version = null;
145 boolean nodelete = false;
146
147 while (i < args.length && args[i].startsWith("-")) {
148 String option = args[i++];
149 trace("bundle option %s", option);
150 if (option.equals("-scm"))
151 scm = args[i++];
152 else if (option.equals("-group"))
153 group = args[i++];
154 else if (option.equals("-artifact"))
155 artifact = args[i++];
156 else if (option.equals("-version"))
157 version = args[i++];
158 else if (option.equals("-developer"))
159 developers.add(args[i++]);
160 else if (option.equals("-passphrase")) {
161 passphrase = args[i++];
162 } else if (option.equals("-url")) {
163 url = args[i++];
164 } else if (option.equals("-javadoc"))
165 javadoc = args[i++];
166 else if (option.equals("-source"))
167 source = args[i++];
168 else if (option.equals("-output"))
169 output = args[i++];
170 else if (option.equals("-nodelete"))
171 nodelete=true;
172 else if (option.startsWith("-properties")) {
173 InputStream in = new FileInputStream(args[i++]);
174 try {
175 properties.load(in);
176 } catch (Exception e) {
177 in.close();
178 }
179 }
180 }
181
182 if (developers.isEmpty()) {
183 String email = settings.globalGet(Settings.EMAIL, null);
184 if (email == null)
185 error("No developer email set, you can set global default email with: bnd global email Peter.Kriens@aQute.biz");
186 else
187 developers.add(email);
188 }
189
190 if (i == args.length) {
191 error("too few arguments, no bundle specified");
192 return;
193 }
194
195 if (i != args.length - 1) {
196 error("too many arguments, only one bundle allowed");
197 return;
198 }
199
200 String input = args[i++];
201
202 Jar binaryJar = getJarFromFileOrURL(input);
203 trace("got %s", binaryJar);
204 if (binaryJar == null) {
205 error("JAR does not exist: %s", input);
206 return;
207 }
208
209 File original = getFile(temp, "original");
210 original.mkdirs();
211 binaryJar.expand(original);
212 binaryJar.calcChecksums(null);
213
214 Manifest manifest = binaryJar.getManifest();
215 trace("got manifest");
216
217 PomFromManifest pom = new PomFromManifest(manifest);
218
219 if (scm != null)
220 pom.setSCM(scm);
221 if (url != null)
222 pom.setURL(url);
223 if (artifact != null)
224 pom.setArtifact(artifact);
225 if (artifact != null)
226 pom.setGroup(group);
227 if (version != null)
228 pom.setVersion(version);
229 trace(url);
230 for (String d : developers)
231 pom.addDeveloper(d);
232
233 Set<String> exports = OSGiHeader.parseHeader(
234 manifest.getMainAttributes().getValue(Constants.EXPORT_PACKAGE)).keySet();
235
236 Jar sourceJar;
237 if (source == null) {
238 trace("Splitting source code");
239 sourceJar = new Jar("source");
240 for (Map.Entry<String, Resource> entry : binaryJar.getResources().entrySet()) {
241 if (entry.getKey().startsWith("OSGI-OPT/src")) {
242 sourceJar.putResource(entry.getKey().substring("OSGI-OPT/src/".length()),
243 entry.getValue());
244 }
245 }
246 copyInfo(binaryJar, sourceJar, "source");
247 } else {
248 sourceJar = getJarFromFileOrURL(source);
249 }
250 sourceJar.calcChecksums(null);
251
252 Jar javadocJar;
253 if (javadoc == null) {
254 trace("creating javadoc because -javadoc not used");
255 javadocJar = javadoc(getFile(original, "OSGI-OPT/src"), exports, manifest, properties);
256 if (javadocJar == null) {
257 error("Cannot find source code in OSGI-OPT/src to generate Javadoc");
258 return;
259 }
260 copyInfo(binaryJar, javadocJar, "javadoc");
261 } else {
262 trace("Loading javadoc externally %s", javadoc);
263 javadocJar = getJarFromFileOrURL(javadoc);
264 }
265 javadocJar.calcChecksums(null);
266
267 addClose(binaryJar);
268 addClose(sourceJar);
269 addClose(javadocJar);
270
271 trace("creating bundle dir");
272 File bundle = new File(temp, "bundle");
273 bundle.mkdirs();
274
275 String prefix = pom.getArtifactId() + "-" + pom.getVersion();
276 File binaryFile = new File(bundle, prefix + ".jar");
277 File sourceFile = new File(bundle, prefix + "-sources.jar");
278 File javadocFile = new File(bundle, prefix + "-javadoc.jar");
279 File pomFile = new File(bundle, "pom.xml").getAbsoluteFile();
280 trace("creating output files %s, %s,%s, and %s", binaryFile, sourceFile, javadocFile,
281 pomFile);
282
283 IO.copy(pom.openInputStream(), pomFile);
284 trace("copied pom");
285
286 trace("writing binary %s", binaryFile);
287 binaryJar.write(binaryFile);
288
289 trace("writing source %s", sourceFile);
290 sourceJar.write(sourceFile);
291
292 trace("writing javadoc %s", javadocFile);
293 javadocJar.write(javadocFile);
294
295 sign(binaryFile, passphrase);
296 sign(sourceFile, passphrase);
297 sign(javadocFile, passphrase);
298 sign(pomFile, passphrase);
299
300 trace("create bundle");
301 Jar bundleJar = new Jar(bundle);
302 addClose(bundleJar);
303 File outputFile = getFile(output);
304 bundleJar.write(outputFile);
305 trace("created bundle %s", outputFile);
306
307 binaryJar.close();
308 sourceJar.close();
309 javadocJar.close();
310 bundleJar.close();
311 if (!nodelete)
312 IO.delete(temp);
313 }
314
315 private void copyInfo(Jar source, Jar dest, String type) throws Exception {
316 source.ensureManifest();
317 dest.ensureManifest();
318 copyInfoResource( source, dest, "LICENSE");
319 copyInfoResource( source, dest, "LICENSE.html");
320 copyInfoResource( source, dest, "about.html");
321
322 Manifest sm = source.getManifest();
323 Manifest dm = dest.getManifest();
324 copyInfoHeader( sm, dm, "Bundle-Description","");
325 copyInfoHeader( sm, dm, "Bundle-Vendor","");
326 copyInfoHeader( sm, dm, "Bundle-Copyright","");
327 copyInfoHeader( sm, dm, "Bundle-DocURL","");
328 copyInfoHeader( sm, dm, "Bundle-License","");
329 copyInfoHeader( sm, dm, "Bundle-Name", " " + type);
330 copyInfoHeader( sm, dm, "Bundle-SymbolicName", "." + type);
331 copyInfoHeader( sm, dm, "Bundle-Version", "");
332 }
333
334 private void copyInfoHeader(Manifest sm, Manifest dm, String key, String value) {
335 String v = sm.getMainAttributes().getValue(key);
336 if ( v == null) {
337 trace("no source for " + key);
338 return;
339 }
340
341 if ( dm.getMainAttributes().getValue(key) != null) {
342 trace("already have " + key );
343 return;
344 }
345
346 dm.getMainAttributes().putValue(key, v + value);
347 }
348
349 private void copyInfoResource(Jar source, Jar dest, String type) {
350 if ( source.getResources().containsKey(type) && !dest.getResources().containsKey(type))
351 dest.putResource(type, source.getResource(type));
352 }
353
354 /**
355 * @return
356 * @throws IOException
357 * @throws MalformedURLException
358 */
359 protected Jar getJarFromFileOrURL(String spec) throws IOException, MalformedURLException {
360 Jar jar;
361 File jarFile = getFile(spec);
362 if (jarFile.exists()) {
363 jar = new Jar(jarFile);
364 } else {
365 URL url = new URL(spec);
366 InputStream in = url.openStream();
367 try {
368 jar = new Jar(url.getFile(), in);
369 } finally {
370 in.close();
371 }
372 }
373 addClose(jar);
374 return jar;
375 }
376
377 private void sign(File file, String passphrase) throws Exception {
378 trace("signing %s", file);
379 File asc = new File(file.getParentFile(), file.getName() + ".asc");
380 asc.delete();
381
382 Command command = new Command();
383 command.setTrace();
384
385 command.add(getProperty("gpgp", "gpg"));
386 if (passphrase != null)
387 command.add("--passphrase", passphrase);
388 command.add("-ab", "--sign"); // not the -b!!
389 command.add(file.getAbsolutePath());
390 System.out.println(command);
391 StringBuffer stdout = new StringBuffer();
392 StringBuffer stderr = new StringBuffer();
393 int result = command.execute(stdout, stderr);
394 if (result != 0) {
395 error("gpg signing %s failed because %s", file, "" + stdout + stderr);
396 }
397 }
398
399 private Jar javadoc(File source, Set<String> exports, Manifest m, Properties p)
400 throws Exception {
401 File tmp = new File(temp, "javadoc");
402 tmp.mkdirs();
403
404 Command command = new Command();
405 command.add(getProperty("javadoc", "javadoc"));
406 command.add("-quiet");
407 command.add("-protected");
408 // command.add("-classpath");
409 // command.add(binary.getAbsolutePath());
410 command.add("-d");
411 command.add(tmp.getAbsolutePath());
412 command.add("-charset");
413 command.add("UTF-8");
414 command.add("-sourcepath");
415 command.add(source.getAbsolutePath());
416
417 Attributes attr = m.getMainAttributes();
418 Properties pp = new Properties(p);
419 set(pp, "-doctitle", description(attr));
420 set(pp, "-header", description(attr));
421 set(pp, "-windowtitle", name(attr));
422 set(pp, "-bottom", copyright(attr));
423 set(pp, "-footer", license(attr));
424
425 command.add("-tag");
426 command.add("Immutable:t:Immutable");
427 command.add("-tag");
428 command.add("ThreadSafe:t:ThreadSafe");
429 command.add("-tag");
430 command.add("NotThreadSafe:t:NotThreadSafe");
431 command.add("-tag");
432 command.add("GuardedBy:mf:Guarded By:");
433 command.add("-tag");
434 command.add("security:m:Required Permissions");
435 command.add("-tag");
436 command.add("noimplement:t:Consumers of this API must not implement this interface");
437
438 for (Enumeration<?> e = pp.propertyNames(); e.hasMoreElements();) {
439 String key = (String) e.nextElement();
440 String value = pp.getProperty(key);
441
442 if (key.startsWith("javadoc")) {
443 key = key.substring("javadoc".length());
444 removeDuplicateMarker(key);
445 command.add(key);
446 command.add(value);
447 }
448 }
449 for (String packageName : exports) {
450 command.add(packageName);
451 }
452
453 StringBuffer out = new StringBuffer();
454 StringBuffer err = new StringBuffer();
455
456 System.out.println(command);
457
458 int result = command.execute(out, err);
459 if (result != 0) {
460 warning("Error during execution of javadoc command: %s\n******************\n%s", out,
461 err);
462 }
463 Jar jar = new Jar(tmp);
464 addClose(jar);
465 return jar;
466 }
467
468 /**
469 * Generate a license string
470 *
471 * @param attr
472 * @return
473 */
474 private String license(Attributes attr) {
475 Map<String, Map<String, String>> map = Processor.parseHeader(
476 attr.getValue(Constants.BUNDLE_LICENSE), null);
477 if (map.isEmpty())
478 return null;
479
480 StringBuilder sb = new StringBuilder();
481 String sep = "Licensed under ";
482 for (Map.Entry<String, Map<String, String>> entry : map.entrySet()) {
483 sb.append(sep);
484 String key = entry.getKey();
485 String link = entry.getValue().get("link");
486 String description = entry.getValue().get("description");
487
488 if (description == null)
489 description = key;
490
491 if (link != null) {
492 sb.append("<a href='");
493 sb.append(link);
494 sb.append("'>");
495 }
496 sb.append(description);
497 if (link != null) {
498 sb.append("</a>");
499 }
500 sep = ",<br/>";
501 }
502
503 return sb.toString();
504 }
505
506 /**
507 * Generate the copyright statement.
508 *
509 * @param attr
510 * @return
511 */
512 private String copyright(Attributes attr) {
513 return attr.getValue(Constants.BUNDLE_COPYRIGHT);
514 }
515
516 private String name(Attributes attr) {
517 String name = attr.getValue(Constants.BUNDLE_NAME);
518 if (name == null)
519 name = attr.getValue(Constants.BUNDLE_SYMBOLICNAME);
520 return name;
521 }
522
523 private String description(Attributes attr) {
524 String descr = attr.getValue(Constants.BUNDLE_DESCRIPTION);
525 if (descr == null)
526 descr = attr.getValue(Constants.BUNDLE_NAME);
527 if (descr == null)
528 descr = attr.getValue(Constants.BUNDLE_SYMBOLICNAME);
529 return descr;
530 }
531
532 private void set(Properties pp, String option, String defaultValue) {
533 String key = "javadoc" + option;
534 String existingValue = pp.getProperty(key);
535 if (existingValue != null)
536 return;
537
538 pp.setProperty(key, defaultValue);
539 }
540
541
542 /**
543 * View - Show the dependency details of an artifact
544 */
545
546
547 static Executor executor = Executors.newCachedThreadPool();
548 static Pattern GROUP_ARTIFACT_VERSION = Pattern.compile("([^+]+)\\+([^+]+)\\+([^+]+)");
549 void view( String args[], int i) throws Exception {
550 Maven maven = new Maven(executor);
551 OutputStream out = System.out;
552
553 List<URI> urls = new ArrayList<URI>();
554
555 while ( i < args.length && args[i].startsWith("-")) {
556 if( "-r".equals(args[i])) {
557 URI uri = new URI(args[++i]);
558 urls.add( uri );
559 System.out.println("URI for repo " + uri);
560 }
561 else
562 if ( "-o".equals(args[i])) {
563 out = new FileOutputStream(args[++i]);
564 }
565 else
566 throw new IllegalArgumentException("Unknown option: " + args[i]);
567
568 i++;
569 }
570
571 URI[] urls2 = urls.toArray(new URI[urls.size()]);
572 PrintWriter pw = new PrintWriter(out);
573
574 while ( i < args.length) {
575 String ref = args[i++];
576 pw.println("Ref " + ref);
577
578 Matcher matcher = GROUP_ARTIFACT_VERSION.matcher(ref);
579 if (matcher.matches()) {
580
581 String group = matcher.group(1);
582 String artifact = matcher.group(2);
583 String version = matcher.group(3);
584 CachedPom pom = maven.getPom(group, artifact, version, urls2);
585
586 Builder a = new Builder();
587 a.setProperty("Private-Package", "*");
588 Set<Pom> dependencies = pom.getDependencies(Scope.compile, urls2);
589 for ( Pom dep : dependencies ) {
590 System.out.printf( "%20s %-20s %10s\n", dep.getGroupId(), dep.getArtifactId(), dep.getVersion());
591 a.addClasspath(dep.getArtifact());
592 }
593 pw.println(a.getClasspath());
594 a.build();
595
596 TreeSet<String> sorted = new TreeSet<String>( a.getImports().keySet());
597 for ( String p :sorted) {
598 pw.printf("%-40s\n",p);
599 }
600// for ( Map.Entry<String, Set<String>> entry : a.getUses().entrySet()) {
601// String from = entry.getKey();
602// for ( String uses : entry.getValue()) {
603// System.out.printf("%40s %s\n", from, uses);
604// from = "";
605// }
606// }
607 a.close();
608 } else
609 System.err.println("Wrong, must look like group+artifact+version, is " + ref);
610
611 }
612 }
613
614
615}