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:
parent
3f498a4803
commit
d2fc80b818
|
@ -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();
|
||||
}
|
|
@ -17,6 +17,8 @@
|
|||
package org.springframework.boot.cli;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
@ -26,10 +28,11 @@ import org.springframework.boot.cli.command.grab.GrabCommand;
|
|||
import org.springframework.util.FileSystemUtils;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link GrabCommand}
|
||||
*
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Dave Syer
|
||||
*/
|
||||
|
@ -58,4 +61,32 @@ public class GrabCommandIntegrationTests {
|
|||
// Should be resolved from local repository cache
|
||||
assertTrue(output.contains("Downloading: file:"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void duplicateGrabMetadataAnnotationsProducesAnError() throws Exception {
|
||||
try {
|
||||
this.cli.grab("duplicateGrabMetadata.groovy");
|
||||
fail();
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertTrue(e.getMessage().contains("Duplicate @GrabMetadata annotation"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customMetadata() throws Exception {
|
||||
System.setProperty("grape.root", "target");
|
||||
|
||||
File testArtifactDir = new File("target/repository/test/test/1.0.0");
|
||||
testArtifactDir.mkdirs();
|
||||
|
||||
File testArtifact = new File(testArtifactDir, "test-1.0.0.properties");
|
||||
testArtifact.createNewFile();
|
||||
PrintWriter writer = new PrintWriter(new FileWriter(testArtifact));
|
||||
writer.println("javax.ejb\\:ejb-api=3.0");
|
||||
writer.close();
|
||||
|
||||
this.cli.grab("customGrabMetadata.groovy", "--autoconfigure=false");
|
||||
assertTrue(new File("target/repository/javax/ejb/ejb-api/3.0").isDirectory());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ import static org.mockito.Mockito.when;
|
|||
|
||||
/**
|
||||
* Tests for {@link ResolveDependencyCoordinatesTransformation}
|
||||
*
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public final class ResolveDependencyCoordinatesTransformationTests {
|
||||
|
@ -64,9 +64,12 @@ public final class ResolveDependencyCoordinatesTransformationTests {
|
|||
|
||||
private final ArtifactCoordinatesResolver coordinatesResolver = mock(ArtifactCoordinatesResolver.class);
|
||||
|
||||
private final ASTTransformation transformation = new ResolveDependencyCoordinatesTransformation(
|
||||
private final DependencyResolutionContext resolutionContext = new DependencyResolutionContext(
|
||||
this.coordinatesResolver);
|
||||
|
||||
private final ASTTransformation transformation = new ResolveDependencyCoordinatesTransformation(
|
||||
this.resolutionContext);
|
||||
|
||||
@Before
|
||||
public void setupExpectations() {
|
||||
when(this.coordinatesResolver.getGroupId("spring-core")).thenReturn(
|
||||
|
|
|
@ -24,12 +24,13 @@ import java.util.HashMap;
|
|||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.cli.compiler.DependencyResolutionContext;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Tests for {@link AetherGrapeEngine}.
|
||||
*
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class AetherGrapeEngineTests {
|
||||
|
@ -38,7 +39,8 @@ public class AetherGrapeEngineTests {
|
|||
|
||||
private final AetherGrapeEngine grapeEngine = AetherGrapeEngineFactory.create(
|
||||
this.groovyClassLoader, Arrays.asList(new RepositoryConfiguration("central",
|
||||
URI.create("http://repo1.maven.org/maven2"), false)));
|
||||
URI.create("http://repo1.maven.org/maven2"), false)),
|
||||
new DependencyResolutionContext());
|
||||
|
||||
@Test
|
||||
public void dependencyResolution() {
|
||||
|
@ -47,7 +49,7 @@ public class AetherGrapeEngineTests {
|
|||
this.grapeEngine.grab(args,
|
||||
createDependency("org.springframework", "spring-jdbc", "3.2.4.RELEASE"));
|
||||
|
||||
assertEquals(5, this.groovyClassLoader.getURLs().length);
|
||||
assertEquals(6, this.groovyClassLoader.getURLs().length);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
@ -61,7 +63,7 @@ public class AetherGrapeEngineTests {
|
|||
createDependency("org.springframework", "spring-jdbc", "3.2.4.RELEASE"),
|
||||
createDependency("org.springframework", "spring-beans", "3.2.4.RELEASE"));
|
||||
|
||||
assertEquals(3, this.groovyClassLoader.getURLs().length);
|
||||
assertEquals(4, this.groovyClassLoader.getURLs().length);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -86,7 +88,7 @@ public class AetherGrapeEngineTests {
|
|||
createDependency("org.springframework", "spring-jdbc", "3.2.4.RELEASE"));
|
||||
|
||||
assertEquals(0, this.groovyClassLoader.getURLs().length);
|
||||
assertEquals(5, customClassLoader.getURLs().length);
|
||||
assertEquals(6, customClassLoader.getURLs().length);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
@org.springframework.boot.groovy.GrabMetadata('test:test:1.0.0')
|
||||
@Grab('ejb-api')
|
||||
class CustomGrabMetadata {
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
@GrabMetadata("foo:bar:1.0")
|
||||
@GrabMetadata("alpha:bravo:2.0")
|
||||
class DuplicateGrabMetadata {
|
||||
|
||||
}
|
|
@ -153,6 +153,31 @@ in the Spring Boot CLI source code to understand exactly how customizations are
|
|||
|
||||
|
||||
|
||||
[[cli-default-grab-deduced-coordinates]]
|
||||
==== Deduced ``grab'' coordinates
|
||||
Spring Boot extends Groovy's standard `@Grab` support by allowing you to specify a dependency
|
||||
without a group or version, for example `@Grab('freemarker')`. This will consult Spring Boot's
|
||||
default dependency metadata to deduce the artifact's group and version. Note that the default
|
||||
metadata is tied to the version of the CLI that you're using – it will only change when you move
|
||||
to a new version of the CLI, putting you in control of when the versions of your dependencies
|
||||
may change.
|
||||
|
||||
|
||||
|
||||
[[cli-default-grab-deduced-coordinates-custom-metadata]]
|
||||
===== Custom ``grab'' metadata
|
||||
Spring Boot provides a new annotation, `@GrabMetadata` that can be used to provide custom
|
||||
dependency metadata that overrides Spring Boot's defaults. This metadata is specified by
|
||||
using the new annotation to provide the coordinates of one or more properties files. For example `
|
||||
@GrabMetadata(['com.example:versions-one:1.0.0', 'com.example.versions-two:1.0.0'])`. The
|
||||
properties files are applied in the order that their specified. In the example above, this means
|
||||
that properties in `versions-two` will override properties in `versions-one`. Each entry in
|
||||
each properties file must be in the form `group:module=version`. You can use `@GrabVersions`
|
||||
anywhere that you can use `@Grab`, however, to ensure consistent ordering of the metadata, you
|
||||
can only use `@GrabVersions` at most once in your application.
|
||||
|
||||
|
||||
|
||||
[[cli-default-import-statements]]
|
||||
==== Default import statements
|
||||
To help reduce the size of your Groovy code, several `import` statements are
|
||||
|
|
Loading…
Reference in New Issue