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