From 5fb42c3c3351100db0ac0d832a7acfc1ac763cf1 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 15 Jan 2014 10:51:42 +0000 Subject: [PATCH] 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 --- .../cli/compiler/grape/AetherGrapeEngine.java | 22 ++++- .../grape/AetherGrapeEngineFactory.java | 7 +- .../grape/ManagedDependenciesFactory.java | 32 +++++++ .../PropertiesManagedDependenciesFactory.java | 93 +++++++++++++++++++ 4 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/ManagedDependenciesFactory.java create mode 100644 spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/PropertiesManagedDependenciesFactory.java diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngine.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngine.java index aad0c84782..3e0f06dc45 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngine.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngine.java @@ -58,6 +58,8 @@ public class AetherGrapeEngine implements GrapeEngine { private static final Collection WILDCARD_EXCLUSION = Arrays .asList(new Exclusion("*", "*", "*", "*")); + private final List managedDependencies = new ArrayList(); + 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 remoteRepositories) { + List remoteRepositories, + List managedDependencies) { this.classLoader = classLoader; this.repositorySystem = repositorySystem; this.session = repositorySystemSession; + this.managedDependencies.addAll(managedDependencies); this.repositories = new ArrayList(); List remotes = new ArrayList( @@ -174,13 +178,20 @@ public class AetherGrapeEngine implements GrapeEngine { private List resolve(List dependencies) throws ArtifactResolutionException { + try { CollectRequest collectRequest = new CollectRequest((Dependency) null, dependencies, new ArrayList(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 getDependencies(DependencyResult dependencyResult) { + List dependencies = new ArrayList(); + for (ArtifactResult artifactResult : dependencyResult.getArtifactResults()) { + dependencies.add(new Dependency(artifactResult.getArtifact(), + JavaScopes.COMPILE)); + } + return dependencies; + } + private List getFiles(DependencyResult dependencyResult) { List files = new ArrayList(); for (ArtifactResult result : dependencyResult.getArtifactResults()) { diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngineFactory.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngineFactory.java index 5d36785d9a..a0b2dfd294 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngineFactory.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/AetherGrapeEngineFactory.java @@ -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 managedDependencies = new PropertiesManagedDependenciesFactory() + .getManagedDependencies(); + return new AetherGrapeEngine(classLoader, repositorySystem, - repositorySystemSession, createRepositories(repositoryConfigurations)); + repositorySystemSession, createRepositories(repositoryConfigurations), + managedDependencies); } private static ServiceLocator createServiceLocator() { diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/ManagedDependenciesFactory.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/ManagedDependenciesFactory.java new file mode 100644 index 0000000000..ae1482318b --- /dev/null +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/ManagedDependenciesFactory.java @@ -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 getManagedDependencies(); +} diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/PropertiesManagedDependenciesFactory.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/PropertiesManagedDependenciesFactory.java new file mode 100644 index 0000000000..3cea373c1c --- /dev/null +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/PropertiesManagedDependenciesFactory.java @@ -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 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 getManagedDependencies(Properties properties) { + List dependencies = new ArrayList(); + + for (Entry 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 getManagedDependencies() { + return new ArrayList(this.managedDependencies); + } + +}