Provide an Aether-based Grape Engine
Previously, @Grab annotations would use Ivy to download the dependencies with some of Ivy's known limitations being worked around by GrapeEngineCustomizer. This commit adds a GrapeEngine implementation that uses Aether, the dependency resolution 'engine' used by Maven and Grails. To ensure consistent behaviour with a Maven build, the Aether-powered dependency resolution uses the dependency management configuration from the spring-boot-starter-parent pom file.pull/97/head
parent
a28947f276
commit
39e8e46e2a
@ -0,0 +1,326 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* 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.springframework.boot.cli.compiler;
|
||||
|
||||
import groovy.grape.GrapeEngine;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
|
||||
import org.eclipse.aether.AbstractRepositoryListener;
|
||||
import org.eclipse.aether.DefaultRepositorySystemSession;
|
||||
import org.eclipse.aether.RepositoryEvent;
|
||||
import org.eclipse.aether.RepositorySystem;
|
||||
import org.eclipse.aether.RepositorySystemSession;
|
||||
import org.eclipse.aether.artifact.Artifact;
|
||||
import org.eclipse.aether.artifact.DefaultArtifact;
|
||||
import org.eclipse.aether.collection.CollectRequest;
|
||||
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
|
||||
import org.eclipse.aether.graph.Dependency;
|
||||
import org.eclipse.aether.impl.ArtifactDescriptorReader;
|
||||
import org.eclipse.aether.impl.DefaultServiceLocator;
|
||||
import org.eclipse.aether.internal.impl.DefaultRepositorySystem;
|
||||
import org.eclipse.aether.repository.LocalRepository;
|
||||
import org.eclipse.aether.repository.RemoteRepository;
|
||||
import org.eclipse.aether.resolution.ArtifactDescriptorException;
|
||||
import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
|
||||
import org.eclipse.aether.resolution.ArtifactDescriptorResult;
|
||||
import org.eclipse.aether.resolution.ArtifactResolutionException;
|
||||
import org.eclipse.aether.resolution.ArtifactResult;
|
||||
import org.eclipse.aether.resolution.DependencyRequest;
|
||||
import org.eclipse.aether.resolution.DependencyResult;
|
||||
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
|
||||
import org.eclipse.aether.spi.connector.transport.TransporterFactory;
|
||||
import org.eclipse.aether.spi.log.Logger;
|
||||
import org.eclipse.aether.spi.log.LoggerFactory;
|
||||
import org.eclipse.aether.transfer.AbstractTransferListener;
|
||||
import org.eclipse.aether.transfer.TransferCancelledException;
|
||||
import org.eclipse.aether.transfer.TransferEvent;
|
||||
import org.eclipse.aether.transport.file.FileTransporterFactory;
|
||||
import org.eclipse.aether.transport.http.HttpTransporterFactory;
|
||||
import org.eclipse.aether.util.artifact.JavaScopes;
|
||||
import org.eclipse.aether.util.filter.DependencyFilterUtils;
|
||||
|
||||
/**
|
||||
* A {@link GrapeEngine} implementation that uses <a
|
||||
* href="http://eclipse.org/aether">Aether</a>, the dependency resolution system used by
|
||||
* Maven.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class AetherGrapeEngine implements GrapeEngine {
|
||||
|
||||
private static final String DEPENDENCY_MODULE = "module";
|
||||
|
||||
private static final String DEPENDENCY_GROUP = "group";
|
||||
|
||||
private static final String DEPENDENCY_VERSION = "version";
|
||||
|
||||
private final ProgressReporter progressReporter = new ProgressReporter();
|
||||
|
||||
private final ArtifactCoordinatesResolver artifactCoordinatesResolver;
|
||||
|
||||
private final ArtifactDescriptorReader artifactDescriptorReader;
|
||||
|
||||
private final ExtendedGroovyClassLoader defaultClassLoader;
|
||||
|
||||
private final RepositorySystemSession repositorySystemSession;
|
||||
|
||||
private final RepositorySystem repositorySystem;
|
||||
|
||||
private final List<RemoteRepository> repositories;
|
||||
|
||||
public AetherGrapeEngine(ExtendedGroovyClassLoader classLoader,
|
||||
ArtifactCoordinatesResolver artifactCoordinatesResolver) {
|
||||
this.defaultClassLoader = classLoader;
|
||||
this.artifactCoordinatesResolver = artifactCoordinatesResolver;
|
||||
|
||||
DefaultServiceLocator mavenServiceLocator = MavenRepositorySystemUtils
|
||||
.newServiceLocator();
|
||||
mavenServiceLocator.setService(LoggerFactory.class, NopLoggerFactory.class);
|
||||
mavenServiceLocator.addService(RepositorySystem.class,
|
||||
DefaultRepositorySystem.class);
|
||||
|
||||
mavenServiceLocator.addService(RepositoryConnectorFactory.class,
|
||||
BasicRepositoryConnectorFactory.class);
|
||||
|
||||
mavenServiceLocator.addService(TransporterFactory.class,
|
||||
HttpTransporterFactory.class);
|
||||
mavenServiceLocator.addService(TransporterFactory.class,
|
||||
FileTransporterFactory.class);
|
||||
|
||||
this.repositorySystem = mavenServiceLocator.getService(RepositorySystem.class);
|
||||
|
||||
DefaultRepositorySystemSession repositorySystemSession = MavenRepositorySystemUtils
|
||||
.newSession();
|
||||
repositorySystemSession.setTransferListener(new AbstractTransferListener() {
|
||||
|
||||
@Override
|
||||
public void transferStarted(TransferEvent event)
|
||||
throws TransferCancelledException {
|
||||
AetherGrapeEngine.this.progressReporter.reportProgress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transferProgressed(TransferEvent event)
|
||||
throws TransferCancelledException {
|
||||
AetherGrapeEngine.this.progressReporter.reportProgress();
|
||||
}
|
||||
});
|
||||
|
||||
repositorySystemSession.setRepositoryListener(new AbstractRepositoryListener() {
|
||||
@Override
|
||||
public void artifactResolved(RepositoryEvent event) {
|
||||
AetherGrapeEngine.this.progressReporter.reportProgress();
|
||||
}
|
||||
});
|
||||
|
||||
LocalRepository localRepo = new LocalRepository(new File(
|
||||
System.getProperty("user.home"), ".m2/repository"));
|
||||
repositorySystemSession.setLocalRepositoryManager(this.repositorySystem
|
||||
.newLocalRepositoryManager(repositorySystemSession, localRepo));
|
||||
|
||||
this.repositorySystemSession = repositorySystemSession;
|
||||
|
||||
this.repositories = Arrays.asList(new RemoteRepository.Builder("central",
|
||||
"default", "http://repo1.maven.org/maven2/").build(),
|
||||
new RemoteRepository.Builder("spring-snapshot", "default",
|
||||
"http://repo.spring.io/snapshot").build(),
|
||||
new RemoteRepository.Builder("spring-milestone", "default",
|
||||
"http://repo.spring.io/milestone").build());
|
||||
|
||||
this.artifactDescriptorReader = mavenServiceLocator
|
||||
.getService(ArtifactDescriptorReader.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object grab(Map args) {
|
||||
return grab(args, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object grab(Map args, Map... dependencyMaps) {
|
||||
List<Dependency> dependencies = createDependencies(dependencyMaps);
|
||||
|
||||
try {
|
||||
List<File> files = resolve(dependencies);
|
||||
ExtendedGroovyClassLoader classLoader = (ExtendedGroovyClassLoader) args
|
||||
.get("classLoader");
|
||||
if (classLoader == null) {
|
||||
classLoader = this.defaultClassLoader;
|
||||
}
|
||||
for (File file : files) {
|
||||
classLoader.addURL(file.toURI().toURL());
|
||||
}
|
||||
}
|
||||
catch (ArtifactResolutionException ex) {
|
||||
throw new DependencyResolutionFailedException(ex);
|
||||
}
|
||||
catch (MalformedURLException ex) {
|
||||
throw new DependencyResolutionFailedException(ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<Dependency> createDependencies(Map<?, ?>... dependencyMaps) {
|
||||
List<Dependency> dependencies = new ArrayList<Dependency>(dependencyMaps.length);
|
||||
for (Map<?, ?> dependencyMap : dependencyMaps) {
|
||||
dependencies.add(createDependency(dependencyMap));
|
||||
}
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
private Dependency createDependency(Map<?, ?> dependencyMap) {
|
||||
String group = (String) dependencyMap.get(DEPENDENCY_GROUP);
|
||||
String module = (String) dependencyMap.get(DEPENDENCY_MODULE);
|
||||
String version = (String) dependencyMap.get(DEPENDENCY_VERSION);
|
||||
|
||||
// TODO Transitivity
|
||||
|
||||
Artifact artifact = new DefaultArtifact(group, module, "jar", version);
|
||||
return new Dependency(artifact, JavaScopes.COMPILE);
|
||||
}
|
||||
|
||||
private List<File> resolve(List<Dependency> dependencies)
|
||||
throws ArtifactResolutionException {
|
||||
|
||||
CollectRequest collectRequest = new CollectRequest((Dependency) null,
|
||||
dependencies, this.repositories);
|
||||
collectRequest.setManagedDependencies(getManagedDependencies());
|
||||
|
||||
try {
|
||||
DependencyResult dependencyResult = this.repositorySystem
|
||||
.resolveDependencies(
|
||||
this.repositorySystemSession,
|
||||
new DependencyRequest(collectRequest, DependencyFilterUtils
|
||||
.classpathFilter(JavaScopes.COMPILE)));
|
||||
List<File> files = new ArrayList<File>();
|
||||
for (ArtifactResult result : dependencyResult.getArtifactResults()) {
|
||||
files.add(result.getArtifact().getFile());
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new DependencyResolutionFailedException(ex);
|
||||
}
|
||||
finally {
|
||||
this.progressReporter.finished();
|
||||
}
|
||||
}
|
||||
|
||||
private List<Dependency> getManagedDependencies() {
|
||||
ArtifactDescriptorRequest parentRequest = new ArtifactDescriptorRequest();
|
||||
Artifact parentArtifact = new DefaultArtifact("org.springframework.boot",
|
||||
"spring-boot-starter-parent", "pom",
|
||||
this.artifactCoordinatesResolver.getVersion("spring-boot"));
|
||||
parentRequest.setArtifact(parentArtifact);
|
||||
|
||||
try {
|
||||
ArtifactDescriptorResult artifactDescriptorResult = this.artifactDescriptorReader
|
||||
.readArtifactDescriptor(this.repositorySystemSession, parentRequest);
|
||||
return artifactDescriptorResult.getManagedDependencies();
|
||||
}
|
||||
catch (ArtifactDescriptorException ex) {
|
||||
throw new DependencyResolutionFailedException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Map<String, List<String>>> enumerateGrapes() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI[] resolve(Map args, Map... dependencies) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI[] resolve(Map args, List depsInfo, Map... dependencies) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map[] listDependencies(ClassLoader classLoader) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addResolver(Map<String, Object> args) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object grab(String endorsedModule) {
|
||||
throw new UnsupportedOperationException("Auto-generated method stub");
|
||||
}
|
||||
|
||||
private static final class NopLoggerFactory implements LoggerFactory {
|
||||
|
||||
@Override
|
||||
public Logger getLogger(String name) {
|
||||
// TODO Something more robust; I'm surprised this doesn't NPE
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ProgressReporter {
|
||||
|
||||
private static final long INITIAL_DELAY = TimeUnit.SECONDS.toMillis(3);
|
||||
|
||||
private static final long PROGRESS_DELAY = TimeUnit.SECONDS.toMillis(1);
|
||||
|
||||
private long startTime = System.currentTimeMillis();
|
||||
|
||||
private long lastProgressTime = System.currentTimeMillis();
|
||||
|
||||
private boolean started;
|
||||
|
||||
private boolean finished;
|
||||
|
||||
void reportProgress() {
|
||||
if (!this.finished
|
||||
&& System.currentTimeMillis() - this.startTime > INITIAL_DELAY) {
|
||||
if (!this.started) {
|
||||
this.started = true;
|
||||
System.out.print("Resolving dependencies..");
|
||||
this.lastProgressTime = System.currentTimeMillis();
|
||||
}
|
||||
else if (System.currentTimeMillis() - this.lastProgressTime > PROGRESS_DELAY) {
|
||||
System.out.print(".");
|
||||
this.lastProgressTime = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void finished() {
|
||||
if (this.started && !this.finished) {
|
||||
this.finished = true;
|
||||
System.out.println("");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* 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.springframework.boot.cli.compiler;
|
||||
|
||||
/**
|
||||
* Thrown to indicate a failure during dependency resolution.
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class DependencyResolutionFailedException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Creates a new {@code DependencyResolutionFailedException} with the given
|
||||
* {@code cause}.
|
||||
* @param cause The cause of the resolution failure
|
||||
*/
|
||||
public DependencyResolutionFailedException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
@ -1,356 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* 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.springframework.boot.cli.compiler;
|
||||
|
||||
import groovy.grape.GrapeEngine;
|
||||
import groovy.grape.GrapeIvy;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.ivy.Ivy;
|
||||
import org.apache.ivy.core.cache.ArtifactOrigin;
|
||||
import org.apache.ivy.core.event.IvyEvent;
|
||||
import org.apache.ivy.core.event.IvyListener;
|
||||
import org.apache.ivy.core.event.resolve.EndResolveEvent;
|
||||
import org.apache.ivy.core.module.descriptor.Artifact;
|
||||
import org.apache.ivy.core.module.id.ModuleId;
|
||||
import org.apache.ivy.core.settings.IvySettings;
|
||||
import org.apache.ivy.plugins.parser.m2.PomReader;
|
||||
import org.apache.ivy.plugins.repository.Resource;
|
||||
import org.apache.ivy.plugins.repository.url.URLRepository;
|
||||
import org.apache.ivy.plugins.repository.url.URLResource;
|
||||
import org.apache.ivy.plugins.resolver.ChainResolver;
|
||||
import org.apache.ivy.plugins.resolver.DependencyResolver;
|
||||
import org.apache.ivy.plugins.resolver.IBiblioResolver;
|
||||
import org.apache.ivy.util.AbstractMessageLogger;
|
||||
import org.apache.ivy.util.MessageLogger;
|
||||
import org.springframework.boot.cli.Log;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
/**
|
||||
* Customizes the groovy grape engine to enhance and patch the behavior of ivy. Can add
|
||||
* Spring repos to the search path, provide simple log progress feedback if downloads are
|
||||
* taking a long time, and also fixes a problem where ivy cannot use a local Maven cache
|
||||
* repo.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Dave Syer
|
||||
*/
|
||||
class GrapeEngineCustomizer {
|
||||
|
||||
private GrapeIvy engine;
|
||||
|
||||
public GrapeEngineCustomizer(GrapeEngine engine) {
|
||||
this.engine = (GrapeIvy) engine;
|
||||
}
|
||||
|
||||
public void customize() {
|
||||
Ivy ivy = this.engine.getIvyInstance();
|
||||
IvySettings settings = this.engine.getSettings();
|
||||
addDownloadingLogSupport(ivy);
|
||||
setupResolver(settings);
|
||||
}
|
||||
|
||||
private void addDownloadingLogSupport(Ivy ivy) {
|
||||
final DownloadingLog downloadingLog = new DownloadingLog();
|
||||
ivy.getLoggerEngine().pushLogger(downloadingLog);
|
||||
ivy.getEventManager().addIvyListener(new IvyListener() {
|
||||
@Override
|
||||
public void progress(IvyEvent event) {
|
||||
if (event instanceof EndResolveEvent) {
|
||||
downloadingLog.finished();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void setupResolver(IvySettings settings) {
|
||||
ChainResolver grapesResolver = (ChainResolver) settings
|
||||
.getResolver("downloadGrapes");
|
||||
List<DependencyResolver> grapesResolvers = grapesResolver.getResolvers();
|
||||
|
||||
// Replace localm2 resolver to fix missing artifact errors
|
||||
for (int i = 0; i < grapesResolvers.size(); i++) {
|
||||
DependencyResolver resolver = grapesResolvers.get(i);
|
||||
if ("localm2".equals(resolver.getName())) {
|
||||
((IBiblioResolver) resolver).setRepository(new LocalM2Repository());
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new top level resolver, encapsulating the default resolvers
|
||||
SpringBootResolver springBootResolver = new SpringBootResolver(grapesResolvers);
|
||||
springBootResolver.setSettings(settings);
|
||||
springBootResolver.setReturnFirst(grapesResolver.isReturnFirst());
|
||||
springBootResolver.setName("springBoot");
|
||||
|
||||
// Add support for spring snapshots and milestones if required
|
||||
if (!Boolean.getBoolean("disableSpringSnapshotRepos")) {
|
||||
springBootResolver.addSpringSnapshotResolver(newResolver("spring-snapshot",
|
||||
"http://repo.spring.io/snapshot"));
|
||||
springBootResolver.addSpringSnapshotResolver(newResolver("spring-milestone",
|
||||
"http://repo.spring.io/milestone"));
|
||||
}
|
||||
|
||||
// Replace the original resolvers
|
||||
grapesResolvers.clear();
|
||||
grapesResolvers.add(springBootResolver);
|
||||
}
|
||||
|
||||
private IBiblioResolver newResolver(String name, String root) {
|
||||
IBiblioResolver resolver = new IBiblioResolver();
|
||||
resolver.setName(name);
|
||||
resolver.setRoot(root);
|
||||
resolver.setM2compatible(true);
|
||||
resolver.setSettings(this.engine.getSettings());
|
||||
return resolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link DependencyResolver} that is optimized for Spring Boot.
|
||||
*/
|
||||
private static class SpringBootResolver extends ChainResolver {
|
||||
|
||||
private static final Object SPRING_BOOT_GROUP_ID = "org.springframework.boot";
|
||||
|
||||
private static final String STARTER_PREFIX = "spring-boot-starter";
|
||||
|
||||
private static final Object SOURCE_TYPE = "source";
|
||||
|
||||
private static final Object JAVADOC_TYPE = "javadoc";
|
||||
|
||||
private static final Set<String> POM_ONLY_DEPENDENCIES;
|
||||
static {
|
||||
Set<String> dependencies = new HashSet<String>();
|
||||
dependencies.add("spring-boot-dependencies");
|
||||
dependencies.add("spring-boot-parent");
|
||||
dependencies.add("spring-boot-starters");
|
||||
POM_ONLY_DEPENDENCIES = Collections.unmodifiableSet(dependencies);
|
||||
}
|
||||
|
||||
private final List<DependencyResolver> springSnapshotResolvers = new ArrayList<DependencyResolver>();
|
||||
|
||||
public SpringBootResolver(List<DependencyResolver> resolvers) {
|
||||
for (DependencyResolver resolver : resolvers) {
|
||||
add(resolver);
|
||||
}
|
||||
}
|
||||
|
||||
public void addSpringSnapshotResolver(DependencyResolver resolver) {
|
||||
add(resolver);
|
||||
this.springSnapshotResolvers.add(resolver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArtifactOrigin locate(Artifact artifact) {
|
||||
if (isUnresolvable(artifact)) {
|
||||
return null;
|
||||
}
|
||||
if (isSpringSnapshot(artifact)) {
|
||||
for (DependencyResolver resolver : this.springSnapshotResolvers) {
|
||||
ArtifactOrigin origin = resolver.locate(artifact);
|
||||
if (origin != null) {
|
||||
return origin;
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.locate(artifact);
|
||||
}
|
||||
|
||||
private boolean isUnresolvable(Artifact artifact) {
|
||||
try {
|
||||
ModuleId moduleId = artifact.getId().getArtifactId().getModuleId();
|
||||
if (SPRING_BOOT_GROUP_ID.equals(moduleId.getOrganisation())) {
|
||||
// Skip any POM only deps if they are not pom ext
|
||||
if (POM_ONLY_DEPENDENCIES.contains(moduleId.getName())
|
||||
&& !("pom".equalsIgnoreCase(artifact.getId().getExt()))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Skip starter javadoc and source
|
||||
if (moduleId.getName().startsWith(STARTER_PREFIX)
|
||||
&& (SOURCE_TYPE.equals(artifact.getType()) || JAVADOC_TYPE
|
||||
.equals(artifact.getType()))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSpringSnapshot(Artifact artifact) {
|
||||
try {
|
||||
ModuleId moduleId = artifact.getId().getArtifactId().getModuleId();
|
||||
String revision = artifact.getModuleRevisionId().getRevision();
|
||||
return (SPRING_BOOT_GROUP_ID.equals(moduleId.getOrganisation()) && (revision
|
||||
.endsWith("SNAPSHOT") || revision.contains("M")));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Variant of {@link URLRepository} used to fix the 'localm2' so that when the local
|
||||
* repo contains a POM but not an artifact we continue to maven central.
|
||||
* @see "http://issues.gradle.org/browse/GRADLE-2034"
|
||||
*/
|
||||
private static class LocalM2Repository extends URLRepository {
|
||||
|
||||
private Map<String, Resource> resourcesCache = new HashMap<String, Resource>();
|
||||
|
||||
@Override
|
||||
public Resource getResource(String source) throws IOException {
|
||||
Resource resource = this.resourcesCache.get(source);
|
||||
if (resource == null) {
|
||||
URL url = new URL(source);
|
||||
resource = new LocalM2Resource(url);
|
||||
this.resourcesCache.put(source, resource);
|
||||
}
|
||||
return resource;
|
||||
}
|
||||
|
||||
private static class LocalM2Resource extends URLResource {
|
||||
|
||||
private Boolean artifactExists;
|
||||
|
||||
public LocalM2Resource(URL url) {
|
||||
super(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
if (getURL().getPath().endsWith(".pom")) {
|
||||
return super.exists() && artifactExists();
|
||||
}
|
||||
return super.exists();
|
||||
}
|
||||
|
||||
private boolean artifactExists() {
|
||||
if (this.artifactExists == null) {
|
||||
try {
|
||||
final String packaging = getPackaging();
|
||||
if ("pom".equals(packaging)) {
|
||||
this.artifactExists = true;
|
||||
}
|
||||
else {
|
||||
File parent = new File(getURL().toURI()).getParentFile();
|
||||
File[] artifactFiles = parent.listFiles(new FileFilter() {
|
||||
@Override
|
||||
public boolean accept(File file) {
|
||||
return file.getName().endsWith("." + packaging);
|
||||
}
|
||||
});
|
||||
this.artifactExists = artifactFiles.length > 0;
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
return this.artifactExists;
|
||||
}
|
||||
|
||||
private String getPackaging() throws IOException, SAXException {
|
||||
PomReader reader = new PomReader(getURL(), this);
|
||||
String packaging = reader.getPackaging();
|
||||
if ("bundle".equals(packaging)) {
|
||||
packaging = "jar";
|
||||
}
|
||||
return packaging;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link MessageLogger} to provide simple progress information.
|
||||
*/
|
||||
private static class DownloadingLog extends AbstractMessageLogger {
|
||||
|
||||
private static final long INITIAL_DELAY = TimeUnit.SECONDS.toMillis(3);
|
||||
|
||||
private static final long PROGRESS_DELAY = TimeUnit.SECONDS.toMillis(1);
|
||||
|
||||
private long startTime = System.currentTimeMillis();
|
||||
|
||||
private long lastProgressTime = System.currentTimeMillis();
|
||||
|
||||
private boolean started;
|
||||
|
||||
private boolean finished;
|
||||
|
||||
@Override
|
||||
public void log(String msg, int level) {
|
||||
logDownloadingMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rawlog(String msg, int level) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doProgress() {
|
||||
logDownloadingMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEndProgress(String msg) {
|
||||
}
|
||||
|
||||
private void logDownloadingMessage() {
|
||||
if (!this.finished
|
||||
&& System.currentTimeMillis() - this.startTime > INITIAL_DELAY) {
|
||||
if (!this.started) {
|
||||
this.started = true;
|
||||
Log.infoPrint("Downloading dependencies..");
|
||||
this.lastProgressTime = System.currentTimeMillis();
|
||||
}
|
||||
else if (System.currentTimeMillis() - this.lastProgressTime > PROGRESS_DELAY) {
|
||||
Log.infoPrint(".");
|
||||
this.lastProgressTime = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void finished() {
|
||||
if (!this.finished) {
|
||||
this.finished = true;
|
||||
if (this.started) {
|
||||
Log.info("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* 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.springframework.boot.cli.compiler;
|
||||
|
||||
import groovy.grape.Grape;
|
||||
import groovy.grape.GrapeEngine;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class GrapeEngineInstaller {
|
||||
|
||||
private final GrapeEngine grapeEngine;
|
||||
|
||||
public GrapeEngineInstaller(GrapeEngine grapeEngine) {
|
||||
this.grapeEngine = grapeEngine;
|
||||
}
|
||||
|
||||
public void install() {
|
||||
synchronized (Grape.class) {
|
||||
try {
|
||||
Field instanceField = Grape.class.getDeclaredField("instance");
|
||||
instanceField.setAccessible(true);
|
||||
|
||||
GrapeEngine existingGrapeEngine = (GrapeEngine) instanceField.get(null);
|
||||
|
||||
if (existingGrapeEngine == null) {
|
||||
instanceField.set(null, this.grapeEngine);
|
||||
}
|
||||
else if (!existingGrapeEngine.getClass().equals(
|
||||
this.grapeEngine.getClass())) {
|
||||
throw new IllegalStateException(
|
||||
"Another GrapeEngine of a different type has already been initialized");
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Failed to install GrapeEngine", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* 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.springframework.boot.cli.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* File utility methods
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class FileUtils {
|
||||
|
||||
private FileUtils() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively deletes the given {@code file} and all files beneath it.
|
||||
* @param file The root of the structure to delete
|
||||
* @throw IllegalStateException if the delete fails
|
||||
*/
|
||||
public static void recursiveDelete(File file) {
|
||||
if (file.exists()) {
|
||||
if (file.isDirectory()) {
|
||||
for (File inDir : file.listFiles()) {
|
||||
recursiveDelete(inDir);
|
||||
}
|
||||
}
|
||||
if (!file.delete()) {
|
||||
throw new IllegalStateException("Failed to delete " + file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists the given {@code file} and all the files beneath it.
|
||||
* @param file The root of the structure to delete
|
||||
* @return The list of files and directories
|
||||
*/
|
||||
public static List<File> recursiveList(File file) {
|
||||
List<File> files = new ArrayList<File>();
|
||||
if (file.isDirectory()) {
|
||||
for (File inDir : file.listFiles()) {
|
||||
files.addAll(recursiveList(inDir));
|
||||
}
|
||||
}
|
||||
files.add(file);
|
||||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the data read from the given {@code source} {@link URL} to the given
|
||||
* {@code target} {@link File}.
|
||||
* @param source The source to copy from
|
||||
* @param target The target to copy to
|
||||
*/
|
||||
public static void copy(URL source, File target) {
|
||||
InputStream input = null;
|
||||
OutputStream output = null;
|
||||
try {
|
||||
input = source.openStream();
|
||||
output = new FileOutputStream(target);
|
||||
IoUtils.copy(input, output);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException("Failed to copy '" + source + "' to '"
|
||||
+ target + "'", ex);
|
||||
}
|
||||
finally {
|
||||
IoUtils.closeQuietly(input, output);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* 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.springframework.boot.cli.util;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* General IO utility methods
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class IoUtils {
|
||||
|
||||
private IoUtils() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the entire contents of the resource referenced by {@code uri} and returns
|
||||
* them as a String.
|
||||
* @param uri The resource to read
|
||||
* @return The contents of the resource
|
||||
*/
|
||||
public static String readEntirely(String uri) {
|
||||
try {
|
||||
InputStream stream = URI.create(uri).toURL().openStream();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
|
||||
String line;
|
||||
StringBuilder result = new StringBuilder();
|
||||
while ((line = reader.readLine()) != null) {
|
||||
result.append(line);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the data read from {@code input} into {@code output}.
|
||||
* <p>
|
||||
* <strong>Note:</strong> it is the caller's responsibility to close the streams
|
||||
* @param input The stream to read data from
|
||||
* @param output The stream to write data to
|
||||
* @throws IOException if the copy fails
|
||||
*/
|
||||
public static void copy(InputStream input, OutputStream output) throws IOException {
|
||||
byte[] buffer = new byte[4096];
|
||||
int read;
|
||||
while ((read = input.read(buffer)) >= 0) {
|
||||
output.write(buffer, 0, read);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Quietly closes the given {@link Closeables Closeable}. Any exceptions thrown by
|
||||
* {@link Closeable#close() close()} are swallowed. Any {@code null}
|
||||
* {@code Closeables} are ignored.
|
||||
* @param closeables The {@link Closeable closeables} to close
|
||||
*/
|
||||
public static void closeQuietly(Closeable... closeables) {
|
||||
for (Closeable closeable : closeables) {
|
||||
if (closeable != null) {
|
||||
try {
|
||||
closeable.close();
|
||||
}
|
||||
catch (IOException ioe) {
|
||||
// Closing quietly
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,161 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* 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.springframework.boot.cli.compiler;
|
||||
|
||||
import groovy.grape.GrapeIvy;
|
||||
import groovy.grape.IvyGrabRecord;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.ivy.core.module.id.ModuleId;
|
||||
import org.apache.ivy.core.module.id.ModuleRevisionId;
|
||||
import org.apache.ivy.core.report.ResolveReport;
|
||||
import org.apache.ivy.plugins.resolver.ChainResolver;
|
||||
import org.apache.ivy.plugins.resolver.IBiblioResolver;
|
||||
import org.apache.ivy.util.FileUtil;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class GrapeEngineCustomizerTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException expected = ExpectedException.none();
|
||||
private String level;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.level = System.getProperty("ivy.message.logger.level");
|
||||
System.setProperty("ivy.message.logger.level", "3");
|
||||
System.setProperty("disableSpringSnapshotRepos", "true");
|
||||
}
|
||||
|
||||
@After
|
||||
public void shutdown() {
|
||||
if (this.level == null) {
|
||||
System.clearProperty("ivy.message.logger.level");
|
||||
}
|
||||
else {
|
||||
System.setProperty("ivy.message.logger.level", this.level);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void vanillaEngineWithPomExistsAndJarDoesToo() throws Exception {
|
||||
GrapeIvy engine = new GrapeIvy();
|
||||
prepareFoo(engine, true);
|
||||
ResolveReport report = resolveFoo(engine, "foo", "foo", "1.0");
|
||||
assertFalse(report.hasError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void vanillaEngineWithPomExistsButJarDoesNot() throws Exception {
|
||||
GrapeIvy engine = new GrapeIvy();
|
||||
prepareFoo(engine, false);
|
||||
this.expected.expectMessage("Error grabbing Grapes");
|
||||
ResolveReport report = resolveFoo(engine, "foo", "foo", "1.0");
|
||||
assertTrue(report.hasError());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void customizedEngineWithPomExistsButJarCanBeResolved() throws Exception {
|
||||
|
||||
GrapeIvy engine = new GrapeIvy();
|
||||
GrapeEngineCustomizer customizer = new GrapeEngineCustomizer(engine);
|
||||
ChainResolver grapesResolver = (ChainResolver) engine.getSettings().getResolver(
|
||||
"downloadGrapes");
|
||||
|
||||
// Add a resolver that will actually resolve the artifact
|
||||
IBiblioResolver resolver = new IBiblioResolver();
|
||||
resolver.setName("target");
|
||||
resolver.setRoot("file:" + System.getProperty("user.dir") + "/target/repository");
|
||||
resolver.setM2compatible(true);
|
||||
resolver.setSettings(engine.getSettings());
|
||||
grapesResolver.getResolvers().add(resolver);
|
||||
|
||||
// Allow resolvers to be customized
|
||||
customizer.customize();
|
||||
prepareFoo(engine, false);
|
||||
prepareFoo(engine, "target/repository/foo/foo/1.0", true);
|
||||
ResolveReport report = resolveFoo(engine, "foo", "foo", "1.0");
|
||||
assertFalse(report.hasError());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customizedEngineWithPomExistsButJarCannotBeResolved() throws Exception {
|
||||
|
||||
GrapeIvy engine = new GrapeIvy();
|
||||
GrapeEngineCustomizer customizer = new GrapeEngineCustomizer(engine);
|
||||
|
||||
// Allow resolvers to be customized
|
||||
customizer.customize();
|
||||
prepareFoo(engine, false);
|
||||
this.expected.expectMessage("Error grabbing Grapes");
|
||||
ResolveReport report = resolveFoo(engine, "foo", "foo", "1.0");
|
||||
assertFalse(report.hasError());
|
||||
|
||||
}
|
||||
|
||||
private ResolveReport resolveFoo(GrapeIvy engine, String group, String artifact,
|
||||
String version) {
|
||||
Map<String, Object> args = new HashMap<String, Object>();
|
||||
args.put("autoDownload", true);
|
||||
IvyGrabRecord record = new IvyGrabRecord();
|
||||
record.setConf(Arrays.asList("default"));
|
||||
record.setForce(true);
|
||||
record.setTransitive(true);
|
||||
record.setExt("");
|
||||
record.setType("");
|
||||
record.setMrid(new ModuleRevisionId(new ModuleId(group, artifact), version));
|
||||
ResolveReport report = engine.getDependencies(args, record);
|
||||
return report;
|
||||
}
|
||||
|
||||
private void prepareFoo(GrapeIvy engine, boolean includeJar) throws IOException {
|
||||
prepareFoo(engine, System.getProperty("user.home")
|
||||
+ "/.m2/repository/foo/foo/1.0", includeJar);
|
||||
}
|
||||
|
||||
private void prepareFoo(GrapeIvy engine, String root, boolean includeJar)
|
||||
throws IOException {
|
||||
File maven = new File(root);
|
||||
FileUtil.forceDelete(maven);
|
||||
FileUtil.copy(new File("src/test/resources/foo.pom"), new File(maven,
|
||||
"foo-1.0.pom"), null);
|
||||
if (includeJar) {
|
||||
FileUtil.copy(new File("src/test/resources/foo.jar"), new File(maven,
|
||||
"foo-1.0.jar"), null);
|
||||
}
|
||||
File ivy = new File(engine.getGrapeCacheDir() + "/foo");
|
||||
FileUtil.forceDelete(ivy);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue