blob: 18fc375e9857f0e664240cf6cb713553304f31b8 [file] [log] [blame]
/*
* 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);
}
}
}