Allow custom dependency metadata to be used with the CLI
Add support for a new annotation, @GrabMetadata, that can be used
to provide the coordinates of one or more properties files, such as
the one published by Spring IO Platform, as a source of dependency
metadata. For example:
@GrabMetadata("com.example:metadata:1.0.0")
The referenced properties files must be in the format
group:module=version.
Limitations:
- Only a single @GrabMetadata annotation is supported
- The referenced properties file must be accessible in one of the
default repositories, i.e. it cannot be accessed in a repository
that's added using @GrabResolver
Closes #814
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.codehaus.groovy.ast.ASTNode;
|
||||
import org.codehaus.groovy.ast.AnnotatedNode;
|
||||
import org.codehaus.groovy.ast.AnnotationNode;
|
||||
import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
|
||||
import org.codehaus.groovy.ast.ClassNode;
|
||||
import org.codehaus.groovy.ast.ImportNode;
|
||||
import org.codehaus.groovy.ast.ModuleNode;
|
||||
import org.codehaus.groovy.control.SourceUnit;
|
||||
import org.codehaus.groovy.transform.ASTTransformation;
|
||||
|
||||
/**
|
||||
* A base class for {@link ASTTransformation AST transformations} that are solely
|
||||
* interested in {@link AnnotatedNode AnnotatedNodes}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public abstract class AnnotatedNodeASTTransformation implements ASTTransformation {
|
||||
|
||||
private final Set<String> interestingAnnotationNames;
|
||||
|
||||
private List<AnnotationNode> annotationNodes = new ArrayList<AnnotationNode>();
|
||||
|
||||
private SourceUnit sourceUnit;
|
||||
|
||||
protected AnnotatedNodeASTTransformation(Set<String> interestingAnnotationNames) {
|
||||
this.interestingAnnotationNames = interestingAnnotationNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(ASTNode[] nodes, SourceUnit source) {
|
||||
this.sourceUnit = source;
|
||||
|
||||
ClassVisitor classVisitor = new ClassVisitor(source);
|
||||
for (ASTNode node : nodes) {
|
||||
if (node instanceof ModuleNode) {
|
||||
ModuleNode module = (ModuleNode) node;
|
||||
|
||||
visitAnnotatedNode(module.getPackage());
|
||||
|
||||
for (ImportNode importNode : module.getImports()) {
|
||||
visitAnnotatedNode(importNode);
|
||||
}
|
||||
for (ImportNode importNode : module.getStarImports()) {
|
||||
visitAnnotatedNode(importNode);
|
||||
}
|
||||
for (Map.Entry<String, ImportNode> entry : module.getStaticImports()
|
||||
.entrySet()) {
|
||||
visitAnnotatedNode(entry.getValue());
|
||||
}
|
||||
for (Map.Entry<String, ImportNode> entry : module.getStaticStarImports()
|
||||
.entrySet()) {
|
||||
visitAnnotatedNode(entry.getValue());
|
||||
}
|
||||
|
||||
for (ClassNode classNode : module.getClasses()) {
|
||||
visitAnnotatedNode(classNode);
|
||||
classNode.visitContents(classVisitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processAnnotationNodes(this.annotationNodes);
|
||||
}
|
||||
|
||||
protected SourceUnit getSourceUnit() {
|
||||
return this.sourceUnit;
|
||||
}
|
||||
|
||||
protected abstract void processAnnotationNodes(List<AnnotationNode> annotationNodes);
|
||||
|
||||
private void visitAnnotatedNode(AnnotatedNode annotatedNode) {
|
||||
if (annotatedNode != null) {
|
||||
for (AnnotationNode annotationNode : annotatedNode.getAnnotations()) {
|
||||
if (this.interestingAnnotationNames.contains(annotationNode
|
||||
.getClassNode().getName())) {
|
||||
this.annotationNodes.add(annotationNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ClassVisitor extends ClassCodeVisitorSupport {
|
||||
|
||||
private final SourceUnit source;
|
||||
|
||||
public ClassVisitor(SourceUnit source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SourceUnit getSourceUnit() {
|
||||
return this.source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitAnnotations(AnnotatedNode node) {
|
||||
visitAnnotatedNode(node);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -23,13 +23,12 @@ import org.codehaus.groovy.ast.ClassNode;
|
||||
import org.codehaus.groovy.ast.ModuleNode;
|
||||
import org.codehaus.groovy.control.SourceUnit;
|
||||
import org.codehaus.groovy.transform.ASTTransformation;
|
||||
import org.springframework.boot.cli.compiler.dependencies.ArtifactCoordinatesResolver;
|
||||
|
||||
/**
|
||||
* {@link ASTTransformation} to apply
|
||||
* {@link CompilerAutoConfiguration#applyDependencies(DependencyCustomizer) dependency
|
||||
* auto-configuration}.
|
||||
*
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Dave Syer
|
||||
* @author Andy Wilkinson
|
||||
@@ -38,15 +37,15 @@ public class DependencyAutoConfigurationTransformation implements ASTTransformat
|
||||
|
||||
private final GroovyClassLoader loader;
|
||||
|
||||
private final ArtifactCoordinatesResolver coordinatesResolver;
|
||||
private final DependencyResolutionContext dependencyResolutionContext;
|
||||
|
||||
private final Iterable<CompilerAutoConfiguration> compilerAutoConfigurations;
|
||||
|
||||
public DependencyAutoConfigurationTransformation(GroovyClassLoader loader,
|
||||
ArtifactCoordinatesResolver coordinatesResolver,
|
||||
DependencyResolutionContext dependencyResolutionContext,
|
||||
Iterable<CompilerAutoConfiguration> compilerAutoConfigurations) {
|
||||
this.loader = loader;
|
||||
this.coordinatesResolver = coordinatesResolver;
|
||||
this.dependencyResolutionContext = dependencyResolutionContext;
|
||||
this.compilerAutoConfigurations = compilerAutoConfigurations;
|
||||
|
||||
}
|
||||
@@ -62,7 +61,7 @@ public class DependencyAutoConfigurationTransformation implements ASTTransformat
|
||||
|
||||
private void visitModule(ModuleNode module) {
|
||||
DependencyCustomizer dependencies = new DependencyCustomizer(this.loader, module,
|
||||
this.coordinatesResolver);
|
||||
this.dependencyResolutionContext);
|
||||
for (ClassNode classNode : module.getClasses()) {
|
||||
for (CompilerAutoConfiguration autoConfiguration : this.compilerAutoConfigurations) {
|
||||
if (autoConfiguration.matches(classNode)) {
|
||||
|
||||
@@ -32,7 +32,7 @@ import org.springframework.boot.cli.compiler.dependencies.ArtifactCoordinatesRes
|
||||
* <p>
|
||||
* This class provides a fluent API for conditionally adding dependencies. For example:
|
||||
* {@code dependencies.ifMissing("com.corp.SomeClass").add(module)}.
|
||||
*
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@@ -42,17 +42,17 @@ public class DependencyCustomizer {
|
||||
|
||||
private final ClassNode classNode;
|
||||
|
||||
private final ArtifactCoordinatesResolver coordinatesResolver;
|
||||
private final DependencyResolutionContext dependencyResolutionContext;
|
||||
|
||||
/**
|
||||
* Create a new {@link DependencyCustomizer} instance.
|
||||
* @param loader
|
||||
*/
|
||||
public DependencyCustomizer(GroovyClassLoader loader, ModuleNode moduleNode,
|
||||
ArtifactCoordinatesResolver coordinatesResolver) {
|
||||
DependencyResolutionContext dependencyResolutionContext) {
|
||||
this.loader = loader;
|
||||
this.classNode = moduleNode.getClasses().get(0);
|
||||
this.coordinatesResolver = coordinatesResolver;
|
||||
this.dependencyResolutionContext = dependencyResolutionContext;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,7 +62,7 @@ public class DependencyCustomizer {
|
||||
protected DependencyCustomizer(DependencyCustomizer parent) {
|
||||
this.loader = parent.loader;
|
||||
this.classNode = parent.classNode;
|
||||
this.coordinatesResolver = parent.coordinatesResolver;
|
||||
this.dependencyResolutionContext = parent.dependencyResolutionContext;
|
||||
}
|
||||
|
||||
public String getVersion(String artifactId) {
|
||||
@@ -71,7 +71,8 @@ public class DependencyCustomizer {
|
||||
}
|
||||
|
||||
public String getVersion(String artifactId, String defaultVersion) {
|
||||
String version = this.coordinatesResolver.getVersion(artifactId);
|
||||
String version = this.dependencyResolutionContext
|
||||
.getArtifactCoordinatesResolver().getVersion(artifactId);
|
||||
if (version == null) {
|
||||
version = defaultVersion;
|
||||
}
|
||||
@@ -201,9 +202,11 @@ public class DependencyCustomizer {
|
||||
*/
|
||||
public DependencyCustomizer add(String module, boolean transitive) {
|
||||
if (canAdd()) {
|
||||
ArtifactCoordinatesResolver artifactCoordinatesResolver = this.dependencyResolutionContext
|
||||
.getArtifactCoordinatesResolver();
|
||||
this.classNode.addAnnotation(createGrabAnnotation(
|
||||
this.coordinatesResolver.getGroupId(module), module,
|
||||
this.coordinatesResolver.getVersion(module), transitive));
|
||||
artifactCoordinatesResolver.getGroupId(module), module,
|
||||
artifactCoordinatesResolver.getVersion(module), transitive));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.aether.graph.Dependency;
|
||||
import org.springframework.boot.cli.compiler.dependencies.ArtifactCoordinatesResolver;
|
||||
import org.springframework.boot.cli.compiler.dependencies.ManagedDependenciesArtifactCoordinatesResolver;
|
||||
import org.springframework.boot.cli.compiler.grape.ManagedDependenciesFactory;
|
||||
import org.springframework.boot.dependency.tools.ManagedDependencies;
|
||||
|
||||
/**
|
||||
* @author Andy Wilkinson
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class DependencyResolutionContext {
|
||||
|
||||
private ArtifactCoordinatesResolver artifactCoordinatesResolver;
|
||||
|
||||
private List<Dependency> managedDependencies = new ArrayList<Dependency>();
|
||||
|
||||
public DependencyResolutionContext() {
|
||||
this(new ManagedDependenciesArtifactCoordinatesResolver());
|
||||
}
|
||||
|
||||
DependencyResolutionContext(ArtifactCoordinatesResolver artifactCoordinatesResolver) {
|
||||
this.artifactCoordinatesResolver = artifactCoordinatesResolver;
|
||||
}
|
||||
|
||||
void setManagedDependencies(ManagedDependencies managedDependencies) {
|
||||
this.artifactCoordinatesResolver = new ManagedDependenciesArtifactCoordinatesResolver(
|
||||
managedDependencies);
|
||||
this.managedDependencies = new ArrayList<Dependency>(
|
||||
new ManagedDependenciesFactory(managedDependencies)
|
||||
.getManagedDependencies());
|
||||
|
||||
}
|
||||
|
||||
ArtifactCoordinatesResolver getArtifactCoordinatesResolver() {
|
||||
return this.artifactCoordinatesResolver;
|
||||
}
|
||||
|
||||
public List<Dependency> getManagedDependencies() {
|
||||
return this.managedDependencies;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import groovy.grape.Grape;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
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 org.codehaus.groovy.ast.ASTNode;
|
||||
import org.codehaus.groovy.ast.AnnotationNode;
|
||||
import org.codehaus.groovy.ast.expr.ConstantExpression;
|
||||
import org.codehaus.groovy.ast.expr.Expression;
|
||||
import org.codehaus.groovy.ast.expr.ListExpression;
|
||||
import org.codehaus.groovy.control.messages.Message;
|
||||
import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
|
||||
import org.codehaus.groovy.syntax.SyntaxException;
|
||||
import org.codehaus.groovy.transform.ASTTransformation;
|
||||
import org.springframework.boot.dependency.tools.ManagedDependencies;
|
||||
import org.springframework.boot.dependency.tools.PropertiesFileManagedDependencies;
|
||||
import org.springframework.boot.dependency.tools.VersionManagedDependencies;
|
||||
import org.springframework.boot.groovy.GrabMetadata;
|
||||
|
||||
/**
|
||||
* {@link ASTTransformation} for processing {@link GrabMetadata @GrabMetadata}
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class GrabMetadataTransformation extends AnnotatedNodeASTTransformation {
|
||||
|
||||
private static final Set<String> GRAB_METADATA_ANNOTATION_NAMES = Collections
|
||||
.unmodifiableSet(new HashSet<String>(Arrays.asList(
|
||||
GrabMetadata.class.getName(), GrabMetadata.class.getSimpleName())));
|
||||
|
||||
private final DependencyResolutionContext resolutionContext;
|
||||
|
||||
public GrabMetadataTransformation(DependencyResolutionContext resolutionContext) {
|
||||
super(GRAB_METADATA_ANNOTATION_NAMES);
|
||||
this.resolutionContext = resolutionContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processAnnotationNodes(List<AnnotationNode> annotationNodes) {
|
||||
if (!annotationNodes.isEmpty()) {
|
||||
if (annotationNodes.size() > 1) {
|
||||
for (AnnotationNode annotationNode : annotationNodes) {
|
||||
handleDuplicateGrabMetadataAnnotation(annotationNode);
|
||||
}
|
||||
}
|
||||
else {
|
||||
processGrabMetadataAnnotation(annotationNodes.get(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processGrabMetadataAnnotation(AnnotationNode annotationNode) {
|
||||
Expression valueExpression = annotationNode.getMember("value");
|
||||
|
||||
List<Map<String, String>> metadataDependencies = createDependencyMaps(valueExpression);
|
||||
updateArtifactCoordinatesResolver(metadataDependencies);
|
||||
}
|
||||
|
||||
private List<Map<String, String>> createDependencyMaps(Expression valueExpression) {
|
||||
Map<String, String> dependency = null;
|
||||
|
||||
List<ConstantExpression> constantExpressions = new ArrayList<ConstantExpression>();
|
||||
|
||||
if (valueExpression instanceof ListExpression) {
|
||||
ListExpression listExpression = (ListExpression) valueExpression;
|
||||
for (Expression expression : listExpression.getExpressions()) {
|
||||
if (expression instanceof ConstantExpression
|
||||
&& ((ConstantExpression) expression).getValue() instanceof String) {
|
||||
constantExpressions.add((ConstantExpression) expression);
|
||||
}
|
||||
else {
|
||||
reportError(
|
||||
"Each entry in the array must be an inline string constant",
|
||||
expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (valueExpression instanceof ConstantExpression
|
||||
&& ((ConstantExpression) valueExpression).getValue() instanceof String) {
|
||||
constantExpressions = Arrays.asList((ConstantExpression) valueExpression);
|
||||
}
|
||||
else {
|
||||
reportError(
|
||||
"@GrabMetadata requires an inline constant that is a string or a string array",
|
||||
valueExpression);
|
||||
}
|
||||
|
||||
List<Map<String, String>> dependencies = new ArrayList<Map<String, String>>(
|
||||
constantExpressions.size());
|
||||
|
||||
for (ConstantExpression expression : constantExpressions) {
|
||||
Object value = expression.getValue();
|
||||
if (value instanceof String) {
|
||||
String[] components = ((String) expression.getValue()).split(":");
|
||||
if (components.length == 3) {
|
||||
dependency = new HashMap<String, String>();
|
||||
dependency.put("group", components[0]);
|
||||
dependency.put("module", components[1]);
|
||||
dependency.put("version", components[2]);
|
||||
dependency.put("type", "properties");
|
||||
|
||||
dependencies.add(dependency);
|
||||
}
|
||||
else {
|
||||
handleMalformedDependency(expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
private void handleMalformedDependency(Expression expression) {
|
||||
Message message = createSyntaxErrorMessage(
|
||||
"The string must be of the form \"group:module:version\"\n", expression);
|
||||
getSourceUnit().getErrorCollector().addErrorAndContinue(message);
|
||||
}
|
||||
|
||||
private void updateArtifactCoordinatesResolver(
|
||||
List<Map<String, String>> metadataDependencies) {
|
||||
URI[] uris = Grape.getInstance().resolve(null,
|
||||
metadataDependencies.toArray(new Map[metadataDependencies.size()]));
|
||||
List<ManagedDependencies> managedDependencies = new ArrayList<ManagedDependencies>(
|
||||
uris.length);
|
||||
for (URI uri : uris) {
|
||||
try {
|
||||
managedDependencies.add(new PropertiesFileManagedDependencies(uri.toURL()
|
||||
.openStream()));
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new IllegalStateException("Failed to parse '" + uris[0]
|
||||
+ "'. Is it a valid properties file?");
|
||||
}
|
||||
}
|
||||
|
||||
this.resolutionContext.setManagedDependencies(new VersionManagedDependencies(
|
||||
managedDependencies));
|
||||
}
|
||||
|
||||
private void handleDuplicateGrabMetadataAnnotation(AnnotationNode annotationNode) {
|
||||
Message message = createSyntaxErrorMessage(
|
||||
"Duplicate @GrabMetadata annotation. It must be declared at most once.",
|
||||
annotationNode);
|
||||
getSourceUnit().getErrorCollector().addErrorAndContinue(message);
|
||||
}
|
||||
|
||||
private void reportError(String message, ASTNode node) {
|
||||
getSourceUnit().getErrorCollector().addErrorAndContinue(
|
||||
createSyntaxErrorMessage(message, node));
|
||||
}
|
||||
|
||||
private Message createSyntaxErrorMessage(String message, ASTNode node) {
|
||||
return new SyntaxErrorMessage(new SyntaxException(message, node.getLineNumber(),
|
||||
node.getColumnNumber(), node.getLastLineNumber(),
|
||||
node.getLastColumnNumber()), getSourceUnit());
|
||||
}
|
||||
}
|
||||
@@ -43,8 +43,6 @@ import org.codehaus.groovy.control.customizers.CompilationCustomizer;
|
||||
import org.codehaus.groovy.control.customizers.ImportCustomizer;
|
||||
import org.codehaus.groovy.transform.ASTTransformation;
|
||||
import org.codehaus.groovy.transform.ASTTransformationVisitor;
|
||||
import org.springframework.boot.cli.compiler.dependencies.ArtifactCoordinatesResolver;
|
||||
import org.springframework.boot.cli.compiler.dependencies.ManagedDependenciesArtifactCoordinatesResolver;
|
||||
import org.springframework.boot.cli.compiler.grape.AetherGrapeEngine;
|
||||
import org.springframework.boot.cli.compiler.grape.AetherGrapeEngineFactory;
|
||||
import org.springframework.boot.cli.compiler.grape.GrapeEngineInstaller;
|
||||
@@ -58,21 +56,19 @@ import org.springframework.boot.cli.util.ResourceUtils;
|
||||
* <li>{@link CompilerAutoConfiguration} strategies will be read from
|
||||
* <code>META-INF/services/org.springframework.boot.cli.compiler.CompilerAutoConfiguration</code>
|
||||
* (per the standard java {@link ServiceLoader} contract) and applied during compilation</li>
|
||||
*
|
||||
*
|
||||
* <li>Multiple classes can be returned if the Groovy source defines more than one Class</li>
|
||||
*
|
||||
*
|
||||
* <li>Generated class files can also be loaded using
|
||||
* {@link ClassLoader#getResource(String)}</li>
|
||||
* </ul>
|
||||
*
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Dave Syer
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class GroovyCompiler {
|
||||
|
||||
private final ArtifactCoordinatesResolver coordinatesResolver;
|
||||
|
||||
private final GroovyCompilerConfiguration configuration;
|
||||
|
||||
private final ExtendedGroovyClassLoader loader;
|
||||
@@ -90,10 +86,10 @@ public class GroovyCompiler {
|
||||
this.configuration = configuration;
|
||||
this.loader = createLoader(configuration);
|
||||
|
||||
this.coordinatesResolver = new ManagedDependenciesArtifactCoordinatesResolver();
|
||||
DependencyResolutionContext resolutionContext = new DependencyResolutionContext();
|
||||
|
||||
AetherGrapeEngine grapeEngine = AetherGrapeEngineFactory.create(this.loader,
|
||||
configuration.getRepositoryConfiguration());
|
||||
configuration.getRepositoryConfiguration(), resolutionContext);
|
||||
|
||||
GrapeEngineInstaller.install(grapeEngine);
|
||||
|
||||
@@ -108,12 +104,13 @@ public class GroovyCompiler {
|
||||
}
|
||||
|
||||
this.transformations = new ArrayList<ASTTransformation>();
|
||||
this.transformations.add(new GrabMetadataTransformation(resolutionContext));
|
||||
this.transformations.add(new DependencyAutoConfigurationTransformation(
|
||||
this.loader, this.coordinatesResolver, this.compilerAutoConfigurations));
|
||||
this.loader, resolutionContext, this.compilerAutoConfigurations));
|
||||
this.transformations.add(new GroovyBeansTransformation());
|
||||
if (this.configuration.isGuessDependencies()) {
|
||||
this.transformations.add(new ResolveDependencyCoordinatesTransformation(
|
||||
this.coordinatesResolver));
|
||||
resolutionContext));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +167,7 @@ public class GroovyCompiler {
|
||||
* @throws IOException
|
||||
*/
|
||||
public Class<?>[] compile(String... sources) throws CompilationFailedException,
|
||||
IOException {
|
||||
IOException {
|
||||
|
||||
this.loader.clearCache();
|
||||
List<Class<?>> classes = new ArrayList<Class<?>>();
|
||||
@@ -290,9 +287,9 @@ public class GroovyCompiler {
|
||||
classNode);
|
||||
}
|
||||
autoConfiguration
|
||||
.apply(GroovyCompiler.this.loader,
|
||||
GroovyCompiler.this.configuration, context, source,
|
||||
classNode);
|
||||
.apply(GroovyCompiler.this.loader,
|
||||
GroovyCompiler.this.configuration, context, source,
|
||||
classNode);
|
||||
}
|
||||
}
|
||||
importCustomizer.call(source, context, classNode);
|
||||
|
||||
@@ -21,81 +21,39 @@ import groovy.lang.Grab;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.codehaus.groovy.ast.ASTNode;
|
||||
import org.codehaus.groovy.ast.AnnotatedNode;
|
||||
import org.codehaus.groovy.ast.AnnotationNode;
|
||||
import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
|
||||
import org.codehaus.groovy.ast.ClassNode;
|
||||
import org.codehaus.groovy.ast.ImportNode;
|
||||
import org.codehaus.groovy.ast.ModuleNode;
|
||||
import org.codehaus.groovy.ast.expr.ConstantExpression;
|
||||
import org.codehaus.groovy.ast.expr.Expression;
|
||||
import org.codehaus.groovy.control.SourceUnit;
|
||||
import org.codehaus.groovy.transform.ASTTransformation;
|
||||
import org.springframework.boot.cli.compiler.dependencies.ArtifactCoordinatesResolver;
|
||||
|
||||
/**
|
||||
* {@link ASTTransformation} to resolve {@link Grab} artifact coordinates.
|
||||
*
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class ResolveDependencyCoordinatesTransformation implements ASTTransformation {
|
||||
public class ResolveDependencyCoordinatesTransformation extends
|
||||
AnnotatedNodeASTTransformation {
|
||||
|
||||
private static final Set<String> GRAB_ANNOTATION_NAMES = Collections
|
||||
.unmodifiableSet(new HashSet<String>(Arrays.asList(Grab.class.getName(),
|
||||
Grab.class.getSimpleName())));
|
||||
|
||||
private final ArtifactCoordinatesResolver coordinatesResolver;
|
||||
private final DependencyResolutionContext resolutionContext;
|
||||
|
||||
public ResolveDependencyCoordinatesTransformation(
|
||||
ArtifactCoordinatesResolver coordinatesResolver) {
|
||||
this.coordinatesResolver = coordinatesResolver;
|
||||
DependencyResolutionContext resolutionContext) {
|
||||
super(GRAB_ANNOTATION_NAMES);
|
||||
this.resolutionContext = resolutionContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(ASTNode[] nodes, SourceUnit source) {
|
||||
ClassVisitor classVisitor = new ClassVisitor(source);
|
||||
for (ASTNode node : nodes) {
|
||||
if (node instanceof ModuleNode) {
|
||||
ModuleNode module = (ModuleNode) node;
|
||||
|
||||
visitAnnotatedNode(module.getPackage());
|
||||
|
||||
for (ImportNode importNode : module.getImports()) {
|
||||
visitAnnotatedNode(importNode);
|
||||
}
|
||||
for (ImportNode importNode : module.getStarImports()) {
|
||||
visitAnnotatedNode(importNode);
|
||||
}
|
||||
for (Map.Entry<String, ImportNode> entry : module.getStaticImports()
|
||||
.entrySet()) {
|
||||
visitAnnotatedNode(entry.getValue());
|
||||
}
|
||||
for (Map.Entry<String, ImportNode> entry : module.getStaticStarImports()
|
||||
.entrySet()) {
|
||||
visitAnnotatedNode(entry.getValue());
|
||||
}
|
||||
|
||||
for (ClassNode classNode : module.getClasses()) {
|
||||
visitAnnotatedNode(classNode);
|
||||
classNode.visitContents(classVisitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void visitAnnotatedNode(AnnotatedNode annotatedNode) {
|
||||
if (annotatedNode != null) {
|
||||
for (AnnotationNode annotationNode : annotatedNode.getAnnotations()) {
|
||||
if (GRAB_ANNOTATION_NAMES.contains(annotationNode.getClassNode()
|
||||
.getName())) {
|
||||
transformGrabAnnotation(annotationNode);
|
||||
}
|
||||
}
|
||||
protected void processAnnotationNodes(List<AnnotationNode> annotationNodes) {
|
||||
for (AnnotationNode annotationNode : annotationNodes) {
|
||||
transformGrabAnnotation(annotationNode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,10 +87,12 @@ public class ResolveDependencyCoordinatesTransformation implements ASTTransforma
|
||||
module = (String) ((ConstantExpression) expression).getValue();
|
||||
}
|
||||
if (annotation.getMember("group") == null) {
|
||||
setMember(annotation, "group", this.coordinatesResolver.getGroupId(module));
|
||||
setMember(annotation, "group", this.resolutionContext
|
||||
.getArtifactCoordinatesResolver().getGroupId(module));
|
||||
}
|
||||
if (annotation.getMember("version") == null) {
|
||||
setMember(annotation, "version", this.coordinatesResolver.getVersion(module));
|
||||
setMember(annotation, "version", this.resolutionContext
|
||||
.getArtifactCoordinatesResolver().getVersion(module));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,24 +100,4 @@ public class ResolveDependencyCoordinatesTransformation implements ASTTransforma
|
||||
ConstantExpression expression = new ConstantExpression(value);
|
||||
annotation.setMember(name, expression);
|
||||
}
|
||||
|
||||
private class ClassVisitor extends ClassCodeVisitorSupport {
|
||||
|
||||
private final SourceUnit source;
|
||||
|
||||
public ClassVisitor(SourceUnit source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SourceUnit getSourceUnit() {
|
||||
return this.source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitAnnotations(AnnotatedNode node) {
|
||||
visitAnnotatedNode(node);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
|
||||
|
||||
/**
|
||||
* {@link CompilerAutoConfiguration} for Spring.
|
||||
*
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@@ -39,7 +39,7 @@ public class SpringBootCompilerAutoConfiguration extends CompilerAutoConfigurati
|
||||
@Override
|
||||
public void applyDependencies(DependencyCustomizer dependencies) {
|
||||
dependencies.ifAnyMissingClasses("org.springframework.boot.SpringApplication")
|
||||
.add("spring-boot-starter");
|
||||
.add("spring-boot-starter");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -63,7 +63,8 @@ public class SpringBootCompilerAutoConfiguration extends CompilerAutoConfigurati
|
||||
"org.springframework.core.annotation.Order",
|
||||
"org.springframework.core.io.ResourceLoader",
|
||||
"org.springframework.boot.CommandLineRunner",
|
||||
"org.springframework.boot.autoconfigure.EnableAutoConfiguration");
|
||||
"org.springframework.boot.autoconfigure.EnableAutoConfiguration",
|
||||
"org.springframework.boot.groovy.GrabMetadata");
|
||||
imports.addStarImports("org.springframework.stereotype",
|
||||
"org.springframework.scheduling.annotation");
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import org.springframework.boot.dependency.tools.VersionManagedDependencies;
|
||||
|
||||
/**
|
||||
* {@link ArtifactCoordinatesResolver} backed by {@link ManagedDependencies}.
|
||||
*
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class ManagedDependenciesArtifactCoordinatesResolver implements
|
||||
@@ -34,7 +34,7 @@ public class ManagedDependenciesArtifactCoordinatesResolver implements
|
||||
this(new VersionManagedDependencies());
|
||||
}
|
||||
|
||||
ManagedDependenciesArtifactCoordinatesResolver(ManagedDependencies dependencies) {
|
||||
public ManagedDependenciesArtifactCoordinatesResolver(ManagedDependencies dependencies) {
|
||||
this.dependencies = dependencies;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,12 +43,13 @@ import org.eclipse.aether.resolution.DependencyRequest;
|
||||
import org.eclipse.aether.resolution.DependencyResult;
|
||||
import org.eclipse.aether.util.artifact.JavaScopes;
|
||||
import org.eclipse.aether.util.filter.DependencyFilterUtils;
|
||||
import org.springframework.boot.cli.compiler.DependencyResolutionContext;
|
||||
|
||||
/**
|
||||
* A {@link GrapeEngine} implementation that uses <a
|
||||
* href="http://eclipse.org/aether">Aether</a>, the dependency resolution system used by
|
||||
* Maven.
|
||||
*
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@@ -58,7 +59,7 @@ 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 DependencyResolutionContext resolutionContext;
|
||||
|
||||
private final ProgressReporter progressReporter;
|
||||
|
||||
@@ -74,11 +75,11 @@ public class AetherGrapeEngine implements GrapeEngine {
|
||||
RepositorySystem repositorySystem,
|
||||
DefaultRepositorySystemSession repositorySystemSession,
|
||||
List<RemoteRepository> remoteRepositories,
|
||||
List<Dependency> managedDependencies) {
|
||||
DependencyResolutionContext resolutionContext) {
|
||||
this.classLoader = classLoader;
|
||||
this.repositorySystem = repositorySystem;
|
||||
this.session = repositorySystemSession;
|
||||
this.managedDependencies.addAll(managedDependencies);
|
||||
this.resolutionContext = resolutionContext;
|
||||
|
||||
this.repositories = new ArrayList<RemoteRepository>();
|
||||
List<RemoteRepository> remotes = new ArrayList<RemoteRepository>(
|
||||
@@ -128,11 +129,13 @@ public class AetherGrapeEngine implements GrapeEngine {
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<Exclusion> createExclusions(Map<?, ?> args) {
|
||||
List<Exclusion> exclusions = new ArrayList<Exclusion>();
|
||||
List<Map<String, Object>> exclusionMaps = (List<Map<String, Object>>) args
|
||||
.get("excludes");
|
||||
if (exclusionMaps != null) {
|
||||
for (Map<String, Object> exclusionMap : exclusionMaps) {
|
||||
exclusions.add(createExclusion(exclusionMap));
|
||||
if (args != null) {
|
||||
List<Map<String, Object>> exclusionMaps = (List<Map<String, Object>>) args
|
||||
.get("excludes");
|
||||
if (exclusionMaps != null) {
|
||||
for (Map<String, Object> exclusionMap : exclusionMaps) {
|
||||
exclusions.add(createExclusion(exclusionMap));
|
||||
}
|
||||
}
|
||||
}
|
||||
return exclusions;
|
||||
@@ -168,7 +171,13 @@ public class AetherGrapeEngine implements GrapeEngine {
|
||||
String group = (String) dependencyMap.get("group");
|
||||
String module = (String) dependencyMap.get("module");
|
||||
String version = (String) dependencyMap.get("version");
|
||||
return new DefaultArtifact(group, module, "jar", version);
|
||||
String type = (String) dependencyMap.get("type");
|
||||
|
||||
if (type == null) {
|
||||
type = "jar";
|
||||
}
|
||||
|
||||
return new DefaultArtifact(group, module, type, version);
|
||||
}
|
||||
|
||||
private boolean isTransitive(Map<?, ?> dependencyMap) {
|
||||
@@ -182,7 +191,8 @@ public class AetherGrapeEngine implements GrapeEngine {
|
||||
try {
|
||||
CollectRequest collectRequest = new CollectRequest((Dependency) null,
|
||||
dependencies, new ArrayList<RemoteRepository>(this.repositories));
|
||||
collectRequest.setManagedDependencies(this.managedDependencies);
|
||||
collectRequest.setManagedDependencies(this.resolutionContext
|
||||
.getManagedDependencies());
|
||||
|
||||
DependencyRequest dependencyRequest = new DependencyRequest(collectRequest,
|
||||
DependencyFilterUtils.classpathFilter(JavaScopes.COMPILE));
|
||||
@@ -190,7 +200,8 @@ public class AetherGrapeEngine implements GrapeEngine {
|
||||
DependencyResult dependencyResult = this.repositorySystem
|
||||
.resolveDependencies(this.session, dependencyRequest);
|
||||
|
||||
this.managedDependencies.addAll(getDependencies(dependencyResult));
|
||||
this.resolutionContext.getManagedDependencies().addAll(
|
||||
getDependencies(dependencyResult));
|
||||
|
||||
return getFiles(dependencyResult);
|
||||
}
|
||||
@@ -252,13 +263,26 @@ public class AetherGrapeEngine implements GrapeEngine {
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI[] resolve(Map args, Map... dependencies) {
|
||||
throw new UnsupportedOperationException("Resolving to URIs is not supported");
|
||||
public URI[] resolve(Map args, Map... dependencyMaps) {
|
||||
return this.resolve(args, null, dependencyMaps);
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI[] resolve(Map args, List depsInfo, Map... dependencies) {
|
||||
throw new UnsupportedOperationException("Resolving to URIs is not supported");
|
||||
public URI[] resolve(Map args, List depsInfo, Map... dependencyMaps) {
|
||||
List<Exclusion> exclusions = createExclusions(args);
|
||||
List<Dependency> dependencies = createDependencies(dependencyMaps, exclusions);
|
||||
|
||||
try {
|
||||
List<File> files = resolve(dependencies);
|
||||
List<URI> uris = new ArrayList<URI>(files.size());
|
||||
for (File file : files) {
|
||||
uris.add(file.toURI());
|
||||
}
|
||||
return uris.toArray(new URI[uris.size()]);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new DependencyResolutionFailedException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -26,7 +26,6 @@ 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;
|
||||
@@ -36,16 +35,18 @@ import org.eclipse.aether.spi.connector.transport.TransporterFactory;
|
||||
import org.eclipse.aether.spi.locator.ServiceLocator;
|
||||
import org.eclipse.aether.transport.file.FileTransporterFactory;
|
||||
import org.eclipse.aether.transport.http.HttpTransporterFactory;
|
||||
import org.springframework.boot.cli.compiler.DependencyResolutionContext;
|
||||
|
||||
/**
|
||||
* Utility class to create a pre-configured {@link AetherGrapeEngine}.
|
||||
*
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public abstract class AetherGrapeEngineFactory {
|
||||
|
||||
public static AetherGrapeEngine create(GroovyClassLoader classLoader,
|
||||
List<RepositoryConfiguration> repositoryConfigurations) {
|
||||
List<RepositoryConfiguration> repositoryConfigurations,
|
||||
DependencyResolutionContext dependencyResolutionContext) {
|
||||
|
||||
RepositorySystem repositorySystem = createServiceLocator().getService(
|
||||
RepositorySystem.class);
|
||||
@@ -63,12 +64,9 @@ public abstract class AetherGrapeEngineFactory {
|
||||
new DefaultRepositorySystemSessionAutoConfiguration().apply(
|
||||
repositorySystemSession, repositorySystem);
|
||||
|
||||
List<Dependency> managedDependencies = new ManagedDependenciesFactory()
|
||||
.getManagedDependencies();
|
||||
|
||||
return new AetherGrapeEngine(classLoader, repositorySystem,
|
||||
repositorySystemSession, createRepositories(repositoryConfigurations),
|
||||
managedDependencies);
|
||||
dependencyResolutionContext);
|
||||
}
|
||||
|
||||
private static ServiceLocator createServiceLocator() {
|
||||
@@ -88,7 +86,7 @@ public abstract class AetherGrapeEngineFactory {
|
||||
for (RepositoryConfiguration repositoryConfiguration : repositoryConfigurations) {
|
||||
RemoteRepository.Builder builder = new RemoteRepository.Builder(
|
||||
repositoryConfiguration.getName(), "default", repositoryConfiguration
|
||||
.getUri().toASCIIString());
|
||||
.getUri().toASCIIString());
|
||||
|
||||
if (!repositoryConfiguration.getSnapshotsEnabled()) {
|
||||
builder.setSnapshotPolicy(new RepositoryPolicy(false,
|
||||
|
||||
@@ -30,7 +30,7 @@ import org.springframework.boot.dependency.tools.VersionManagedDependencies;
|
||||
/**
|
||||
* Factory to create Maven {@link Dependency} objects from Boot
|
||||
* {@link PomManagedDependencies}.
|
||||
*
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class ManagedDependenciesFactory {
|
||||
@@ -41,7 +41,7 @@ public class ManagedDependenciesFactory {
|
||||
this(new VersionManagedDependencies());
|
||||
}
|
||||
|
||||
ManagedDependenciesFactory(ManagedDependencies dependencies) {
|
||||
public ManagedDependenciesFactory(ManagedDependencies dependencies) {
|
||||
this.dependencies = dependencies;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.groovy;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Used to provide an alternative source of dependency metadata that is used to deduce
|
||||
* groups and versions when processing {@code @Grab} dependencies.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Target({ ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.LOCAL_VARIABLE,
|
||||
ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE })
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface GrabMetadata {
|
||||
|
||||
/**
|
||||
* One or more sets of colon-separated coordinates ({@code group:module:version}) of a
|
||||
* properties file that contains dependency metadata that will add to and override the
|
||||
* default metadata.
|
||||
*/
|
||||
String[] value();
|
||||
}
|
||||
Reference in New Issue
Block a user