Support @Grab when app has multiple groovy scripts

The AST transformation that processes @Grab annotations is driven once
per source file. Previously, this meant that if an app consisted of
multiple source files then multiple, discrete dependency resolutions
would be performed.

This commit updates AetherGrapeEngine to cache a previous resolution's
outcome and use its dependency to influence the outcome of subsequent
resolutions. For example if a one resolution results in spring-core
4.0.0.RELEASE being added to the classpath, subsequent resolutions
that depend upon spring-core will always get the 4.0.0.RELEASE
version. This is achieved by using the dependencies found by earlier
resolutions as dependency management configuration of the current
resolution. This removes the possibility of multiple versions of the
same dependency ending up on the classpath.

In addition to using the results of earlier resolutions to provide
dependency management configuration, default dependency management
configuration is also provided. This configuration is specified by
the springcli.properties file and ensures that, where Boot prescribes
certain versions of a dependency, that is the version that will be
resolved. For example, this ensures that spring-data-redis, which
depends upon Spring 3.1.4, pulls in the version of Spring that Boot
requires instead.

Fixes #224
This commit is contained in:
Andy Wilkinson 2014-01-15 10:51:42 +00:00
parent b45683f103
commit 5fb42c3c33
4 changed files with 152 additions and 2 deletions

View File

@ -58,6 +58,8 @@ public class AetherGrapeEngine implements GrapeEngine {
private static final Collection<Exclusion> WILDCARD_EXCLUSION = Arrays
.asList(new Exclusion("*", "*", "*", "*"));
private final List<Dependency> managedDependencies = new ArrayList<Dependency>();
private final ProgressReporter progressReporter;
private final GroovyClassLoader classLoader;
@ -71,10 +73,12 @@ public class AetherGrapeEngine implements GrapeEngine {
public AetherGrapeEngine(GroovyClassLoader classLoader,
RepositorySystem repositorySystem,
DefaultRepositorySystemSession repositorySystemSession,
List<RemoteRepository> remoteRepositories) {
List<RemoteRepository> remoteRepositories,
List<Dependency> managedDependencies) {
this.classLoader = classLoader;
this.repositorySystem = repositorySystem;
this.session = repositorySystemSession;
this.managedDependencies.addAll(managedDependencies);
this.repositories = new ArrayList<RemoteRepository>();
List<RemoteRepository> remotes = new ArrayList<RemoteRepository>(
@ -174,13 +178,20 @@ public class AetherGrapeEngine implements GrapeEngine {
private List<File> resolve(List<Dependency> dependencies)
throws ArtifactResolutionException {
try {
CollectRequest collectRequest = new CollectRequest((Dependency) null,
dependencies, new ArrayList<RemoteRepository>(this.repositories));
collectRequest.setManagedDependencies(this.managedDependencies);
DependencyRequest dependencyRequest = new DependencyRequest(collectRequest,
DependencyFilterUtils.classpathFilter(JavaScopes.COMPILE));
DependencyResult dependencyResult = this.repositorySystem
.resolveDependencies(this.session, dependencyRequest);
this.managedDependencies.addAll(getDependencies(dependencyResult));
return getFiles(dependencyResult);
}
catch (Exception ex) {
@ -191,6 +202,15 @@ public class AetherGrapeEngine implements GrapeEngine {
}
}
private List<Dependency> getDependencies(DependencyResult dependencyResult) {
List<Dependency> dependencies = new ArrayList<Dependency>();
for (ArtifactResult artifactResult : dependencyResult.getArtifactResults()) {
dependencies.add(new Dependency(artifactResult.getArtifact(),
JavaScopes.COMPILE));
}
return dependencies;
}
private List<File> getFiles(DependencyResult dependencyResult) {
List<File> files = new ArrayList<File>();
for (ArtifactResult result : dependencyResult.getArtifactResults()) {

View File

@ -26,6 +26,7 @@ import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.impl.DefaultServiceLocator;
import org.eclipse.aether.internal.impl.DefaultRepositorySystem;
import org.eclipse.aether.repository.RemoteRepository;
@ -62,8 +63,12 @@ public abstract class AetherGrapeEngineFactory {
new DefaultRepositorySystemSessionAutoConfiguration().apply(
repositorySystemSession, repositorySystem);
List<Dependency> managedDependencies = new PropertiesManagedDependenciesFactory()
.getManagedDependencies();
return new AetherGrapeEngine(classLoader, repositorySystem,
repositorySystemSession, createRepositories(repositoryConfigurations));
repositorySystemSession, createRepositories(repositoryConfigurations),
managedDependencies);
}
private static ServiceLocator createServiceLocator() {

View File

@ -0,0 +1,32 @@
/*
* Copyright 2012-2014 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.grape;
import java.util.List;
import org.eclipse.aether.graph.Dependency;
/**
* An abstraction for accessing the managed dependencies that should be used to influence
* the outcome of dependency resolution performed by Aether.
*
* @author Andy Wilkinson
*/
public interface ManagedDependenciesFactory {
List<Dependency> getManagedDependencies();
}

View File

@ -0,0 +1,93 @@
/*
* Copyright 2012-2014 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.grape;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Properties;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.util.artifact.JavaScopes;
import org.springframework.util.Assert;
/**
* A {@link ManagedDependenciesFactory} that uses a properties file to configure the list
* of managed dependencies that it returns.
*
* @author Andy Wilkinson
*/
class PropertiesManagedDependenciesFactory implements ManagedDependenciesFactory {
private static final String PROPERTY_SUFFIX_GROUP_ID = ".groupId";
private static final String PROPERTY_SUFFIX_VERSION = ".version";
private final List<Dependency> managedDependencies;
public PropertiesManagedDependenciesFactory() {
Properties properties = loadProperties();
this.managedDependencies = getManagedDependencies(properties);
}
private static Properties loadProperties() {
Properties properties = new Properties();
InputStream inputStream = PropertiesManagedDependenciesFactory.class
.getClassLoader().getResourceAsStream("META-INF/springcli.properties");
Assert.state(inputStream != null, "Unable to load springcli properties");
try {
properties.load(inputStream);
return properties;
}
catch (IOException ex) {
throw new IllegalStateException("Unable to load springcli properties", ex);
}
}
private static List<Dependency> getManagedDependencies(Properties properties) {
List<Dependency> dependencies = new ArrayList<Dependency>();
for (Entry<Object, Object> entry : properties.entrySet()) {
String propertyName = (String) entry.getKey();
if (propertyName.endsWith(PROPERTY_SUFFIX_GROUP_ID)) {
String artifactId = propertyName.substring(0, propertyName.length()
- PROPERTY_SUFFIX_GROUP_ID.length());
String groupId = (String) entry.getValue();
String version = properties.getProperty(artifactId
+ PROPERTY_SUFFIX_VERSION);
if (version != null) {
Artifact artifact = new DefaultArtifact(groupId, artifactId, "jar",
version);
dependencies.add(new Dependency(artifact, JavaScopes.COMPILE));
}
}
}
return dependencies;
}
@Override
public List<Dependency> getManagedDependencies() {
return new ArrayList<Dependency>(this.managedDependencies);
}
}