/* * Copyright 2012 James Moger * * Licensed 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.moxie; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.text.MessageFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import org.moxie.MoxieException.MissingParentPomException; import org.moxie.console.Console; import org.moxie.utils.DeepCopier; import org.moxie.utils.FileUtils; import org.moxie.utils.Parallel; import org.moxie.utils.StringUtils; /** * Solves transitive dependency chains, scoped classpaths, and linked projects. * <p> * Solver employs Maven 2 transitive dependency resolution based on "nearness" * and declaration order using a 2-pass scheme. Pass one recursively retrieves * all referenced POMs. Pass two analyzes each retrieved POM for a particular * solving scope and builds a flat dependency list with ring scores. * <p> * Ring-0 is the project itself. Ring-1 are direct named dependencies. Ring > 1 * are solved transitive dependencies. Dependency conflicts are resolved by * choosing the dependency in the lowest ring OR the first declaration, if the * rings are equal. * <p> * Linked projects are treated as source folders of the build project. Therefore * the dependencies linked projects are assimilated into the build project's * dependencies. */ public class Solver { private final BuildConfig config; private final MoxieCache moxieCache; private final Console console; private final Map<Scope, Collection<Dependency>> requiredDependencies; private final Map<Scope, Set<Dependency>> solutions; private final Map<Scope, List<File>> classpaths; private final Set<String> registeredUrls; private List<Build> linkedModuleBuilds; private boolean silent; private boolean verbose; private boolean solutionBuilt; public Solver(Console console, BuildConfig config) { this.config = config; this.moxieCache = new MoxieCache(config.getMoxieRoot()); this.requiredDependencies = new HashMap<Scope, Collection<Dependency>>(); this.solutions = new HashMap<Scope, Set<Dependency>>(); this.classpaths = new HashMap<Scope, List<File>>(); this.linkedModuleBuilds = new ArrayList<Build>(); this.registeredUrls = new HashSet<String>(); this.console = console == null ? new Console(config.isColor()) : console; this.moxieCache.setMavenCacheStrategy(config.getMavenCacheStrategy()); this.moxieCache.setLogger(console); // define required dependencies requiredDependencies.put(Scope.build,resolveAliasedDependencies( new Dependency("mx:commons-net"), new Dependency("mx:oro") )); } boolean isOnline() { String mxOnline = System.getProperty(Toolkit.MX_ONLINE, null); if (!StringUtils.isEmpty(mxOnline)) { // use system property to determine online return Boolean.parseBoolean(mxOnline); } return true; } boolean isUpdateMetadata() { String mxUpdateMetadata = System.getProperty(Toolkit.MX_UPDATEMETADATA, null); if (!StringUtils.isEmpty(mxUpdateMetadata)) { // use system property to force updating repository and artifact metadata return Boolean.parseBoolean(mxUpdateMetadata); } return false; } boolean isFailOnChecksumError() { String mxEnforceChecksums = System.getProperty(Toolkit.MX_ENFORCECHECKSUMS, null); if (!StringUtils.isEmpty(mxEnforceChecksums)) { // use system property to determine enforcing checksum verification return Boolean.parseBoolean(mxEnforceChecksums); } return true; } private boolean cache() { return config.getMoxieConfig().apply(Toolkit.APPLY_CACHE) || config.getProjectConfig().apply(Toolkit.APPLY_CACHE); } private void resolveAliasedDependencies() { resolveAliasedDependencies(config.getPom().getDependencies(false).toArray(new Dependency[0])); } private List<Dependency> resolveAliasedDependencies(Dependency... dependencies) { List<Dependency> list = new ArrayList<Dependency>(); for (Dependency dep : dependencies) { list.add(dep); // check for alias String name = null; if (StringUtils.isEmpty(dep.artifactId) && config.getAliases().containsKey(dep.groupId)) { // alias by simple name name = dep.groupId; } else if (config.getAliases().containsKey(dep.getManagementId())) { // alias by groupId:artifactId name = dep.getManagementId(); } if (name != null) { // we have an alias Dependency alias = config.getAliases().get(name); dep.groupId = alias.groupId; dep.artifactId = alias.artifactId; dep.version = alias.version; if (StringUtils.isEmpty(dep.version)) { dep.version = config.getPom().getManagedVersion(dep); if (StringUtils.isEmpty(dep.version)) { dep.version = config.getMoxieConfig().getPom().getManagedVersion(dep); } } if (StringUtils.isEmpty(dep.version)) { console.error("unable to resolve version for alias {0} = ", name, dep.getCoordinates()); } else { console.debug("resolved dependency alias {0} = {1}", name, dep.getCoordinates()); } } } return list; } public List<Build> getLinkedModules() { return linkedModuleBuilds; } public MoxieCache getMoxieCache() { return moxieCache; } public Console getConsole() { return console; } public BuildConfig getBuildConfig() { return config; } public Pom getPom(Dependency dependency) { return PomReader.readPom(moxieCache, dependency); } public File getArtifact(Dependency dependency) { return moxieCache.getArtifact(dependency, dependency.extension); } public Set<Dependency> getDependencies(Scope scope) { return solve(scope); } public void updateRepositoryMetadata() { Set<String> allPrefixes = new TreeSet<String>(); for (Repository repository : config.getRepositories()) { File index = retrievePrefixIndex(repository); if (index != null && index.exists()) { Collection<String> prefixes = moxieCache.getPrefixes(repository.getRepositoryUrl()); allPrefixes.addAll(prefixes); if (!prefixes.isEmpty()) { repository.setPrefixes(prefixes); console.debug("loaded prefixes for " + repository.getRepositoryUrl()); console.debug(prefixes.toString()); } } } if (!allPrefixes.isEmpty()) { moxieCache.writePrefixes(allPrefixes); } } public boolean solve() { return solve(new LinkedHashSet<Build>()); } private boolean solve(Set<Build> solvedProjects) { readProjectSolution(); if (solutions.size() == 0) { // solve linked projects solveLinkedModules(solvedProjects); // substitute aliases with definitions resolveAliasedDependencies(); // build solution retrievePOMs(); importDependencyManagement(); assimilateDependencies(); retrieveDependencies(); // cache built solution cacheProjectSolution(); // flag new solution solutionBuilt = true; } else { // we may have a cached solution, but we need to confirm we have // the pom and artifacts Set<Dependency> all = new LinkedHashSet<Dependency>(); for (Map.Entry<Scope, Set<Dependency>> entry : solutions.entrySet()) { all.addAll(entry.getValue()); } Set<Dependency> retrieved = new HashSet<Dependency>(); for (Dependency dep : all) { if (!retrieved.contains(dep)) { retrievePOM(dep, retrieved); } } if (config.isParallelDownloads()) { // download artifacts in parallel Parallel.WaitFor(retrieved, new Parallel.Operation<Dependency>() { public void perform(Dependency dep) { retrieveArtifact(dep); } }); } else { // download artifacts in serial for (Dependency dep : retrieved) { retrieveArtifact(dep); } } } return solutionBuilt; } private void solveLinkedModules(Set<Build> solvedModules) { if (config.getProjectConfig().linkedModules.size() > 0) { console.separator(); console.log("solving {0} modules", config.getPom().getManagementId()); console.separator(); } Set<Build> builds = new LinkedHashSet<Build>(); for (Module linkedModule : config.getProjectConfig().linkedModules) { console.debug(Console.SEP); String resolvedName = config.getPom().resolveProperties(linkedModule.folder); if (resolvedName.equals(linkedModule.folder)) { console.debug("locating module {0}", linkedModule.folder); } else { console.debug("locating module {0} ({1})", linkedModule.folder, resolvedName); } File moduleDir = new File(resolvedName); console.debug(1, "trying {0}", moduleDir.getAbsolutePath()); if (!moduleDir.exists()) { moduleDir = new File(config.getProjectDirectory().getParentFile(), resolvedName); console.debug(1, "trying {0}", moduleDir.getAbsolutePath()); if (!moduleDir.exists()) { String msg = console.error("failed to find module \"{0}\".", linkedModule.folder); throw new MoxieException(msg); } } try { File file = new File(moduleDir, linkedModule.descriptor); if (file.exists()) { // use Moxie config console.debug("located module {0} ({1})", linkedModule.folder, file.getAbsolutePath()); Build subModule = new Build(file.getAbsoluteFile(), null); if (solvedModules.contains(subModule)) { for (Build solvedProject: solvedModules) { if (solvedProject.equals(subModule)) { // add this submodule and it's dependent modules builds.add(subModule); builds.addAll(subModule.getSolver().getLinkedModules()); break; } } console.log(1, "=> already solved module {0}", subModule.getPom().getCoordinates()); continue; } console.log(1, "=> solving module {0}", subModule.getPom().getCoordinates()); subModule.getSolver().silent = true; subModule.getSolver().solve(solvedModules); // add this subproject and it's dependent modules // as solvedModules for recursive, cross-linked modules solvedModules.add(subModule); solvedModules.addAll(subModule.getSolver().getLinkedModules()); // add this submodule and it's dependent modules builds.add(subModule); builds.addAll(subModule.getSolver().getLinkedModules()); // linked module dependencies are considered ring-1 for (Scope scope : new Scope[] { Scope.compile }) { for (Dependency dep : subModule.getPom().getDependencies(scope, Constants.RING1)) { config.getPom().addDependency(dep, scope); } } } else { console.error("module {0} does not have a {1} descriptor!", linkedModule.folder, linkedModule.descriptor); } } catch (Exception e) { console.error(e, "failed to parse module {0}", linkedModule.folder); throw new RuntimeException(e); } } // add the list of unique builds linkedModuleBuilds.addAll(builds); } private void retrievePOMs() { console.debug("locating POMs"); // clear registered urls registeredUrls.clear(); for (Repository repository : config.getRepositories()) { registeredUrls.add(repository.getRepositoryUrl()); } // retrieve POMs serially for all dependencies in all scopes // parallelizing this is tricky because of recursive pom retrieval with // potential dependency overlap Set<Dependency> downloaded = new HashSet<Dependency>(); for (Scope scope : config.getPom().getScopes()) { for (Dependency dependency : config.getPom().getDependencies(scope, Constants.RING1)) { retrievePOM(dependency, downloaded); } } } private void importDependencyManagement() { if (config.getPom().getScopes().contains(Scope.imprt)) { console.debug("importing dependency management"); // This Moxie project imports a pom's dependencyManagement list. for (Dependency dependency : config.getPom().getDependencies(Scope.imprt, Constants.RING1)) { Pom pom = PomReader.readPom(moxieCache, dependency); config.getPom().importManagedDependencies(pom); } } } private void assimilateDependencies() { Map<Scope, List<Dependency>> assimilate = new LinkedHashMap<Scope, List<Dependency>>(); if (config.getPom().getScopes().contains(Scope.assimilate)) { console.debug("assimilating dependencies"); // This Moxie project integrates a pom's dependency list. for (Dependency dependency : config.getPom().getDependencies(Scope.assimilate, Constants.RING1)) { Pom pom = PomReader.readPom(moxieCache, dependency); for (Scope scope : pom.getScopes()) { if (!assimilate.containsKey(scope)) { assimilate.put(scope, new ArrayList<Dependency>()); } assimilate.get(scope).addAll(pom.getDependencies(scope)); } } // merge unique, assimilated dependencies into the Moxie project pom for (Map.Entry<Scope, List<Dependency>> entry : assimilate.entrySet()) { for (Dependency dependency : entry.getValue()) { config.getPom().addDependency(dependency, entry.getKey()); } } } // remove assimilate scope from the project pom, like it never existed config.getPom().removeScope(Scope.assimilate); } private void retrieveDependencies() { console.debug("retrieving artifacts"); // solve dependencies for compile, runtime, test, and build scopes final Set<Dependency> retrieved = Collections.synchronizedSet(new HashSet<Dependency>()); for (final Scope scope : new Scope [] { Scope.compile, Scope.runtime, Scope.test, Scope.build }) { if (!silent && verbose) { console.separator(); console.scope(scope, 0); console.separator(); } Set<Dependency> solution = solve(scope); if (solution.size() == 0) { if (!silent && verbose) { console.log(1, "none"); } } else { Parallel.Operation<Dependency> worker = new Parallel.Operation<Dependency>() { public void perform(Dependency dependency) { if (retrieved.add(dependency)) { if (!silent && verbose) { console.dependency(dependency); } File artifactFile = retrieveArtifact(dependency); if (artifactFile == null && !Constants.POM.equals(dependency.extension)) { console.artifactResolutionFailed(dependency); if (config.isFailFastOnArtifactResolution()) { throw new MoxieException(MessageFormat.format("Failed to resolve {0}", dependency.getCoordinates())); } } if (!Scope.build.equals(scope) && !Scope.system.equals(scope)) { copyArtifact(dependency, artifactFile); } } } }; if (config.isParallelDownloads()) { // Download artifacts in parallel Parallel.WaitFor(solution, worker); } else { // Download artifacts in serial for (Dependency dep : solution) { worker.perform(dep); } } } } } Set<Dependency> solve(Scope solutionScope) { if (solutions.containsKey(solutionScope)) { return solutions.get(solutionScope); } console.debug("solving {0} dependency solution", solutionScope); // assemble the flat, ordered list of dependencies // this list may have duplicates/conflicts List<Dependency> all = new ArrayList<Dependency>(); // add in required dependencies (designed for build scope) if (requiredDependencies.containsKey(solutionScope)) { all.addAll(requiredDependencies.get(solutionScope)); } for (Dependency dependency : config.getPom().getDependencies(solutionScope, Constants.RING1)) { console.debug(dependency.getDetailedCoordinates()); all.add(dependency); all.addAll(solve(solutionScope, dependency)); } // dependency mediation based on artifact type and nearness (ring) Map<String, Dependency> uniques = new LinkedHashMap<String, Dependency>(); for (Dependency dependency : all) { if (uniques.containsKey(dependency.getMediationId())) { // we have another registration for this dependency Dependency registered = uniques.get(dependency.getMediationId()); if (registered.ring > dependency.ring) { // this dependency is closer, use it instead uniques.put(dependency.getMediationId(), dependency); } } else { // register unique dependency uniques.put(dependency.getMediationId(), dependency); } } Set<Dependency> solution = new LinkedHashSet<Dependency>(uniques.values()); solutions.put(solutionScope, solution); return solution; } private List<Dependency> solve(Scope scope, Dependency dependency) { List<Dependency> resolved = new ArrayList<Dependency>(); if (dependency instanceof SystemDependency) { return resolved; } if (!dependency.resolveDependencies) { return resolved; } File pomFile = moxieCache.getArtifact(dependency, Constants.POM); if (pomFile == null || !pomFile.exists()) { return resolved; } List<Dependency> dependencies = null; // check to see if we have overridden the POM dependencies Pom override = config.getProjectConfig().getDependencyOverrides(scope, dependency.getCoordinates()); if (override == null) { override = config.getMoxieConfig().getDependencyOverrides(scope, dependency.getCoordinates()); } if (override != null) { if (Scope.build.equals(scope)) { // build scope overrides are normal console.debug("OVERRIDE: {0} {1} dependency {2}", config.getPom().getCoordinates(), scope.name().toUpperCase(), dependency.getCoordinates()); } else { // notify on any other scope console.notice("OVERRIDE: {0} {1} dependency {2}", config.getPom().getCoordinates(), scope.name().toUpperCase(), dependency.getCoordinates()); } dependencies = override.getDependencies(scope, dependency.ring + 1); } if (dependencies == null) { // try pre-resolved solution for this scope dependencies = readSolution(scope, dependency); } if (dependencies == null) { // solve the transitive dependencies for this scope Pom pom = PomReader.readPom(moxieCache, dependency); dependencies = pom.getDependencies(scope, dependency.ring + 1); // cache the scope's transitive dependency solution cacheSolution(scope, dependency, dependencies); } if (dependencies.size() > 0) { for (Dependency dep : dependencies) { if (!dependency.excludes(dep)) { dep.tags.addAll(dependency.tags); dep.exclusions.addAll(dependency.exclusions); resolved.add(dep); resolved.addAll(solve(scope, dep)); } } } return resolved; } private List<Dependency> readSolution(Scope scope, Dependency dependency) { if (!cache() || !dependency.isMavenObject()) { // caching forbidden return null; } if (dependency.isSnapshot()) { // do not use cached solution for snapshots return null; } MoxieData moxiedata = moxieCache.readMoxieData(dependency); if (moxiedata.isValidSolution() && moxiedata.getLastSolved().getTime() == FileUtils.getLastModified(moxieCache.getArtifact(dependency, Constants.POM))) { // solution lastModified date must equal pom lastModified date try { console.debug(1, "=> reusing solution {0}", dependency.getDetailedCoordinates()); if (moxiedata.hasScope(scope)) { List<Dependency> list = new ArrayList<Dependency>(moxiedata.getDependencies(scope)); for (Dependency dep : list) { // reset ring to be relative to the dependency dep.ring += dependency.ring + 1; } return list; } } catch (Exception e) { console.error(e, "Failed to read dependency solution {0}", dependency.getDetailedCoordinates()); } } return null; } private void cacheSolution(Scope scope, Dependency dependency, List<Dependency> transitiveDependencies) { if (transitiveDependencies.size() == 0) { return; } MoxieData moxiedata = moxieCache.readMoxieData(dependency); // copy transitives and reset the ring level relative to the dependency List<Dependency> dependencies = new ArrayList<Dependency>(); for (Dependency dep : transitiveDependencies) { dep = DeepCopier.copy(dep); dep.ring -= (dependency.ring + 1); dependencies.add(dep); } try { console.debug(1, "=> caching solution {0}", scope); moxiedata.setDependencies(scope, dependencies); // solution date is lastModified of POM moxiedata.setLastSolved(new Date(FileUtils.getLastModified(moxieCache.getArtifact(dependency, Constants.POM)))); moxieCache.writeMoxieData(dependency, moxiedata); } catch (Exception e) { console.error(e, "Failed to cache {0} solution {1}", scope, dependency.getDetailedCoordinates()); } } private void readProjectSolution() { if (!cache()) { // caching forbidden return; } String coordinates = config.getPom().getCoordinates(); Dependency projectAsDep = new Dependency(coordinates); if (projectAsDep.isSnapshot()) { // do not use cached solution for snapshots return; } MoxieData moxiedata = moxieCache.readMoxieData(projectAsDep); if (moxiedata.getLastSolved().getTime() == config.getProjectConfig().lastModified) { try { console.debug("reusing project solution {0}", config.getPom()); for (Scope scope : moxiedata.getScopes()) { Set<Dependency> dependencies = new LinkedHashSet<Dependency>(moxiedata.getDependencies(scope)); console.debug(1, "{0} {1} dependencies", dependencies.size(), scope); solutions.put(scope, dependencies); } } catch (Exception e) { console.error(e, "Failed to read project solution {0}", projectAsDep.getDetailedCoordinates()); } } } private void cacheProjectSolution() { Dependency projectAsDep = new Dependency(config.getPom().toString()); MoxieData moxiedata = moxieCache.readMoxieData(projectAsDep); try { console.debug("caching project solution {0}", config.getPom()); for (Map.Entry<Scope, Set<Dependency>> entry : solutions.entrySet()) { moxiedata.setDependencies(entry.getKey(), entry.getValue()); } moxiedata.setLastSolved(new Date(config.getProjectConfig().lastModified)); moxieCache.writeMoxieData(projectAsDep, moxiedata); } catch (Exception e) { console.error(e, "Failed to cache project solution {0}", projectAsDep.getDetailedCoordinates()); } } private File retrievePrefixIndex(Repository repository) { if (!repository.isMavenSource()) { // skip non-Maven repositories return null; } File prefixesFile = moxieCache.getPrefixesIndex(repository.getRepositoryUrl()); boolean updateRequired = !prefixesFile.exists() || isUpdateMetadata(); if (!updateRequired) { MoxieData moxiedata = moxieCache.readRepositoryMoxieData(repository.getRepositoryUrl()); UpdatePolicy policy = config.getUpdatePolicy(); // we have metadata, check update policy if (UpdatePolicy.daily.equals(policy)) { // daily is a special case SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd"); String mdate = df.format(moxiedata.getLastChecked()); String today = df.format(new Date()); updateRequired = !mdate.equals(today); } else { // always, never, interval long msecs = policy.mins*60*1000L; updateRequired = Math.abs(System.currentTimeMillis() - moxiedata.getLastChecked().getTime()) > msecs; } if (updateRequired) { console.debug(1, "{0} prefixes index is STALE according to {1} update policy", repository.getRepositoryUrl(), policy.toString()); } else { console.debug(1, "{0} prefixes index is CURRENT according to {1} update policy", repository.getRepositoryUrl(), policy.toString()); } } if (updateRequired && isOnline()) { // download prefixes index console.debug(1, "downloading prefixes index for {0}", Constants.PREFIXES, repository.getRepositoryUrl()); prefixesFile = repository.downloadPrefixIndex(this); } return prefixesFile; } private File retrievePOM(Dependency dependency, Set<Dependency> retrieved) { if (!dependency.isMavenObject()) { return null; } if (StringUtils.isEmpty(dependency.version)) { return null; } if (retrieved != null && retrieved.contains(dependency)) { return null; } if (dependency.isMetaVersion()) { // Support VERSION RANGE, SNAPSHOT, RELEASE, and LATEST versions File metadataFile = moxieCache.getMetadata(dependency, Constants.XML); boolean updateRequired = !metadataFile.exists() || isUpdateMetadata(); // read MoxieData which sets the origin, if we have it MoxieData moxiedata = moxieCache.readMoxieData(dependency); if (!updateRequired) { UpdatePolicy policy = config.getUpdatePolicy(); // we have metadata, check update policy if (UpdatePolicy.daily.equals(policy)) { // daily is a special case SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd"); String mdate = df.format(moxiedata.getLastChecked()); String today = df.format(new Date()); updateRequired = !mdate.equals(today); } else { // always, never, interval long msecs = policy.mins*60*1000L; updateRequired = Math.abs(System.currentTimeMillis() - moxiedata.getLastChecked().getTime()) > msecs; } if (updateRequired) { console.debug(1, "{0} maven-metadata.xml is STALE according to {1} update policy", dependency.getManagementId(), policy.toString()); } else { console.debug(1, "{0} maven-metadata.xml is CURRENT according to {1} update policy", dependency.getManagementId(), policy.toString()); } } if (updateRequired && isOnline()) { // download artifact maven-metadata.xml console.debug(1, "locating maven-metadata.xml for {0}", dependency.getManagementId()); for (Repository repository : config.getRepositories(dependency)) { if (!repository.isMavenSource()) { // skip non-Maven repositories continue; } if (!repository.isSource(dependency)) { // try to match origins continue; } metadataFile = repository.downloadMetadata(this, dependency); if (metadataFile != null && metadataFile.exists()) { // downloaded the metadata break; } } // reload Moxie data and reset last checked date for next update // check after we have resolved RELEASE, LATEST, or SNAPSHOT moxiedata = moxieCache.readMoxieData(dependency); moxiedata.setLastChecked(new Date()); moxieCache.writeMoxieData(dependency, moxiedata); } else { console.debug(1, "reading maven-metadata.xml for {0}", dependency.getManagementId()); } } String unresolvedVersion = dependency.getVersion(); boolean isRangedVersion = dependency.isRangedVersion(); MoxieData moxiedata = moxieCache.readMoxieData(dependency); File pomFile = moxieCache.getArtifact(dependency, Constants.POM); if (isRangedVersion) { console.notice(1, "{0}:{1} resolved to {2}", dependency.getManagementId(), unresolvedVersion, dependency.getVersion()); } if ((!pomFile.exists() || (dependency.isSnapshot() && moxiedata.isRefreshRequired())) && isOnline()) { // download the POM console.debug(1, "locating POM for {0}", dependency.getDetailedCoordinates()); for (Repository repository : config.getRepositories(dependency)) { if (!repository.isMavenSource()) { // skip non-Maven repositories continue; } if (!repository.isSource(dependency)) { // try to match origins continue; } File retrievedFile = repository.download(this, dependency, Constants.POM); if (retrievedFile != null && retrievedFile.exists()) { pomFile = retrievedFile; break; } } } // Read POM if (pomFile.exists()) { // mark as retrieved so we do not re-retrieve if (retrieved != null) { retrieved.add(dependency); } // confirm we have the repository url in our repository list if (!StringUtils.isEmpty(moxiedata.getOrigin()) && !registeredUrls.contains(moxiedata.getOrigin()) && !moxiedata.getOrigin().startsWith("file:/")) { console.missingOriginRepository(moxiedata.getOrigin(), dependency); } try { Pom pom = null; try { pom = PomReader.readPom(moxieCache, pomFile); if (pom.hasParentDependency()) { // we already have the parent POM locally Dependency parent = pom.getParentDependency(); parent.ring = dependency.ring; retrievePOM(parent, retrieved); } } catch (MissingParentPomException e) { // traverse up the graph and retrieve parent POM Dependency parent = e.getParent(); parent.ring = dependency.ring; retrievePOM(parent, retrieved); // Re-read this POM now that we have the parent. // This allows for resolving properties defined in the // parent POM on first retrieval. pom = PomReader.readPom(moxieCache, pomFile); } // traverse down the graph and retrieve all dependent POMs for (Scope scope : pom.getScopes()) { for (Dependency dep : pom.getDependencies(scope, dependency.ring + 1)) { retrievePOM(dep, retrieved); } } } catch (Exception e) { console.error(e); } return pomFile; } return null; } /** * Download an artifact from a local or remote artifact repository. * * @param dependency * the dependency to download * @return */ private File retrieveArtifact(Dependency dependency) { if (dependency instanceof SystemDependency) { String path = ((SystemDependency) dependency).path; return new File(path); } if (Constants.POM.equals(dependency.extension)) { // POM dependencies do not have other artifacts if (dependency.isSnapshot()) { // ... but we still have to purge old pom snapshots for (Repository repository : config.getRepositories(dependency)) { if (!repository.isSource(dependency)) { // dependency incompatible with repository console.debug(1, "{0} is not source of {1}, skipping", repository.name, dependency.getCoordinates()); continue; } // purge snapshots for this dependency moxieCache.purgeSnapshots(dependency, repository.purgePolicy); } } return null; } // standard artifact, read Moxie data and set dependency origin MoxieData moxiedata = moxieCache.readMoxieData(dependency); for (Repository repository : config.getRepositories(dependency)) { if (!repository.isSource(dependency)) { // dependency incompatible with repository console.debug(1, "{0} is not source of {1}, skipping", repository.name, dependency.getCoordinates()); continue; } // Determine to download/update the dependency File artifactFile = moxieCache.getArtifact(dependency, dependency.extension); boolean downloadDependency = !artifactFile.exists(); if (!downloadDependency && dependency.isSnapshot()) { downloadDependency = moxiedata.isRefreshRequired(); if (downloadDependency) { console.debug(1, "{0} is STALE according to {1}", dependency.getManagementId(), moxiedata.getOrigin()); } else { console.debug(1, "{0} is CURRENT according to {1}", dependency.getManagementId(), moxiedata.getOrigin()); } } if (downloadDependency && isOnline()) { // Download primary artifact (e.g. jar) artifactFile = repository.download(this, dependency, dependency.extension); if (artifactFile != null && dependency.isJavaBinary()) { // Download sources artifact (e.g. -sources.jar) Dependency sources = dependency.getSourcesArtifact(); File sourcesFile = moxieCache.getArtifact(sources, sources.extension); if (dependency.isSnapshot() || !sourcesFile.exists()) { repository.download(this, sources, sources.extension); } // Download javadoc artifact (e.g. -javadoc.jar) Dependency javadoc = dependency.getJavadocArtifact(); File javadocFile = moxieCache.getArtifact(javadoc, javadoc.extension); if (dependency.isSnapshot() || !javadocFile.exists()) { repository.download(this, javadoc, javadoc.extension); } } } // purge snapshots for this dependency moxieCache.purgeSnapshots(dependency, repository.purgePolicy); // artifact retrieved if (artifactFile != null) { return artifactFile; } } return null; } private void copyArtifact(Dependency dependency, File artifactFile) { // optionally copy artifact to project-specified folder if (artifactFile != null && artifactFile.exists()) { if (config.getProjectConfig().getDependencyDirectory() != null) { // copy jar File projectFile = config.getProjectConfig().getProjectDependencyArtifact(dependency); if (dependency.isSnapshot() || !projectFile.exists()) { console.debug(1, "copying {0} to {1}", artifactFile.getName(), projectFile.getParent()); try { projectFile.getParentFile().mkdirs(); FileUtils.copyFile(artifactFile, projectFile); } catch (IOException e) { throw new RuntimeException("Error writing to file " + projectFile, e); } } // copy source jar Dependency source = dependency.getSourcesArtifact(); File sourceFile = moxieCache.getArtifact(source, source.extension); File projectSourceFile = config.getProjectConfig().getProjectDependencySourceArtifact(dependency); if (sourceFile.exists() && (dependency.isSnapshot() || !projectSourceFile.exists())) { console.debug(1, "copying {0} to {1}", sourceFile.getName(), projectSourceFile.getParent()); try { projectSourceFile.getParentFile().mkdirs(); FileUtils.copyFile(sourceFile, projectSourceFile); } catch (IOException e) { throw new RuntimeException("Error writing to file " + projectSourceFile, e); } } // remove obsolete artifacts removeObsoleteArtifacts(dependency, projectFile.getParentFile()); removeObsoleteArtifacts(source, projectSourceFile.getParentFile()); } } } /** * If we are maintaining project-local artifacts then make sure to remove * obsolete versions of these local artifacts. For example if we upgrade * from Lucene 3.6.0 to 3.6.1 we should remove the 3.6.0 artifacts. Or the * reverse, if we are downgrading. * * @param dependency * @param folder */ private void removeObsoleteArtifacts(final Dependency dependency, final File folder) { File [] removals = folder.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { String n = name.toLowerCase(); String dep = dependency.artifactId.toLowerCase(); if (n.startsWith(dep)) { dep += "-" + dependency.version.toLowerCase(); if (!n.startsWith(dep)) { String suffix; if (!StringUtils.isEmpty(dependency.classifier)) { suffix = "-" + dependency.classifier; } else { suffix = ""; } suffix += "." + dependency.extension; suffix = suffix.toLowerCase(); if (n.endsWith(suffix)) { // isolate middle section - which may be just // a version OR may be part of the artifact id String v = n.substring(dependency.artifactId.length()); v = v.substring(0, v.length() - suffix.length()); if (v.length() == 0) { return false; } if (v.charAt(0) == '-') { // strip leading - for artifacts like: // wicket-auth-roles when we are trying to delete // the 'wicket' artifact but not the 'wicket-auth-roles' // artifact v = v.substring(1); } // grab first element of middle section // 1.2.3-SNAPSHOT = 1.2.3 // 1.2.3 = 1.2.3 // core-1.2.3-SNAPSHOT = core String v0 = v.split("-")[0]; ArtifactVersion version = new ArtifactVersion(v0); if (version.getQualifier() != null && version.getQualifier().equalsIgnoreCase(v0)) { // this file is a different artifact, not a different // version of the same artifact console.debug("keeping related artifact {0} in {1} when resolving {2}", n, folder, dependency.getCoordinates()); } else { // the middle section is a version number and not // an artifact id fragment AND a version number console.debug("deleting obsolete artifact {0} from {1} when resolving {2}", n, folder, dependency.getCoordinates()); console.debug("qualifier={0}, dep={1}", version.getQualifier(), dep); return true; } } } } return false; } }); // delete any matches if (removals != null) { for (File file : removals) { file.delete(); } } } /** * Downloads internal dependencies needed for runtime operation of Moxie. * This methods is often used in conjunction with manipulating the ANT * runtime classpath. * * @param dependencies */ public Collection<Dependency> getRuntimeDependencies(Dependency... dependencies) { // solve the classpath solution for the Moxie runtime dependencies Pom pom = new Pom(); Set<Dependency> retrieved = new HashSet<Dependency>(); for (Dependency dependency : dependencies) { resolveAliasedDependencies(dependency); retrievePOM(dependency, retrieved); pom.addDependency(dependency, Scope.compile); } Set<Dependency> solution = new LinkedHashSet<Dependency>(); for (Dependency dependency : pom.getDependencies(Scope.compile, Constants.RING1)) { solution.add(dependency); solution.addAll(solve(Scope.compile, dependency)); } for (Dependency dependency : solution) { retrieveArtifact(dependency); } return solution; } /** * Solves the dependency solution for the specified artifacts and scope. * * @param scope * @param dependencies * @return a list of artifacts */ public List<File> solve(Scope scope, Dependency... deps) { Pom pom = new Pom(); Set<Dependency> retrieved = new HashSet<Dependency>(); for (Dependency dep : deps) { resolveAliasedDependencies(dep); getConsole().dependency(dep); retrievePOM(dep, retrieved); pom.addDependency(dep, scope); } Set<Dependency> solution = new LinkedHashSet<Dependency>(); for (Dependency dependency : pom.getDependencies(scope, Constants.RING1)) { solution.add(dependency); solution.addAll(solve(scope, dependency)); } List<File> artifacts = new ArrayList<File>(); for (Dependency dependency : solution) { File artifact = retrieveArtifact(dependency); if (artifact != null) { artifacts.add(artifact); } } return artifacts; } public List<File> getClasspath(Scope scope) { return getClasspath(scope, null); } public List<File> getClasspath(Scope scope, String tag) { if (classpaths.containsKey(scope) && StringUtils.isEmpty(tag)) { // return scoped classpath for non-tag query return classpaths.get(scope); } File projectFolder = null; if (config.getProjectConfig().getDependencyDirectory() != null && config.getProjectConfig().getDependencyDirectory().exists()) { projectFolder = config.getProjectConfig().getDependencyDirectory(); } if (StringUtils.isEmpty(tag)) { console.debug("solving {0} classpath", scope); } else { console.debug("solving {0} {1} classpath", tag, scope); } Set<Dependency> dependencies = solve(scope); List<File> jars = new ArrayList<File>(); for (Dependency dependency : dependencies) { if (!StringUtils.isEmpty(tag)) { // filter deps by tag if (!dependency.tags.contains(tag.toLowerCase())) { continue; } } File jar; if (dependency instanceof SystemDependency) { SystemDependency sys = (SystemDependency) dependency; jar = new File(sys.path); } else { jar = moxieCache.getArtifact(dependency, dependency.extension); if (projectFolder != null) { File pJar = config.getProjectConfig().getProjectDependencyArtifact(dependency); if (pJar.exists()) { jar = pJar; } } } jars.add(jar); } if (StringUtils.isEmpty(tag)) { // store scoped classpath if not filtering by tag classpaths.put(scope, jars); } return jars; } }