001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018: package org.apache.ivy;
019:
020: import java.io.File;
021: import java.io.FileOutputStream;
022: import java.io.IOException;
023: import java.io.PrintWriter;
024: import java.lang.reflect.InvocationTargetException;
025: import java.lang.reflect.Method;
026: import java.net.MalformedURLException;
027: import java.net.URL;
028: import java.net.URLClassLoader;
029: import java.util.ArrayList;
030: import java.util.Arrays;
031: import java.util.Collection;
032: import java.util.Collections;
033: import java.util.Iterator;
034: import java.util.LinkedHashSet;
035: import java.util.List;
036: import java.util.StringTokenizer;
037:
038: import org.apache.commons.cli.CommandLine;
039: import org.apache.commons.cli.CommandLineParser;
040: import org.apache.commons.cli.GnuParser;
041: import org.apache.commons.cli.HelpFormatter;
042: import org.apache.commons.cli.Option;
043: import org.apache.commons.cli.OptionBuilder;
044: import org.apache.commons.cli.Options;
045: import org.apache.commons.cli.ParseException;
046: import org.apache.ivy.core.cache.ResolutionCacheManager;
047: import org.apache.ivy.core.deliver.DeliverOptions;
048: import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
049: import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
050: import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
051: import org.apache.ivy.core.module.id.ModuleRevisionId;
052: import org.apache.ivy.core.publish.PublishOptions;
053: import org.apache.ivy.core.report.ArtifactDownloadReport;
054: import org.apache.ivy.core.report.ResolveReport;
055: import org.apache.ivy.core.resolve.ResolveOptions;
056: import org.apache.ivy.core.retrieve.RetrieveOptions;
057: import org.apache.ivy.core.settings.IvySettings;
058: import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorWriter;
059: import org.apache.ivy.plugins.report.XmlReportParser;
060: import org.apache.ivy.util.DefaultMessageLogger;
061: import org.apache.ivy.util.Message;
062: import org.apache.ivy.util.url.CredentialsStore;
063: import org.apache.ivy.util.url.URLHandler;
064: import org.apache.ivy.util.url.URLHandlerDispatcher;
065: import org.apache.ivy.util.url.URLHandlerRegistry;
066:
067: /**
068: * Class used to launch ivy as a standalone tool.
069: * <p>
070: * Valid arguments can be obtained with the -? argument.
071: */
072: public final class Main {
073: private static final int DEPENDENCY_ARG_COUNT = 3;
074:
075: private static Options getOptions() {
076: Option settings = OptionBuilder.withArgName("settingsfile")
077: .hasArg()
078: .withDescription("use given file for settings").create(
079: "settings");
080: Option conf = OptionBuilder.withArgName("settingsfile")
081: .hasArg().withDescription(
082: "DEPRECATED - use given file for settings")
083: .create("conf");
084: Option cache = OptionBuilder.withArgName("cachedir").hasArg()
085: .withDescription("use given directory for cache")
086: .create("cache");
087: Option ivyfile = OptionBuilder.withArgName("ivyfile").hasArg()
088: .withDescription("use given file as ivy file").create(
089: "ivy");
090: Option dependency = OptionBuilder
091: .withArgName("organisation module revision")
092: .hasArgs()
093: .withDescription(
094: "use this instead of ivy file to do the rest "
095: + "of the work with this as a dependency.")
096: .create("dependency");
097: Option confs = OptionBuilder.withArgName("configurations")
098: .hasArgs().withDescription(
099: "resolve given configurations").create("confs");
100: Option retrieve = OptionBuilder.withArgName("retrievepattern")
101: .hasArg().withDescription(
102: "use given pattern as retrieve pattern")
103: .create("retrieve");
104: Option cachepath = OptionBuilder
105: .withArgName("cachepathfile")
106: .hasArg()
107: .withDescription(
108: "outputs a classpath consisting of all dependencies in cache "
109: + "(including transitive ones) "
110: + "of the given ivy file to the given cachepathfile")
111: .create("cachepath");
112: Option revision = OptionBuilder.withArgName("revision")
113: .hasArg().withDescription(
114: "use given revision to publish the module")
115: .create("revision");
116: Option status = OptionBuilder.withArgName("status").hasArg()
117: .withDescription(
118: "use given status to publish the module")
119: .create("status");
120: Option deliver = OptionBuilder
121: .withArgName("ivypattern")
122: .hasArg()
123: .withDescription(
124: "use given pattern as resolved ivy file pattern")
125: .create("deliverto");
126: Option publishResolver = OptionBuilder.withArgName(
127: "resolvername").hasArg().withDescription(
128: "use given resolver to publish to").create("publish");
129: Option publishPattern = OptionBuilder
130: .withArgName("artpattern")
131: .hasArg()
132: .withDescription(
133: "use given pattern to find artifacts to publish")
134: .create("publishpattern");
135: Option realm = OptionBuilder.withArgName("realm").hasArg()
136: .withDescription("use given realm for HTTP AUTH")
137: .create("realm");
138: Option host = OptionBuilder.withArgName("host").hasArg()
139: .withDescription("use given host for HTTP AUTH")
140: .create("host");
141: Option username = OptionBuilder.withArgName("username")
142: .hasArg().withDescription(
143: "use given username for HTTP AUTH").create(
144: "username");
145: Option passwd = OptionBuilder.withArgName("passwd").hasArg()
146: .withDescription("use given password for HTTP AUTH")
147: .create("passwd");
148: Option main = OptionBuilder.withArgName("main").hasArg()
149: .withDescription("the main class to runtime process")
150: .create("main");
151: Option args = OptionBuilder.withArgName("args").hasArgs()
152: .withDescription("the arguments to runtime process")
153: .create("args");
154: Option cp = OptionBuilder
155: .withArgName("cp")
156: .hasArg()
157: .withDescription(
158: "extra classpath, used only in combination with option main")
159: .create("cp");
160:
161: Options options = new Options();
162:
163: options.addOption("debug", false, "set message level to debug");
164: options.addOption("verbose", false,
165: "set message level to verbose");
166: options.addOption("warn", false, "set message level to warn");
167: options.addOption("error", false, "set message level to error");
168: options.addOption("novalidate", false,
169: "do not validate ivy files against xsd");
170: options
171: .addOption(
172: "useOrigin",
173: false,
174: "DEPRECATED: use original artifact location "
175: + "with local resolvers instead of copying to the cache");
176: options
177: .addOption("sync", false,
178: "in conjonction with -retrieve, does a synced retrieve");
179: options.addOption("m2compatible", false,
180: "use maven2 compatibility");
181: options.addOption("?", false, "display this help");
182: options.addOption(conf);
183: options.addOption(settings);
184: options.addOption(confs);
185: options.addOption(cache);
186: options.addOption(ivyfile);
187: options.addOption(dependency);
188: options.addOption(retrieve);
189: options.addOption(cachepath);
190: options.addOption(revision);
191: options.addOption(status);
192: options.addOption(deliver);
193: options.addOption(publishResolver);
194: options.addOption(publishPattern);
195: options.addOption(realm);
196: options.addOption(host);
197: options.addOption(username);
198: options.addOption(passwd);
199: options.addOption(main);
200: options.addOption(args);
201: options.addOption(cp);
202:
203: return options;
204: }
205:
206: public static void main(String[] args) throws Exception {
207: Options options = getOptions();
208:
209: CommandLineParser parser = new GnuParser();
210: try {
211: // parse the command line arguments
212: CommandLine line = parser.parse(options, args);
213:
214: if (line.hasOption("?")) {
215: usage(options);
216: return;
217: }
218:
219: boolean validate = line.hasOption("novalidate") ? false
220: : true;
221:
222: Ivy ivy = Ivy.newInstance();
223: initMessage(line, ivy);
224: IvySettings settings = initSettings(line, options, ivy);
225:
226: File cache = new File(settings.substitute(line
227: .getOptionValue("cache", settings.getDefaultCache()
228: .getAbsolutePath())));
229: if (!cache.exists()) {
230: cache.mkdirs();
231: } else if (!cache.isDirectory()) {
232: error(options, cache + " is not a directory");
233: }
234:
235: String[] confs;
236: if (line.hasOption("confs")) {
237: confs = line.getOptionValues("confs");
238: } else {
239: confs = new String[] { "*" };
240: }
241:
242: File ivyfile;
243: if (line.hasOption("dependency")) {
244: String[] dep = line.getOptionValues("dependency");
245: if (dep.length != DEPENDENCY_ARG_COUNT) {
246: error(options,
247: "dependency should be expressed with exactly 3 arguments: "
248: + "organisation module revision");
249: }
250: ivyfile = File.createTempFile("ivy", ".xml");
251: ivyfile.deleteOnExit();
252: DefaultModuleDescriptor md = DefaultModuleDescriptor
253: .newDefaultInstance(ModuleRevisionId
254: .newInstance(dep[0],
255: dep[1] + "-caller", "working"));
256: DefaultDependencyDescriptor dd = new DefaultDependencyDescriptor(
257: md, ModuleRevisionId.newInstance(dep[0],
258: dep[1], dep[2]), false, false, true);
259: for (int i = 0; i < confs.length; i++) {
260: dd.addDependencyConfiguration("default", confs[i]);
261: }
262: md.addDependency(dd);
263: XmlModuleDescriptorWriter.write(md, ivyfile);
264: confs = new String[] { "default" };
265: } else {
266: ivyfile = new File(settings.substitute(line
267: .getOptionValue("ivy", "ivy.xml")));
268: if (!ivyfile.exists()) {
269: error(options, "ivy file not found: " + ivyfile);
270: } else if (ivyfile.isDirectory()) {
271: error(options, "ivy file is not a file: " + ivyfile);
272: }
273: }
274:
275: if (line.hasOption("useOrigin")) {
276: ivy.getSettings().useDeprecatedUseOrigin();
277: }
278: ResolveOptions resolveOptions = new ResolveOptions()
279: .setConfs(confs).setValidate(validate);
280: ResolveReport report = ivy.resolve(ivyfile.toURL(),
281: resolveOptions);
282: if (report.hasError()) {
283: System.exit(1);
284: }
285: ModuleDescriptor md = report.getModuleDescriptor();
286:
287: if (confs.length == 1 && "*".equals(confs[0])) {
288: confs = md.getConfigurationsNames();
289: }
290: if (line.hasOption("retrieve")) {
291: String retrievePattern = settings.substitute(line
292: .getOptionValue("retrieve"));
293: if (retrievePattern.indexOf("[") == -1) {
294: retrievePattern = retrievePattern
295: + "/lib/[conf]/[artifact].[ext]";
296: }
297: ivy.retrieve(md.getModuleRevisionId(), retrievePattern,
298: new RetrieveOptions().setConfs(confs).setSync(
299: line.hasOption("sync")).setUseOrigin(
300: line.hasOption("useOrigin")));
301: }
302: if (line.hasOption("cachepath")) {
303: outputCachePath(ivy, cache, md, confs,
304: line.getOptionValue("cachepath",
305: "ivycachepath.txt"));
306: }
307:
308: if (line.hasOption("revision")) {
309: ivy.deliver(md.getResolvedModuleRevisionId(), settings
310: .substitute(line.getOptionValue("revision")),
311: settings.substitute(line.getOptionValue(
312: "deliverto", "ivy-[revision].xml")),
313: DeliverOptions.newInstance(settings).setStatus(
314: settings.substitute(line
315: .getOptionValue("status",
316: "release")))
317: .setValidate(validate));
318: if (line.hasOption("publish")) {
319: ivy
320: .publish(
321: md.getResolvedModuleRevisionId(),
322: Collections
323: .singleton(settings
324: .substitute(line
325: .getOptionValue(
326: "publishpattern",
327: "distrib/[type]s/[artifact]-[revision].[ext]"))),
328: line.getOptionValue("publish"),
329: new PublishOptions()
330: .setPubrevision(
331: settings
332: .substitute(line
333: .getOptionValue("revision")))
334: .setValidate(validate)
335: .setSrcIvyPattern(
336: settings
337: .substitute(line
338: .getOptionValue(
339: "deliverto",
340: "ivy-[revision].xml"))));
341: }
342: }
343: if (line.hasOption("main")) {
344: // check if the option cp has been set
345: List fileList = getExtraClasspathFileList(line);
346:
347: // merge -args and left over args
348: String[] fargs = line.getOptionValues("args");
349: if (fargs == null) {
350: fargs = new String[0];
351: }
352: String[] extra = line.getArgs();
353: if (extra == null) {
354: extra = new String[0];
355: }
356: String[] params = new String[fargs.length
357: + extra.length];
358: System.arraycopy(fargs, 0, params, 0, fargs.length);
359: System.arraycopy(extra, 0, params, fargs.length,
360: extra.length);
361: // invoke with given main class and merged params
362: invoke(ivy, cache, md, confs, fileList, line
363: .getOptionValue("main"), params);
364: }
365: ivy.getLoggerEngine().popLogger();
366: } catch (ParseException exp) {
367: // oops, something went wrong
368: System.err.println("Parsing failed. Reason: "
369: + exp.getMessage());
370:
371: usage(options);
372: }
373: }
374:
375: /**
376: * Parses the <code>cp</code> option from the command line, and returns a list of {@link File}.
377: * <p>
378: * All the files contained in the returned List exist, non existing files are simply skipped
379: * with a warning.
380: * </p>
381: *
382: * @param line
383: * the command line in which the cp option shold be parsed
384: * @return a List of files to include as extra classpath entries, or <code>null</code> if no
385: * cp option was provided.
386: */
387: private static List/*<File>*/getExtraClasspathFileList(
388: CommandLine line) {
389: List fileList = null;
390: if (line.hasOption("cp")) {
391: fileList = new ArrayList/*<File>*/();
392: String[] cpArray = line.getOptionValues("cp");
393: for (int index = 0; index < cpArray.length; index++) {
394: StringTokenizer tokenizer = new StringTokenizer(
395: cpArray[index], System
396: .getProperty("path.separator"));
397: while (tokenizer.hasMoreTokens()) {
398: String token = tokenizer.nextToken();
399: File file = new File(token);
400: if (file.exists()) {
401: fileList.add(file);
402: } else {
403: Message.warn("Skipping extra classpath '"
404: + file + "' as it does not exist.");
405: }
406: }
407: }
408: }
409: return fileList;
410: }
411:
412: private static IvySettings initSettings(CommandLine line,
413: Options options, Ivy ivy) throws java.text.ParseException,
414: IOException {
415: IvySettings settings = ivy.getSettings();
416: settings.addAllVariables(System.getProperties());
417: if (line.hasOption("m2compatible")) {
418: settings.setVariable(
419: "ivy.default.configuration.m2compatible", "true");
420: }
421:
422: configureURLHandler(line.getOptionValue("realm", null), line
423: .getOptionValue("host", null), line.getOptionValue(
424: "username", null), line.getOptionValue("passwd", null));
425:
426: String settingsPath = line.getOptionValue("settings", "");
427: if ("".equals(settingsPath)) {
428: settingsPath = line.getOptionValue("conf", "");
429: if (!"".equals(settingsPath)) {
430: Message
431: .deprecated("-conf is deprecated, use -settings instead");
432: }
433: }
434: if ("".equals(settingsPath)) {
435: ivy.configureDefault();
436: } else {
437: File conffile = new File(settingsPath);
438: if (!conffile.exists()) {
439: error(options, "ivy configuration file not found: "
440: + conffile);
441: } else if (conffile.isDirectory()) {
442: error(options, "ivy configuration file is not a file: "
443: + conffile);
444: }
445: ivy.configure(conffile);
446: }
447: return settings;
448: }
449:
450: private static void initMessage(CommandLine line, Ivy ivy) {
451: if (line.hasOption("debug")) {
452: ivy.getLoggerEngine().pushLogger(
453: new DefaultMessageLogger(Message.MSG_DEBUG));
454: } else if (line.hasOption("verbose")) {
455: ivy.getLoggerEngine().pushLogger(
456: new DefaultMessageLogger(Message.MSG_VERBOSE));
457: } else if (line.hasOption("warn")) {
458: ivy.getLoggerEngine().pushLogger(
459: new DefaultMessageLogger(Message.MSG_WARN));
460: } else if (line.hasOption("error")) {
461: ivy.getLoggerEngine().pushLogger(
462: new DefaultMessageLogger(Message.MSG_ERR));
463: } else {
464: ivy.getLoggerEngine().pushLogger(
465: new DefaultMessageLogger(Message.MSG_INFO));
466: }
467: }
468:
469: private static void outputCachePath(Ivy ivy, File cache,
470: ModuleDescriptor md, String[] confs, String outFile) {
471: try {
472: String pathSeparator = System.getProperty("path.separator");
473: StringBuffer buf = new StringBuffer();
474: Collection all = new LinkedHashSet();
475: ResolutionCacheManager cacheMgr = ivy
476: .getResolutionCacheManager();
477: XmlReportParser parser = new XmlReportParser();
478: for (int i = 0; i < confs.length; i++) {
479: String resolveId = ResolveOptions
480: .getDefaultResolveId(md);
481: File report = cacheMgr
482: .getConfigurationResolveReportInCache(
483: resolveId, confs[i]);
484: parser.parse(report);
485:
486: all.addAll(Arrays.asList(parser.getArtifactReports()));
487: }
488: for (Iterator iter = all.iterator(); iter.hasNext();) {
489: ArtifactDownloadReport artifact = (ArtifactDownloadReport) iter
490: .next();
491: if (artifact.getLocalFile() != null) {
492: buf.append(artifact.getLocalFile()
493: .getCanonicalPath());
494: buf.append(pathSeparator);
495: }
496: }
497:
498: PrintWriter writer = new PrintWriter(new FileOutputStream(
499: outFile));
500: if (buf.length() > 0) {
501: writer.println(buf.substring(0, buf.length()
502: - pathSeparator.length()));
503: }
504: writer.close();
505: System.out.println("cachepath output to " + outFile);
506:
507: } catch (Exception ex) {
508: throw new RuntimeException(
509: "impossible to build ivy cache path: "
510: + ex.getMessage(), ex);
511: }
512: }
513:
514: private static void invoke(Ivy ivy, File cache,
515: ModuleDescriptor md, String[] confs, List fileList,
516: String mainclass, String[] args) {
517: List urls = new ArrayList();
518:
519: // Add option cp (extra classpath) urls
520: if (fileList != null && fileList.size() > 0) {
521: for (Iterator iter = fileList.iterator(); iter.hasNext();) {
522: File file = (File) iter.next();
523: try {
524: urls.add(file.toURL());
525: } catch (MalformedURLException e) {
526: // Should not happen, just ignore.
527: }
528: }
529: }
530:
531: try {
532: Collection all = new LinkedHashSet();
533: ResolutionCacheManager cacheMgr = ivy
534: .getResolutionCacheManager();
535: XmlReportParser parser = new XmlReportParser();
536: for (int i = 0; i < confs.length; i++) {
537: String resolveId = ResolveOptions
538: .getDefaultResolveId(md);
539: File report = cacheMgr
540: .getConfigurationResolveReportInCache(
541: resolveId, confs[i]);
542: parser.parse(report);
543:
544: all.addAll(Arrays.asList(parser.getArtifactReports()));
545: }
546: for (Iterator iter = all.iterator(); iter.hasNext();) {
547: ArtifactDownloadReport artifact = (ArtifactDownloadReport) iter
548: .next();
549:
550: if (artifact.getLocalFile() != null) {
551: urls.add(artifact.getLocalFile().toURL());
552: }
553: }
554: } catch (Exception ex) {
555: throw new RuntimeException(
556: "impossible to build ivy cache path: "
557: + ex.getMessage(), ex);
558: }
559:
560: URLClassLoader classLoader = new URLClassLoader((URL[]) urls
561: .toArray(new URL[urls.size()]), Main.class
562: .getClassLoader());
563:
564: try {
565: Class c = classLoader.loadClass(mainclass);
566:
567: Method mainMethod = c.getMethod("main",
568: new Class[] { String[].class });
569:
570: Thread.currentThread().setContextClassLoader(classLoader);
571: mainMethod
572: .invoke(
573: null,
574: new Object[] { (args == null ? new String[0]
575: : args) });
576: } catch (ClassNotFoundException cnfe) {
577: throw new RuntimeException("Could not find class: "
578: + mainclass, cnfe);
579: } catch (SecurityException e) {
580: throw new RuntimeException("Could not find main method: "
581: + mainclass, e);
582: } catch (NoSuchMethodException e) {
583: throw new RuntimeException("Could not find main method: "
584: + mainclass, e);
585: } catch (IllegalAccessException e) {
586: throw new RuntimeException(
587: "No permissions to invoke main method: "
588: + mainclass, e);
589: } catch (InvocationTargetException e) {
590: throw new RuntimeException(
591: "Unexpected exception invoking main method: "
592: + mainclass, e);
593: }
594: }
595:
596: private static void configureURLHandler(String realm, String host,
597: String username, String passwd) {
598: CredentialsStore.INSTANCE.addCredentials(realm, host, username,
599: passwd);
600:
601: URLHandlerDispatcher dispatcher = new URLHandlerDispatcher();
602: URLHandler httpHandler = URLHandlerRegistry.getHttp();
603: dispatcher.setDownloader("http", httpHandler);
604: dispatcher.setDownloader("https", httpHandler);
605: URLHandlerRegistry.setDefault(dispatcher);
606: }
607:
608: private static void error(Options options, String msg) {
609: System.err.println(msg);
610: usage(options);
611: System.exit(1);
612: }
613:
614: private static void usage(Options options) {
615: // automatically generate the help statement
616: HelpFormatter formatter = new HelpFormatter();
617: formatter.printHelp("ivy", options);
618: }
619:
620: private Main() {
621: }
622: }
|