/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.easyant.tasks; import org.apache.ivy.Ivy; import org.apache.ivy.ant.AntMessageLogger; import org.apache.ivy.core.cache.DefaultRepositoryCacheManager; import org.apache.ivy.core.cache.DefaultResolutionCacheManager; import org.apache.ivy.core.cache.RepositoryCacheManager; import org.apache.ivy.core.cache.ResolutionCacheManager; import org.apache.ivy.core.module.descriptor.Configuration; import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor; import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor; import org.apache.ivy.core.module.descriptor.ModuleDescriptor; import org.apache.ivy.core.module.id.ModuleId; import org.apache.ivy.core.module.id.ModuleRevisionId; import org.apache.ivy.core.report.ArtifactDownloadReport; import org.apache.ivy.core.report.ConfigurationResolveReport; import org.apache.ivy.core.report.ResolveReport; import org.apache.ivy.core.resolve.ResolveOptions; import org.apache.ivy.core.retrieve.RetrieveOptions; import org.apache.ivy.core.settings.IvySettings; import org.apache.ivy.core.sort.SortOptions; import org.apache.ivy.plugins.parser.ModuleDescriptorParser; import org.apache.ivy.plugins.parser.ModuleDescriptorParserRegistry; import org.apache.ivy.plugins.report.XmlReportParser; import org.apache.ivy.plugins.repository.url.URLResource; import org.apache.ivy.plugins.resolver.FileSystemResolver; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import org.apache.tools.ant.Task; import org.apache.tools.ant.taskdefs.Delete; import org.apache.tools.ant.taskdefs.ImportTask; import org.apache.tools.ant.types.Path; import org.apache.tools.ant.types.Path.PathElement; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.text.ParseException; import java.util.*; /** * EXPERIMENTAL, IT IS NOT INTENDED FOR PUBLIC USE * <p/> * An Ant task which resolve some build scripts and import them */ public class ImportAntscripts extends Task { private File ivyfile; private File ivysettings; private Ivy externalIvy; public void setIvyfile(File ivyfile) { this.ivyfile = ivyfile; } public void setIvysettings(File ivysettings) { this.ivysettings = ivysettings; } @Override public void execute() throws BuildException { long startTime = System.currentTimeMillis(); if (getOwningTarget() == null || !"".equals(getOwningTarget().getName())) { throw new BuildException("import only allowed as a top-level task"); } if (ivyfile == null) { throw new BuildException("ivyfile is required"); } if (ivysettings == null) { throw new BuildException("ivysettings is required"); } boolean refresh = false; String refreshValue = getProject().getProperty("easyant.refresh"); if (refreshValue != null) { refresh = Boolean.parseBoolean(refreshValue); } // configure ivy, which may be the local retrieve repo, depending of the setup Ivy ivy = getIvy(); ivy.pushContext(); try { ModuleDescriptor md = getMd(ivy, ivyfile); ArtifactDownloadReport[] artifacts; List<ModuleDescriptor> dependencies = new ArrayList<ModuleDescriptor>(); // first, let's resolve the ant scripts, just the scripts, not their possible jar dependencies, thus only // the configuration "default" XmlReportParser xmlreport = null; if (!refresh) { // first try to relad the resolve from the last report xmlreport = getResolveReport(ivy, md.getModuleRevisionId().getModuleId(), "default", ivyfile); } if (xmlreport != null) { // collect the ant scripts artifacts = xmlreport.getArtifactReports(); // collect the descriptor associated with each ant script ModuleRevisionId[] depIds = xmlreport.getDependencyRevisionIds(); for (ModuleRevisionId depId : depIds) { File depIvyFile = xmlreport.getMetadataArtifactReport(depId).getLocalFile(); dependencies.add(getMd(ivy, depIvyFile)); } } else { // do a full resolve // if in a retrieved setup, clean the repo and repopulate it maybeClearLocalRepo(); maybeRetrieve(md, "default"); // launch the actual resolve ResolveReport resolveReport = resolve(ivy, md, "default"); ConfigurationResolveReport confReport = resolveReport.getConfigurationReport("default"); // collect the ant scripts artifacts = confReport.getAllArtifactsReports(); // collect the descriptor associated with each ant script Set<ModuleRevisionId> mrids = confReport.getModuleRevisionIds(); for (ModuleRevisionId mrid : mrids) { dependencies.add(confReport.getDependency(mrid).getDescriptor()); } } int nbPaths = 1; // save the collection of ant scripts as a path Path antScriptsPath = makePath("easyant.antscripts", sortArtifacts(ivy, artifacts, dependencies)); // now, for each ant script descriptor, search for an ivy configuration which is used by the ant script // itself for (ModuleDescriptor depmd : dependencies) { log("Searching for external conf for " + depmd.getModuleRevisionId(), Project.MSG_VERBOSE); String[] confs = depmd.getConfigurationsNames(); log("configurations for " + depmd.getModuleRevisionId() + " : " + Arrays.toString(confs), Project.MSG_DEBUG); // some trick here: launching a resolve on a module won't resolve the artifacts of the module itself but // only of its dependencies. So we'll create a mock one which will depend on the real one String mockOrg = "_easyant_mocks_"; String mockName = depmd.getModuleRevisionId().getOrganisation() + "__" + depmd.getModuleRevisionId().getName(); ModuleRevisionId mockmrid = ModuleRevisionId.newInstance(mockOrg, mockName, depmd.getModuleRevisionId() .getBranch(), depmd.getRevision(), depmd.getExtraAttributes()); DefaultModuleDescriptor mock = new DefaultModuleDescriptor(mockmrid, depmd.getStatus(), depmd.getPublicationDate(), depmd.isDefault()); DefaultDependencyDescriptor mockdd = new DefaultDependencyDescriptor(depmd.getModuleRevisionId(), false); for (String conf : confs) { mock.addConfiguration(new Configuration(conf)); mockdd.addDependencyConfiguration(conf, conf); } mock.addDependency(mockdd); for (String conf : confs) { if (conf.equals("default")) { continue; } nbPaths++; log("Found configuration " + conf, Project.MSG_VERBOSE); // same process than for the ant script: // * trust the last resolve report // * or launch a full resolve // A full resolve might trigger a retrieve to populate the local repo XmlReportParser xmldepreport = null; if (!refresh) { xmldepreport = getResolveReport(ivy, mock.getModuleRevisionId().getModuleId(), conf, null); } if (xmldepreport != null) { artifacts = xmldepreport.getArtifactReports(); } else { maybeRetrieve(mock, conf); ResolveReport resolveReport = resolve(ivy, mock, conf); ConfigurationResolveReport confReport = resolveReport.getConfigurationReport(conf); artifacts = confReport.getAllArtifactsReports(); } // finally make the resolved artifact a path which can be used by the ant script itself makePath(depmd.getModuleRevisionId().getModuleId().toString() + "[" + conf + "]", Arrays.asList(artifacts)); } } log(nbPaths + " paths resolved in " + (System.currentTimeMillis() - startTime) + "ms.", Project.MSG_VERBOSE); log("Importing " + antScriptsPath.size() + " ant scripts", Project.MSG_VERBOSE); Iterator<?> itScripts = antScriptsPath.iterator(); while (itScripts.hasNext()) { log("\t" + itScripts.next(), Project.MSG_VERBOSE); } ImportTask importTask = new ImportTask(); importTask.setProject(getProject()); importTask.setOwningTarget(getOwningTarget()); importTask.setLocation(getLocation()); importTask.add(antScriptsPath); importTask.execute(); } finally { ivy.popContext(); } } private List<ArtifactDownloadReport> sortArtifacts(Ivy ivy, ArtifactDownloadReport[] artifacts, List<ModuleDescriptor> dependencies) { // first lets map the artifacts to their id Map<ModuleRevisionId, List<ArtifactDownloadReport>> artifactsById = new HashMap<ModuleRevisionId, List<ArtifactDownloadReport>>(); for (ArtifactDownloadReport artifact : artifacts) { List<ArtifactDownloadReport> artifactsForId = artifactsById.get(artifact.getArtifact() .getModuleRevisionId()); if (artifactsForId == null) { artifactsForId = new ArrayList<ArtifactDownloadReport>(); artifactsById.put(artifact.getArtifact().getModuleRevisionId(), artifactsForId); } artifactsForId.add(artifact); } List<ModuleDescriptor> sorted = ivy.sortModuleDescriptors(dependencies, SortOptions.DEFAULT); List<ArtifactDownloadReport> sortedArifacts = new ArrayList<ArtifactDownloadReport>(artifacts.length); for (ModuleDescriptor md : sorted) { List<ArtifactDownloadReport> artifactsForId = artifactsById.get(md.getModuleRevisionId()); if (artifactsForId != null) { sortedArifacts.addAll(artifactsForId); } } return sortedArifacts; } private Ivy getIvy() { Ivy ivy = getLocalRepoIvy(); if (ivy != null) { return ivy; } return getExternalIvy(); } /** * @return the ivy instance corresponding to the ivysettings.xml provided by the end user */ private Ivy getExternalIvy() { if (externalIvy == null) { externalIvy = new Ivy(); try { externalIvy.configure(ivysettings); } catch (ParseException e) { throw new BuildException("Incorrect setup of the ivysettings for easyant (" + e.getMessage() + ")", e); } catch (IOException e) { throw new BuildException("Incorrect setup of the ivysettings for easyant (" + e.getMessage() + ")", e); } AntMessageLogger.register(this, externalIvy); } return externalIvy; } /** * Parse an ivy.xml */ private ModuleDescriptor getMd(Ivy ivy, File file) { URL url; try { url = file.toURI().toURL(); } catch (MalformedURLException e) { throw new BuildException("[easyant bug] a file has not a proper url", e); } URLResource res = new URLResource(url); ModuleDescriptorParser mdparser = ModuleDescriptorParserRegistry.getInstance().getParser(res); ModuleDescriptor md; ivy.pushContext(); try { md = mdparser.parseDescriptor(ivy.getSettings(), url, true); } catch (ParseException e) { throw new BuildException("The file " + file + " is not a correct ivy file (" + e.getMessage() + ")", e); } catch (IOException e) { throw new BuildException("The file " + file + " could not be read (" + e.getMessage() + ")", e); } return md; } /** * Try to load a resolve report. If not found, not available, out of date or contains resolve errors, it returns * <code>null</code>. */ private XmlReportParser getResolveReport(Ivy ivy, ModuleId mid, String conf, File ivyfile) { File report = ivy.getResolutionCacheManager().getConfigurationResolveReportInCache( ResolveOptions.getDefaultResolveId(mid), conf); if (!report.exists()) { return null; } if (ivyfile != null && ivyfile.lastModified() > report.lastModified()) { return null; } // found a report, try to parse it. try { log("Reading resolve report " + report, Project.MSG_DEBUG); XmlReportParser reportparser = new XmlReportParser(); reportparser.parse(report); if (reportparser.hasError()) { return null; } log("Loading last resolve report for " + mid + "[" + conf + "]", Project.MSG_VERBOSE); return reportparser; } catch (ParseException e) { return null; } } /** * Launch an actual resolve */ private ResolveReport resolve(Ivy ivy, ModuleDescriptor md, String conf) { ResolveOptions options = new ResolveOptions(); options.setConfs(new String[]{conf}); ResolveReport report; try { report = ivy.resolve(md, options); } catch (ParseException e) { throw new BuildException("Error while resolving " + ivyfile, e); } catch (IOException e) { throw new BuildException("Error while resolving " + ivyfile, e); } if (report.hasError()) { throw new BuildException("[easyant bug] fail to resolve antlib dependencies"); } return report; } /** * Make an array of artifacts a path and save it in the ant references */ private Path makePath(String pathId, List<ArtifactDownloadReport> artifacts) { log("Path '" + pathId + "' computed with " + artifacts.size() + " files", Project.MSG_VERBOSE); Path path = new Path(getProject()); for (ArtifactDownloadReport artifact : artifacts) { if (artifact.getLocalFile() != null) { PathElement pe = path.createPathElement(); pe.setLocation(artifact.getLocalFile()); log("Adding to path '" + pathId + "': " + artifact.getLocalFile(), Project.MSG_DEBUG); } } getProject().addReference(pathId, path); return path; } /** * If it is setup to have a retrieved local repo, get the basedir of the repo. Returns <code>null</code> otherwise */ private File getLocalRepoBaseDir() { String basedirValue = getProject().getProperty("easyant.localrepo.basedir"); if (basedirValue == null) { return null; } return getProject().resolveFile(basedirValue); } /** * Build the ivy instance to be used on the local retrieved repo */ private Ivy getLocalRepoIvy() { File basedir = getLocalRepoBaseDir(); if (basedir == null) { return null; } IvySettings settings = new IvySettings(); settings.setBaseDir(basedir); settings.setDefaultUseOrigin(true); File cacheDir = new File(basedir, ".cache"); ResolutionCacheManager resolutionCacheManager = new DefaultResolutionCacheManager(cacheDir); settings.setResolutionCacheManager(resolutionCacheManager); RepositoryCacheManager repositoryCacheManager = new DefaultRepositoryCacheManager("default-cache", settings, cacheDir); settings.setDefaultRepositoryCacheManager(repositoryCacheManager); FileSystemResolver localResolver = new FileSystemResolver(); localResolver.setName("local-repo"); localResolver.addIvyPattern(basedir.getAbsolutePath() + "/[organization]/[module]/[revision]/ivy.xml"); localResolver.addArtifactPattern(basedir.getAbsolutePath() + "/[organization]/[module]/[revision]/[type]s/[artifact].[ext]"); settings.addResolver(localResolver); settings.setDefaultResolver("local-repo"); Ivy ivy = Ivy.newInstance(settings); AntMessageLogger.register(this, ivy); return ivy; } /** * If setup with a local repo, clean it */ private void maybeClearLocalRepo() { File basedir = getLocalRepoBaseDir(); if (basedir == null) { return; } log("Deleting the local repository '" + basedir + "'", Project.MSG_VERBOSE); Delete delete = new Delete(); delete.setFailOnError(false); delete.setDir(basedir); } /** * If setup with a local repo, resolve the module and retrieve the artifacts into the local repo. */ private void maybeRetrieve(ModuleDescriptor md, String conf) { File basedir = getLocalRepoBaseDir(); if (basedir == null) { return; } log("Populating the local repository '" + basedir + "' with dependencies of " + md, Project.MSG_VERBOSE); Ivy ivy = getExternalIvy(); try { ivy.setVariable("easyant.localrepo.basedir", basedir.getCanonicalPath()); } catch (IOException e) { throw new BuildException("Unable to compute the path to the local repository", e); } ResolveReport resolve = resolve(ivy, md, conf); RetrieveOptions options = new RetrieveOptions(); options.setSync(false); options.setResolveId(resolve.getResolveId()); options.setConfs(new String[]{conf}); options.setDestIvyPattern("${easyant.localrepo.basedir}/[organization]/[module]/[revision]/ivy.xml"); try { ivy.retrieve(md.getModuleRevisionId(), "${easyant.localrepo.basedir}/[organization]/[module]/[revision]/[type]s/[artifact].[ext]", options); } catch (IOException e) { throw new BuildException("Unable to build the local repository", e); } } }