Isolate class loading for launched CLI apps
Rework classloading for launched applications so that CLI classes and dependencies are not visible. This change allows many of the previous hacks and workarounds to be removed. With the exception of the 'org.springframework.boot.groovy' package and 'groovy-all' all user required depndencies are now pulled in via @Grab annotations. The updated classloading algorithm has enabled the following changes: - AetherGrapeEngine is now back in the cli project and the spring-boot-cli-grape project has been removed. The AetherGrapeEngine has also been simplified. - The TestCommand now launches a TestRunner (similar in design to the SpringApplicationRunner) and report test failures directly using the junit TextListener. Adding custom 'testers' source to the users project is no longer required. The previous 'double compile' for tests has also been removed. - Utility classes have been removed in favor of using versions from spring-core. - The CLI jar is now packaged using the 'boot-loader' rather than using the maven shade plugin. This commit also applied minor polish refactoring to a number of classes.
This commit is contained in:
parent
58300deee4
commit
b19f6bb238
1
pom.xml
1
pom.xml
|
@ -37,7 +37,6 @@
|
|||
<module>spring-boot-actuator</module>
|
||||
<module>spring-boot-starters</module>
|
||||
<module>spring-boot-cli</module>
|
||||
<module>spring-boot-cli-grape</module>
|
||||
<module>spring-boot-integration-tests</module>
|
||||
</modules>
|
||||
</profile>
|
||||
|
|
|
@ -1,176 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-parent</artifactId>
|
||||
<version>0.5.0.BUILD-SNAPSHOT</version>
|
||||
<relativePath>../spring-boot-parent</relativePath>
|
||||
</parent>
|
||||
<artifactId>spring-boot-cli-grape</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<properties>
|
||||
<main.basedir>${basedir}/..</main.basedir>
|
||||
<start-class>org.springframework.boot.cli.SpringCli</start-class>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<!-- Provided -->
|
||||
<dependency>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!-- Compile -->
|
||||
<dependency>
|
||||
<groupId>commons-logging</groupId>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
<version>1.1.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven</groupId>
|
||||
<artifactId>maven-aether-provider</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>org.eclipse.sisu.plexus</artifactId>
|
||||
<groupId>org.eclipse.sisu</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.aether</groupId>
|
||||
<artifactId>aether-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.aether</groupId>
|
||||
<artifactId>aether-connector-basic</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.aether</groupId>
|
||||
<artifactId>aether-impl</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.aether</groupId>
|
||||
<artifactId>aether-spi</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.aether</groupId>
|
||||
<artifactId>aether-transport-file</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.aether</groupId>
|
||||
<artifactId>aether-transport-http</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>jcl-over-slf4j</artifactId>
|
||||
<groupId>org.slf4j</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.aether</groupId>
|
||||
<artifactId>aether-util</artifactId>
|
||||
</dependency>
|
||||
<!-- Test -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<classpathDependencyExcludes>
|
||||
<classpathDependencyExcludes>org.springframework:spring-core</classpathDependencyExcludes>
|
||||
<classpathDependencyExcludes>org.springframework:spring-beans</classpathDependencyExcludes>
|
||||
<classpathDependencyExcludes>org.springframework:spring-aop</classpathDependencyExcludes>
|
||||
<classpathDependencyExcludes>org.springframework:spring-tx</classpathDependencyExcludes>
|
||||
<classpathDependencyExcludes>org.springframework:spring-expression</classpathDependencyExcludes>
|
||||
<classpathDependencyExcludes>org.springframework:spring-context</classpathDependencyExcludes>
|
||||
<classpathDependencyExcludes>org.springframework:spring-test</classpathDependencyExcludes>
|
||||
<classpathDependencyExcludes>org.springframework.retry:spring-retry</classpathDependencyExcludes>
|
||||
<classpathDependencyExcludes>org.springframework.integration:spring-integration-core</classpathDependencyExcludes>
|
||||
<classpathDependencyExcludes>org.springframework.integration:spring-integration-dsl-groovy-core</classpathDependencyExcludes>
|
||||
</classpathDependencyExcludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<configuration>
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/*.SF</exclude>
|
||||
<exclude>META-INF/*.DSA</exclude>
|
||||
<exclude>META-INF/*.RSA</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<transformers>
|
||||
<transformer
|
||||
implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
|
||||
<transformer
|
||||
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>${start-class}</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<!--This plugin's configuration is used to store Eclipse m2e settings
|
||||
only. It has no influence on the Maven build itself. -->
|
||||
<plugin>
|
||||
<groupId>org.eclipse.m2e</groupId>
|
||||
<artifactId>lifecycle-mapping</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<configuration>
|
||||
<lifecycleMappingMetadata>
|
||||
<pluginExecutions>
|
||||
<pluginExecution>
|
||||
<pluginExecutionFilter>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-antrun-plugin</artifactId>
|
||||
<versionRange>[1.7,)</versionRange>
|
||||
<goals>
|
||||
<goal>run</goal>
|
||||
</goals>
|
||||
</pluginExecutionFilter>
|
||||
<action>
|
||||
<execute />
|
||||
</action>
|
||||
</pluginExecution>
|
||||
</pluginExecutions>
|
||||
</lifecycleMappingMetadata>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>objectstyle</id>
|
||||
<name>ObjectStyle.org Repository</name>
|
||||
<url>http://objectstyle.org/maven2/</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
</project>
|
|
@ -1,355 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.cli.compiler;
|
||||
|
||||
import groovy.grape.GrapeEngine;
|
||||
import groovy.lang.GroovyClassLoader;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
|
||||
import org.eclipse.aether.AbstractRepositoryListener;
|
||||
import org.eclipse.aether.DefaultRepositorySystemSession;
|
||||
import org.eclipse.aether.RepositoryEvent;
|
||||
import org.eclipse.aether.RepositorySystem;
|
||||
import org.eclipse.aether.RepositorySystemSession;
|
||||
import org.eclipse.aether.artifact.Artifact;
|
||||
import org.eclipse.aether.artifact.DefaultArtifact;
|
||||
import org.eclipse.aether.collection.CollectRequest;
|
||||
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
|
||||
import org.eclipse.aether.graph.Dependency;
|
||||
import org.eclipse.aether.graph.Exclusion;
|
||||
import org.eclipse.aether.impl.ArtifactDescriptorReader;
|
||||
import org.eclipse.aether.impl.DefaultServiceLocator;
|
||||
import org.eclipse.aether.internal.impl.DefaultRepositorySystem;
|
||||
import org.eclipse.aether.repository.LocalRepository;
|
||||
import org.eclipse.aether.repository.RemoteRepository;
|
||||
import org.eclipse.aether.resolution.ArtifactDescriptorException;
|
||||
import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
|
||||
import org.eclipse.aether.resolution.ArtifactDescriptorResult;
|
||||
import org.eclipse.aether.resolution.ArtifactResolutionException;
|
||||
import org.eclipse.aether.resolution.ArtifactResult;
|
||||
import org.eclipse.aether.resolution.DependencyRequest;
|
||||
import org.eclipse.aether.resolution.DependencyResult;
|
||||
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
|
||||
import org.eclipse.aether.spi.connector.transport.TransporterFactory;
|
||||
import org.eclipse.aether.transfer.AbstractTransferListener;
|
||||
import org.eclipse.aether.transfer.TransferCancelledException;
|
||||
import org.eclipse.aether.transfer.TransferEvent;
|
||||
import org.eclipse.aether.transport.file.FileTransporterFactory;
|
||||
import org.eclipse.aether.transport.http.HttpTransporterFactory;
|
||||
import org.eclipse.aether.util.artifact.JavaScopes;
|
||||
import org.eclipse.aether.util.filter.DependencyFilterUtils;
|
||||
|
||||
/**
|
||||
* A {@link GrapeEngine} implementation that uses <a
|
||||
* href="http://eclipse.org/aether">Aether</a>, the dependency resolution system used by
|
||||
* Maven.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class AetherGrapeEngine implements GrapeEngine {
|
||||
|
||||
private static final String DEPENDENCY_MODULE = "module";
|
||||
|
||||
private static final String DEPENDENCY_GROUP = "group";
|
||||
|
||||
private static final String DEPENDENCY_VERSION = "version";
|
||||
|
||||
private static final Collection<Exclusion> WILDCARD_EXCLUSION = Arrays
|
||||
.asList(new Exclusion("*", "*", "*", "*"));
|
||||
|
||||
private final Artifact parentArtifact;
|
||||
|
||||
private final ProgressReporter progressReporter = new ProgressReporter();
|
||||
|
||||
private final ArtifactDescriptorReader artifactDescriptorReader;
|
||||
|
||||
private final GroovyClassLoader defaultClassLoader;
|
||||
|
||||
private final RepositorySystemSession repositorySystemSession;
|
||||
|
||||
private final RepositorySystem repositorySystem;
|
||||
|
||||
private final List<RemoteRepository> repositories;
|
||||
|
||||
public AetherGrapeEngine(GroovyClassLoader classLoader, String parentGroupId,
|
||||
String parentArtifactId, String parentVersion) {
|
||||
this.defaultClassLoader = classLoader;
|
||||
this.parentArtifact = new DefaultArtifact(parentGroupId, parentArtifactId, "pom",
|
||||
parentVersion);
|
||||
|
||||
DefaultServiceLocator mavenServiceLocator = MavenRepositorySystemUtils
|
||||
.newServiceLocator();
|
||||
mavenServiceLocator.addService(RepositorySystem.class,
|
||||
DefaultRepositorySystem.class);
|
||||
|
||||
mavenServiceLocator.addService(RepositoryConnectorFactory.class,
|
||||
BasicRepositoryConnectorFactory.class);
|
||||
|
||||
mavenServiceLocator.addService(TransporterFactory.class,
|
||||
HttpTransporterFactory.class);
|
||||
mavenServiceLocator.addService(TransporterFactory.class,
|
||||
FileTransporterFactory.class);
|
||||
|
||||
this.repositorySystem = mavenServiceLocator.getService(RepositorySystem.class);
|
||||
|
||||
DefaultRepositorySystemSession repositorySystemSession = MavenRepositorySystemUtils
|
||||
.newSession();
|
||||
repositorySystemSession.setTransferListener(new AbstractTransferListener() {
|
||||
|
||||
@Override
|
||||
public void transferStarted(TransferEvent event)
|
||||
throws TransferCancelledException {
|
||||
AetherGrapeEngine.this.progressReporter.reportProgress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transferProgressed(TransferEvent event)
|
||||
throws TransferCancelledException {
|
||||
AetherGrapeEngine.this.progressReporter.reportProgress();
|
||||
}
|
||||
});
|
||||
|
||||
repositorySystemSession.setRepositoryListener(new AbstractRepositoryListener() {
|
||||
@Override
|
||||
public void artifactResolved(RepositoryEvent event) {
|
||||
AetherGrapeEngine.this.progressReporter.reportProgress();
|
||||
}
|
||||
});
|
||||
|
||||
String grapeRootProperty = System.getProperty("grape.root");
|
||||
File root;
|
||||
if (grapeRootProperty != null && grapeRootProperty.trim().length() > 0) {
|
||||
root = new File(grapeRootProperty);
|
||||
|
||||
}
|
||||
else {
|
||||
root = new File(System.getProperty("user.home"), ".m2");
|
||||
}
|
||||
|
||||
LocalRepository localRepo = new LocalRepository(new File(root, "repository"));
|
||||
|
||||
repositorySystemSession.setLocalRepositoryManager(this.repositorySystem
|
||||
.newLocalRepositoryManager(repositorySystemSession, localRepo));
|
||||
|
||||
this.repositorySystemSession = repositorySystemSession;
|
||||
|
||||
List<RemoteRepository> repositories = new ArrayList<RemoteRepository>();
|
||||
repositories.add(new RemoteRepository.Builder("central", "default",
|
||||
"http://repo1.maven.org/maven2/").build());
|
||||
|
||||
if (!Boolean.getBoolean("disableSpringSnapshotRepos")) {
|
||||
repositories.add(new RemoteRepository.Builder("spring-snapshot", "default",
|
||||
"http://repo.spring.io/snapshot").build());
|
||||
repositories.add(new RemoteRepository.Builder("spring-milestone", "default",
|
||||
"http://repo.spring.io/milestone").build());
|
||||
}
|
||||
|
||||
this.repositories = repositories;
|
||||
|
||||
this.artifactDescriptorReader = mavenServiceLocator
|
||||
.getService(ArtifactDescriptorReader.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object grab(Map args) {
|
||||
return grab(args, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object grab(Map args, Map... dependencyMaps) {
|
||||
List<Dependency> dependencies = createDependencies(dependencyMaps);
|
||||
|
||||
try {
|
||||
List<File> files = resolve(dependencies);
|
||||
GroovyClassLoader classLoader = (GroovyClassLoader) args.get("classLoader");
|
||||
if (classLoader == null) {
|
||||
classLoader = this.defaultClassLoader;
|
||||
}
|
||||
for (File file : files) {
|
||||
classLoader.addURL(file.toURI().toURL());
|
||||
}
|
||||
}
|
||||
catch (ArtifactResolutionException ex) {
|
||||
throw new DependencyResolutionFailedException(ex);
|
||||
}
|
||||
catch (MalformedURLException ex) {
|
||||
throw new DependencyResolutionFailedException(ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<Dependency> createDependencies(Map<?, ?>... dependencyMaps) {
|
||||
List<Dependency> dependencies = new ArrayList<Dependency>(dependencyMaps.length);
|
||||
for (Map<?, ?> dependencyMap : dependencyMaps) {
|
||||
dependencies.add(createDependency(dependencyMap));
|
||||
}
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
private boolean isTransitive(Map<?, ?> dependencyMap) {
|
||||
Boolean transitive = (Boolean) dependencyMap.get("transitive");
|
||||
if (transitive == null) {
|
||||
transitive = true;
|
||||
}
|
||||
return transitive;
|
||||
}
|
||||
|
||||
private Dependency createDependency(Map<?, ?> dependencyMap) {
|
||||
Artifact artifact = createArtifact(dependencyMap);
|
||||
|
||||
Dependency dependency;
|
||||
|
||||
if (!isTransitive(dependencyMap)) {
|
||||
dependency = new Dependency(artifact, JavaScopes.COMPILE, null,
|
||||
WILDCARD_EXCLUSION);
|
||||
}
|
||||
else {
|
||||
dependency = new Dependency(artifact, JavaScopes.COMPILE);
|
||||
}
|
||||
|
||||
return dependency;
|
||||
}
|
||||
|
||||
private Artifact createArtifact(Map<?, ?> dependencyMap) {
|
||||
String group = (String) dependencyMap.get(DEPENDENCY_GROUP);
|
||||
String module = (String) dependencyMap.get(DEPENDENCY_MODULE);
|
||||
String version = (String) dependencyMap.get(DEPENDENCY_VERSION);
|
||||
|
||||
return new DefaultArtifact(group, module, "jar", version);
|
||||
}
|
||||
|
||||
private List<File> resolve(List<Dependency> dependencies)
|
||||
throws ArtifactResolutionException {
|
||||
|
||||
CollectRequest collectRequest = new CollectRequest((Dependency) null,
|
||||
dependencies, this.repositories);
|
||||
collectRequest.setManagedDependencies(getManagedDependencies());
|
||||
|
||||
try {
|
||||
DependencyResult dependencyResult = this.repositorySystem
|
||||
.resolveDependencies(
|
||||
this.repositorySystemSession,
|
||||
new DependencyRequest(collectRequest, DependencyFilterUtils
|
||||
.classpathFilter(JavaScopes.COMPILE)));
|
||||
List<File> files = new ArrayList<File>();
|
||||
for (ArtifactResult result : dependencyResult.getArtifactResults()) {
|
||||
files.add(result.getArtifact().getFile());
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new DependencyResolutionFailedException(ex);
|
||||
}
|
||||
finally {
|
||||
this.progressReporter.finished();
|
||||
}
|
||||
}
|
||||
|
||||
private List<Dependency> getManagedDependencies() {
|
||||
ArtifactDescriptorRequest parentRequest = new ArtifactDescriptorRequest();
|
||||
parentRequest.setArtifact(this.parentArtifact);
|
||||
|
||||
try {
|
||||
ArtifactDescriptorResult artifactDescriptorResult = this.artifactDescriptorReader
|
||||
.readArtifactDescriptor(this.repositorySystemSession, parentRequest);
|
||||
return artifactDescriptorResult.getManagedDependencies();
|
||||
}
|
||||
catch (ArtifactDescriptorException ex) {
|
||||
throw new DependencyResolutionFailedException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Map<String, List<String>>> enumerateGrapes() {
|
||||
throw new UnsupportedOperationException("Grape enumeration is not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI[] resolve(Map args, Map... dependencies) {
|
||||
throw new UnsupportedOperationException("Resolving to URIs is not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI[] resolve(Map args, List depsInfo, Map... dependencies) {
|
||||
throw new UnsupportedOperationException("Resolving to URIs is not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map[] listDependencies(ClassLoader classLoader) {
|
||||
throw new UnsupportedOperationException("Listing dependencies is not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addResolver(Map<String, Object> args) {
|
||||
throw new UnsupportedOperationException("Adding a resolver is not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object grab(String endorsedModule) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Grabbing an endorsed module is not supported");
|
||||
}
|
||||
|
||||
private static final class ProgressReporter {
|
||||
|
||||
private static final long INITIAL_DELAY = TimeUnit.SECONDS.toMillis(3);
|
||||
|
||||
private static final long PROGRESS_DELAY = TimeUnit.SECONDS.toMillis(1);
|
||||
|
||||
private long startTime = System.currentTimeMillis();
|
||||
|
||||
private long lastProgressTime = System.currentTimeMillis();
|
||||
|
||||
private boolean started;
|
||||
|
||||
private boolean finished;
|
||||
|
||||
void reportProgress() {
|
||||
if (!this.finished
|
||||
&& System.currentTimeMillis() - this.startTime > INITIAL_DELAY) {
|
||||
if (!this.started) {
|
||||
this.started = true;
|
||||
System.out.print("Resolving dependencies..");
|
||||
this.lastProgressTime = System.currentTimeMillis();
|
||||
}
|
||||
else if (System.currentTimeMillis() - this.lastProgressTime > PROGRESS_DELAY) {
|
||||
System.out.print(".");
|
||||
this.lastProgressTime = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void finished() {
|
||||
if (this.started && !this.finished) {
|
||||
this.finished = true;
|
||||
System.out.println("");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -43,19 +43,60 @@
|
|||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy</artifactId>
|
||||
<artifactId>groovy-all</artifactId>
|
||||
</dependency>
|
||||
<!-- Optional -->
|
||||
<dependency>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy-templates</artifactId>
|
||||
<optional>true</optional>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven</groupId>
|
||||
<artifactId>maven-aether-provider</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>org.eclipse.sisu.plexus</artifactId>
|
||||
<groupId>org.eclipse.sisu</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.aether</groupId>
|
||||
<artifactId>aether-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.aether</groupId>
|
||||
<artifactId>aether-connector-basic</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.aether</groupId>
|
||||
<artifactId>aether-impl</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.aether</groupId>
|
||||
<artifactId>aether-spi</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.aether</groupId>
|
||||
<artifactId>aether-transport-file</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.aether</groupId>
|
||||
<artifactId>aether-transport-http</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>jcl-over-slf4j</artifactId>
|
||||
<groupId>org.slf4j</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.aether</groupId>
|
||||
<artifactId>aether-util</artifactId>
|
||||
</dependency>
|
||||
<!-- Provided -->
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>spring-boot-cli-grape</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!-- Test -->
|
||||
|
@ -76,11 +117,6 @@
|
|||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.javassist</groupId>
|
||||
<artifactId>javassist</artifactId>
|
||||
|
@ -111,74 +147,80 @@
|
|||
<additionalClasspathElements>
|
||||
<additionalClasspathElement>${project.build.directory}/generated-resources</additionalClasspathElement>
|
||||
</additionalClasspathElements>
|
||||
<classpathDependencyExcludes>
|
||||
<classpathDependencyExcludes>org.springframework:spring-core</classpathDependencyExcludes>
|
||||
<classpathDependencyExcludes>org.springframework:spring-beans</classpathDependencyExcludes>
|
||||
<classpathDependencyExcludes>org.springframework:spring-aop</classpathDependencyExcludes>
|
||||
<classpathDependencyExcludes>org.springframework:spring-tx</classpathDependencyExcludes>
|
||||
<classpathDependencyExcludes>org.springframework:spring-expression</classpathDependencyExcludes>
|
||||
<classpathDependencyExcludes>org.springframework:spring-context</classpathDependencyExcludes>
|
||||
<classpathDependencyExcludes>org.springframework:spring-test</classpathDependencyExcludes>
|
||||
<classpathDependencyExcludes>org.springframework.retry:spring-retry</classpathDependencyExcludes>
|
||||
<classpathDependencyExcludes>org.springframework.integration:spring-integration-core</classpathDependencyExcludes>
|
||||
<classpathDependencyExcludes>org.springframework.integration:spring-integration-dsl-groovy-core</classpathDependencyExcludes>
|
||||
</classpathDependencyExcludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<!-- Build an executable JAR manually since we can't easily depend on
|
||||
a maven plugin that is part of the reactor -->
|
||||
<plugin>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<configuration>
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/*.SF</exclude>
|
||||
<exclude>META-INF/*.DSA</exclude>
|
||||
<exclude>META-INF/*.RSA</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<id>unpack</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
<goal>unpack</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<transformers>
|
||||
<transformer
|
||||
implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
|
||||
<transformer
|
||||
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
</transformer>
|
||||
</transformers>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
<artifactItems>
|
||||
<artifactItem>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>spring-boot-loader</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>jar</type>
|
||||
</artifactItem>
|
||||
</artifactItems>
|
||||
<outputDirectory>${project.build.directory}/assembly</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>copy</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/assembly/lib</outputDirectory>
|
||||
<includeScope>compile</includeScope>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<inherited>false</inherited>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>src/main/assembly/repackage-jar.xml</descriptor>
|
||||
<descriptor>src/main/assembly/bin-package.xml</descriptor>
|
||||
</descriptors>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>${start-class}</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>make-distribution</id>
|
||||
<id>jar-with-dependencies</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>src/main/assembly/jar-with-dependencies.xml</descriptor>
|
||||
</descriptors>
|
||||
<archive>
|
||||
<manifest>
|
||||
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
|
||||
<mainClass>org.springframework.boot.loader.JarLauncher</mainClass>
|
||||
</manifest>
|
||||
<manifestEntries>
|
||||
<Start-Class>${start-class}</Start-Class>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>bin-package</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>src/main/assembly/bin-package.xml</descriptor>
|
||||
</descriptors>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
@ -238,6 +280,27 @@
|
|||
<execute />
|
||||
</action>
|
||||
</pluginExecution>
|
||||
<pluginExecution>
|
||||
<pluginExecutionFilter>
|
||||
<groupId>
|
||||
org.apache.maven.plugins
|
||||
</groupId>
|
||||
<artifactId>
|
||||
maven-dependency-plugin
|
||||
</artifactId>
|
||||
<versionRange>
|
||||
[2.8,)
|
||||
</versionRange>
|
||||
<goals>
|
||||
<goal>
|
||||
copy-dependencies
|
||||
</goal>
|
||||
</goals>
|
||||
</pluginExecutionFilter>
|
||||
<action>
|
||||
<ignore></ignore>
|
||||
</action>
|
||||
</pluginExecution>
|
||||
</pluginExecutions>
|
||||
</lifecycleMappingMetadata>
|
||||
</configuration>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package org.test
|
||||
|
||||
import static org.springframework.boot.cli.template.GroovyTemplate.template;
|
||||
import static org.springframework.boot.groovy.GroovyTemplate.*;
|
||||
|
||||
@Component
|
||||
class Example implements CommandLineRunner {
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
</fileSets>
|
||||
<files>
|
||||
<file>
|
||||
<source>${project.build.directory}/${project.artifactId}-${project.version}-repackaged.jar</source>
|
||||
<source>${project.build.directory}/${project.artifactId}-${project.version}-full.jar</source>
|
||||
<outputDirectory>/lib</outputDirectory>
|
||||
<destName>${project.build.finalName}.jar</destName>
|
||||
</file>
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<assembly
|
||||
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
|
||||
<id>full</id>
|
||||
<formats>
|
||||
<format>jar</format>
|
||||
</formats>
|
||||
<includeBaseDirectory>false</includeBaseDirectory>
|
||||
<dependencySets>
|
||||
<dependencySet>
|
||||
<useProjectArtifact />
|
||||
<includes>
|
||||
<include>${project.groupId}:${project.artifactId}</include>
|
||||
</includes>
|
||||
<unpack>true</unpack>
|
||||
</dependencySet>
|
||||
</dependencySets>
|
||||
<fileSets>
|
||||
<fileSet>
|
||||
<directory>${project.build.directory}/assembly</directory>
|
||||
<outputDirectory>/</outputDirectory>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
</assembly>
|
|
@ -1,25 +0,0 @@
|
|||
<assembly>
|
||||
<id>repackaged</id>
|
||||
<formats>
|
||||
<format>jar</format>
|
||||
</formats>
|
||||
<includeBaseDirectory>false</includeBaseDirectory>
|
||||
<dependencySets>
|
||||
<dependencySet>
|
||||
<includes>
|
||||
<include>org.springframework.boot:spring-boot-cli:jar:*</include>
|
||||
</includes>
|
||||
<unpack>true</unpack>
|
||||
<directoryMode>755</directoryMode>
|
||||
</dependencySet>
|
||||
<dependencySet>
|
||||
<includes>
|
||||
<include>org.springframework.boot:spring-boot-cli-grape:jar:*</include>
|
||||
</includes>
|
||||
<outputDirectory>internal</outputDirectory>
|
||||
<directoryMode>755</directoryMode>
|
||||
<scope>provided</scope>
|
||||
<unpack>true</unpack>
|
||||
</dependencySet>
|
||||
</dependencySets>
|
||||
</assembly>
|
|
@ -1,71 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License")
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import org.junit.runner.JUnitCore
|
||||
import org.junit.runner.Result
|
||||
import org.springframework.boot.cli.command.tester.Failure
|
||||
import org.springframework.boot.cli.command.tester.TestResults
|
||||
|
||||
import java.lang.annotation.Annotation
|
||||
import java.lang.reflect.Method
|
||||
|
||||
|
||||
/**
|
||||
* Groovy script to run JUnit tests inside the {@link TestCommand}.
|
||||
* Needs to be compiled along with the actual code to work properly.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
class JUnitTester extends AbstractTester {
|
||||
|
||||
@Override
|
||||
protected Set<Class<?>> findTestableClasses(List<Class<?>> compiled) {
|
||||
// Look for @Test methods
|
||||
Set<Class<?>> testable = new LinkedHashSet<Class<?>>()
|
||||
for (Class<?> clazz : compiled) {
|
||||
for (Method method : clazz.getMethods()) {
|
||||
for (Annotation annotation : method.getAnnotations()) {
|
||||
if (annotation.toString().contains("Test")) {
|
||||
testable.add(clazz)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return testable
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TestResults test(List<Class<?>> testable) {
|
||||
return JUnitTester.runEmbeddedTests(testable)
|
||||
}
|
||||
|
||||
static TestResults runEmbeddedTests(List<Class<?>> testable) {
|
||||
Result results = JUnitCore.runClasses(testable.toArray(new Class<?>[0]))
|
||||
|
||||
TestResults testResults = new TestResults()
|
||||
testResults.setRunCount(results.getRunCount())
|
||||
|
||||
List<Failure> failures = new ArrayList<Failure>()
|
||||
for (org.junit.runner.notification.Failure failure : results.getFailures()) {
|
||||
failures.add(new Failure(failure.exception.toString(), failure.trace))
|
||||
}
|
||||
|
||||
testResults.setFailures(failures)
|
||||
|
||||
return testResults
|
||||
}
|
||||
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License")
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import org.springframework.boot.cli.command.tester.TestResults
|
||||
import spock.lang.Specification
|
||||
|
||||
/**
|
||||
* Groovy script to run Spock tests inside the {@link TestCommand}.
|
||||
* Needs to be compiled along with the actual code to work properly.
|
||||
*
|
||||
* NOTE: SpockTester depends on JUnitTester to actually run the tests
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
class SpockTester extends AbstractTester {
|
||||
|
||||
@Override
|
||||
protected Set<Class<?>> findTestableClasses(List<Class<?>> compiled) {
|
||||
// Look for classes that implement spock.lang.Specification
|
||||
Set<Class<?>> testable = new LinkedHashSet<Class<?>>()
|
||||
for (Class<?> clazz : compiled) {
|
||||
if (Specification.class.isAssignableFrom(clazz)) {
|
||||
testable.add(clazz)
|
||||
}
|
||||
}
|
||||
return testable
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TestResults test(List<Class<?>> testable) {
|
||||
return JUnitTester.runEmbeddedTests(testable)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License")
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import org.springframework.boot.cli.command.tester.TestResults
|
||||
|
||||
|
||||
/**
|
||||
* Groovy script define abstract basis for automated testers for {@link TestCommand}.
|
||||
* Needs to be compiled along with the actual code to work properly.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public abstract class AbstractTester {
|
||||
|
||||
public TestResults findAndTest(List<Class<?>> compiled) throws FileNotFoundException {
|
||||
Set<Class<?>> testable = findTestableClasses(compiled)
|
||||
|
||||
if (testable.size() == 0) {
|
||||
return TestResults.NONE
|
||||
}
|
||||
|
||||
return test(new ArrayList<Class<?>>(testable))
|
||||
}
|
||||
|
||||
protected abstract Set<Class<?>> findTestableClasses(List<Class<?>> compiled)
|
||||
|
||||
protected abstract TestResults test(List<Class<?>> testable)
|
||||
|
||||
}
|
|
@ -18,13 +18,13 @@ package org.springframework.boot.cli.command;
|
|||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import joptsimple.OptionSet;
|
||||
import joptsimple.OptionSpec;
|
||||
|
||||
import org.springframework.boot.cli.Command;
|
||||
import org.springframework.boot.cli.Log;
|
||||
import org.springframework.boot.cli.util.FileUtils;
|
||||
|
||||
/**
|
||||
* {@link Command} to 'clean' up grapes, removing cached dependencies and forcing a
|
||||
|
@ -107,7 +107,7 @@ public class CleanCommand extends OptionParsingCommand {
|
|||
return;
|
||||
}
|
||||
|
||||
for (Object obj : FileUtils.recursiveList(file)) {
|
||||
for (Object obj : recursiveList(file)) {
|
||||
File candidate = (File) obj;
|
||||
if (candidate.getName().contains("SNAPSHOT")) {
|
||||
delete(candidate);
|
||||
|
@ -117,7 +117,7 @@ public class CleanCommand extends OptionParsingCommand {
|
|||
|
||||
private void delete(File file) {
|
||||
Log.info("Deleting: " + file);
|
||||
FileUtils.recursiveDelete(file);
|
||||
recursiveDelete(file);
|
||||
}
|
||||
|
||||
private File getModulePath(File root, String group, String module, Layout layout) {
|
||||
|
@ -167,6 +167,30 @@ public class CleanCommand extends OptionParsingCommand {
|
|||
IVY, MAVEN;
|
||||
}
|
||||
|
||||
private void recursiveDelete(File file) {
|
||||
if (file.exists()) {
|
||||
if (file.isDirectory()) {
|
||||
for (File inDir : file.listFiles()) {
|
||||
recursiveDelete(inDir);
|
||||
}
|
||||
}
|
||||
if (!file.delete()) {
|
||||
throw new IllegalStateException("Failed to delete " + file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<File> recursiveList(File file) {
|
||||
List<File> files = new ArrayList<File>();
|
||||
if (file.isDirectory()) {
|
||||
for (File inDir : file.listFiles()) {
|
||||
files.addAll(recursiveList(inDir));
|
||||
}
|
||||
}
|
||||
files.add(file);
|
||||
return files;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -30,8 +30,9 @@ import org.springframework.boot.cli.CommandFactory;
|
|||
*/
|
||||
public class DefaultCommandFactory implements CommandFactory {
|
||||
|
||||
private static final List<Command> DEFAULT_COMMANDS = Arrays.<Command> asList(
|
||||
new VersionCommand(), new RunCommand(), new CleanCommand(), new TestCommand());
|
||||
private static final List<Command> DEFAULT_COMMANDS = Arrays
|
||||
.<Command> asList(new VersionCommand(), new RunCommand(), new CleanCommand(),
|
||||
new TestCommand());
|
||||
|
||||
@Override
|
||||
public Collection<Command> getCommands() {
|
||||
|
|
|
@ -28,10 +28,11 @@ import joptsimple.OptionSet;
|
|||
import joptsimple.OptionSpecBuilder;
|
||||
|
||||
/**
|
||||
* A handler that parses and handles options.
|
||||
* Delegate used by {@link OptionParsingCommand} to parse options and run the command.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @see OptionParsingCommand
|
||||
* @see #run(OptionSet)
|
||||
*/
|
||||
public class OptionHandler {
|
||||
|
||||
|
@ -70,6 +71,11 @@ public class OptionHandler {
|
|||
run(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the command using the specified parsed {@link OptionSet}.
|
||||
* @param options the parsed option set
|
||||
* @throws Exception
|
||||
*/
|
||||
protected void run(OptionSet options) throws Exception {
|
||||
}
|
||||
|
||||
|
|
|
@ -19,10 +19,11 @@ package org.springframework.boot.cli.command;
|
|||
import org.springframework.boot.cli.Command;
|
||||
|
||||
/**
|
||||
* Base class for a {@link Command} that pare options using an {@link OptionHandler}.
|
||||
* Base class for a {@link Command} that parse options using an {@link OptionHandler}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Dave Syer
|
||||
* @see OptionHandler
|
||||
*/
|
||||
public abstract class OptionParsingCommand extends AbstractCommand {
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import joptsimple.OptionSet;
|
|||
import joptsimple.OptionSpec;
|
||||
|
||||
import org.springframework.boot.cli.Command;
|
||||
import org.springframework.boot.cli.compiler.GroovyCompilerScope;
|
||||
import org.springframework.boot.cli.runner.SpringApplicationRunner;
|
||||
import org.springframework.boot.cli.runner.SpringApplicationRunnerConfiguration;
|
||||
|
||||
|
@ -120,6 +121,11 @@ public class RunCommand extends OptionParsingCommand {
|
|||
this.options = options;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroovyCompilerScope getScope() {
|
||||
return GroovyCompilerScope.DEFAULT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWatchForFileChanges() {
|
||||
return this.options.has(RunOptionHandler.this.watchOption);
|
||||
|
@ -152,11 +158,12 @@ public class RunCommand extends OptionParsingCommand {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String getClasspath() {
|
||||
public String[] getClasspath() {
|
||||
if (this.options.has(RunOptionHandler.this.classpathOption)) {
|
||||
return this.options.valueOf(RunOptionHandler.this.classpathOption);
|
||||
return this.options.valueOf(RunOptionHandler.this.classpathOption)
|
||||
.split(":");
|
||||
}
|
||||
return "";
|
||||
return NO_CLASSPATH;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import groovy.lang.MetaMethod;
|
|||
import groovy.lang.Script;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
|
@ -32,7 +33,8 @@ import org.codehaus.groovy.control.CompilationFailedException;
|
|||
import org.springframework.boot.cli.Command;
|
||||
import org.springframework.boot.cli.compiler.GroovyCompiler;
|
||||
import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
|
||||
import org.springframework.boot.cli.util.FileUtils;
|
||||
import org.springframework.boot.cli.compiler.GroovyCompilerScope;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
/**
|
||||
* {@link Command} to run a Groovy script.
|
||||
|
@ -219,7 +221,7 @@ public class ScriptCommand implements Command {
|
|||
try {
|
||||
File file = File.createTempFile(name, ".groovy");
|
||||
file.deleteOnExit();
|
||||
FileUtils.copy(url, file);
|
||||
FileCopyUtils.copy(url.openStream(), new FileOutputStream(file));
|
||||
return file;
|
||||
}
|
||||
catch (IOException ex) {
|
||||
|
@ -230,6 +232,11 @@ public class ScriptCommand implements Command {
|
|||
|
||||
private static class ScriptConfiguration implements GroovyCompilerConfiguration {
|
||||
|
||||
@Override
|
||||
public GroovyCompilerScope getScope() {
|
||||
return GroovyCompilerScope.EXTENSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGuessImports() {
|
||||
return true;
|
||||
|
@ -241,8 +248,8 @@ public class ScriptCommand implements Command {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String getClasspath() {
|
||||
return "";
|
||||
public String[] getClasspath() {
|
||||
return NO_CLASSPATH;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ import org.codehaus.groovy.control.CompilePhase;
|
|||
import org.codehaus.groovy.control.SourceUnit;
|
||||
import org.codehaus.groovy.control.customizers.CompilationCustomizer;
|
||||
import org.codehaus.groovy.control.customizers.ImportCustomizer;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.springframework.asm.Opcodes;
|
||||
import org.springframework.boot.cli.Command;
|
||||
|
||||
/**
|
||||
|
|
|
@ -16,196 +16,95 @@
|
|||
|
||||
package org.springframework.boot.cli.command;
|
||||
|
||||
import groovy.lang.GroovyObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import joptsimple.OptionSet;
|
||||
import joptsimple.OptionSpec;
|
||||
|
||||
import org.codehaus.groovy.control.CompilationFailedException;
|
||||
import org.springframework.boot.cli.Log;
|
||||
import org.springframework.boot.cli.command.tester.Failure;
|
||||
import org.springframework.boot.cli.command.tester.TestResults;
|
||||
import org.springframework.boot.cli.compiler.GroovyCompiler;
|
||||
import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
|
||||
import org.springframework.boot.cli.util.FileUtils;
|
||||
import org.springframework.boot.cli.Command;
|
||||
import org.springframework.boot.cli.compiler.GroovyCompilerScope;
|
||||
import org.springframework.boot.cli.testrunner.TestRunner;
|
||||
import org.springframework.boot.cli.testrunner.TestRunnerConfiguration;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
/**
|
||||
* Invokes testing for auto-compiled scripts
|
||||
* {@link Command} to run a groovy test script or scripts.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class TestCommand extends OptionParsingCommand {
|
||||
|
||||
public TestCommand() {
|
||||
super("test", "Test a groovy script", new TestOptionHandler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsageHelp() {
|
||||
return "[options] <files>";
|
||||
}
|
||||
|
||||
public TestResults getResults() {
|
||||
return ((TestOptionHandler) this.getHandler()).results;
|
||||
}
|
||||
|
||||
private static class TestGroovyCompilerConfiguration implements
|
||||
GroovyCompilerConfiguration {
|
||||
|
||||
@Override
|
||||
public boolean isGuessImports() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGuessDependencies() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClasspath() {
|
||||
return "";
|
||||
}
|
||||
super("test", "Run a spring groovy script test", new TestOptionHandler());
|
||||
}
|
||||
|
||||
private static class TestOptionHandler extends OptionHandler {
|
||||
|
||||
private TestResults results;
|
||||
private OptionSpec<Void> noGuessImportsOption;
|
||||
|
||||
private OptionSpec<Void> noGuessDependenciesOption;
|
||||
|
||||
private OptionSpec<String> classpathOption;
|
||||
|
||||
private TestRunner runner;
|
||||
|
||||
@Override
|
||||
protected void options() {
|
||||
this.noGuessImportsOption = option("no-guess-imports",
|
||||
"Do not attempt to guess imports");
|
||||
this.noGuessDependenciesOption = option("no-guess-dependencies",
|
||||
"Do not attempt to guess dependencies");
|
||||
this.classpathOption = option(asList("classpath", "cp"),
|
||||
"Additional classpath entries").withRequiredArg();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run(OptionSet options) throws Exception {
|
||||
TestGroovyCompilerConfiguration configuration = new TestGroovyCompilerConfiguration();
|
||||
GroovyCompiler compiler = new GroovyCompiler(configuration);
|
||||
FileOptions fileOptions = new FileOptions(options);
|
||||
TestRunnerConfiguration configuration = new TestRunnerConfigurationAdapter(
|
||||
options);
|
||||
this.runner = new TestRunner(configuration, fileOptions.getFilesArray(),
|
||||
fileOptions.getArgsArray());
|
||||
this.runner.compileAndRunTests();
|
||||
}
|
||||
|
||||
FileOptions fileOptions = new FileOptions(options, getClass()
|
||||
.getClassLoader());
|
||||
/**
|
||||
* Simple adapter class to present the {@link OptionSet} as a
|
||||
* {@link TestRunnerConfiguration}.
|
||||
*/
|
||||
private class TestRunnerConfigurationAdapter implements TestRunnerConfiguration {
|
||||
|
||||
/*
|
||||
* Need to compile the code twice: The first time automatically pulls in
|
||||
* autoconfigured libraries including test tools. Then the compiled code can
|
||||
* be scanned to see what libraries were activated. Then it can be recompiled,
|
||||
* with appropriate tester groovy scripts included in the same classloading
|
||||
* context. Then the testers can be fetched and invoked through reflection
|
||||
* against the composite AST.
|
||||
*/
|
||||
private OptionSet options;
|
||||
|
||||
// Compile - Pass 1 - compile source code to see what test libraries were
|
||||
// pulled in
|
||||
Object[] sources = compiler.sources(fileOptions.getFilesArray());
|
||||
List<File> testerFiles = compileAndCollectTesterFiles(sources);
|
||||
|
||||
// Compile - Pass 2 - add appropriate testers
|
||||
List<File> files = new ArrayList<File>(fileOptions.getFiles());
|
||||
files.addAll(testerFiles);
|
||||
sources = compiler.sources(files.toArray(new File[files.size()]));
|
||||
if (sources.length == 0) {
|
||||
throw new RuntimeException("No classes found in '" + files + "'");
|
||||
public TestRunnerConfigurationAdapter(OptionSet options) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
// Extract list of compiled classes
|
||||
List<Class<?>> compiled = new ArrayList<Class<?>>();
|
||||
List<Class<?>> testers = new ArrayList<Class<?>>();
|
||||
for (Object source : sources) {
|
||||
if (source instanceof Class) {
|
||||
Class<?> sourceClass = (Class<?>) source;
|
||||
if (sourceClass.getSuperclass().getName().equals("AbstractTester")) {
|
||||
testers.add(sourceClass);
|
||||
}
|
||||
else {
|
||||
compiled.add((Class<?>) source);
|
||||
}
|
||||
@Override
|
||||
public GroovyCompilerScope getScope() {
|
||||
return GroovyCompilerScope.DEFAULT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGuessImports() {
|
||||
return !this.options.has(TestOptionHandler.this.noGuessImportsOption);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGuessDependencies() {
|
||||
return !this.options
|
||||
.has(TestOptionHandler.this.noGuessDependenciesOption);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getClasspath() {
|
||||
if (this.options.has(TestOptionHandler.this.classpathOption)) {
|
||||
return this.options.valueOf(TestOptionHandler.this.classpathOption)
|
||||
.split(":");
|
||||
}
|
||||
return NO_CLASSPATH;
|
||||
}
|
||||
|
||||
this.results = new TestResults();
|
||||
for (Class<?> tester : testers) {
|
||||
GroovyObject obj = (GroovyObject) tester.newInstance();
|
||||
this.results.add((TestResults) obj.invokeMethod("findAndTest", compiled));
|
||||
}
|
||||
|
||||
printReport(this.results);
|
||||
}
|
||||
|
||||
private List<File> compileAndCollectTesterFiles(Object[] sources)
|
||||
throws CompilationFailedException, IOException {
|
||||
Set<String> testerUnits = new LinkedHashSet<String>();
|
||||
List<File> testerFiles = new ArrayList<File>();
|
||||
addTesterOnClass(sources, "org.junit.Test", testerFiles, testerUnits, "junit");
|
||||
addTesterOnClass(sources, "spock.lang.Specification", testerFiles,
|
||||
testerUnits, "junit", "spock");
|
||||
if (!testerFiles.isEmpty()) {
|
||||
testerFiles.add(createTempTesterFile("tester"));
|
||||
}
|
||||
|
||||
return testerFiles;
|
||||
}
|
||||
|
||||
private void addTesterOnClass(Object[] sources, String className,
|
||||
List<File> testerFiles, Set<String> testerUnits, String... testerNames) {
|
||||
for (Object source : sources) {
|
||||
if (source instanceof Class<?>) {
|
||||
try {
|
||||
((Class<?>) source).getClassLoader().loadClass(className);
|
||||
for (String testerName : testerNames) {
|
||||
if (testerUnits.add(testerName)) {
|
||||
testerFiles.add(createTempTesterFile(testerName));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private File createTempTesterFile(String name) {
|
||||
try {
|
||||
File file = File.createTempFile(name, ".groovy");
|
||||
file.deleteOnExit();
|
||||
URL resource = getClass().getClassLoader().getResource(
|
||||
"testers/" + name + ".groovy");
|
||||
FileUtils.copy(resource, file);
|
||||
return file;
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException("Could not create temp file for source: "
|
||||
+ name);
|
||||
}
|
||||
}
|
||||
|
||||
private void printReport(TestResults results) throws FileNotFoundException {
|
||||
PrintWriter writer = new PrintWriter("results.txt");
|
||||
|
||||
String header = "Total: " + results.getRunCount() + ", Success: "
|
||||
+ (results.getRunCount() - results.getFailureCount())
|
||||
+ ", : Failures: " + results.getFailureCount() + "\n" + "Passed? "
|
||||
+ results.wasSuccessful();
|
||||
|
||||
String trailer = "";
|
||||
String trace = "";
|
||||
for (Failure failure : results.getFailures()) {
|
||||
trailer += "Failed: " + failure.getDescription().toString() + "\n";
|
||||
trace += failure.getTrace() + "\n";
|
||||
}
|
||||
|
||||
writer.println(header);
|
||||
writer.println(trace);
|
||||
writer.close();
|
||||
|
||||
Log.info(header);
|
||||
Log.info(trailer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.cli.command.tester;
|
||||
|
||||
/**
|
||||
* Platform neutral way to capture a test failure
|
||||
*
|
||||
* NOTE: This is needed to avoid having to add JUnit jar file to the deployable artifacts
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public class Failure {
|
||||
|
||||
private String description;
|
||||
|
||||
private String trace;
|
||||
|
||||
public Failure(String description, String trace) {
|
||||
this.description = description;
|
||||
this.trace = trace;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getTrace() {
|
||||
return this.trace;
|
||||
}
|
||||
|
||||
public void setTrace(String trace) {
|
||||
this.trace = trace;
|
||||
}
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.cli.command.tester;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Platform neutral way to collect test results
|
||||
*
|
||||
* NOTE: This is needed to avoid having to add JUnit's jar file to the deployable
|
||||
* artifacts
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public class TestResults {
|
||||
|
||||
public static final TestResults NONE = new TestResults() {
|
||||
|
||||
@Override
|
||||
public int getRunCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFailureCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Failure> getFailures() {
|
||||
return new ArrayList<Failure>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean wasSuccessful() {
|
||||
return true;
|
||||
}
|
||||
|
||||
};
|
||||
private int runCount = 0;
|
||||
private List<Failure> failures = new ArrayList<Failure>();
|
||||
|
||||
public void add(TestResults results) {
|
||||
this.runCount += results.getRunCount();
|
||||
this.failures.addAll(results.getFailures());
|
||||
}
|
||||
|
||||
public boolean wasSuccessful() {
|
||||
return this.failures.size() == 0;
|
||||
}
|
||||
|
||||
public int getRunCount() {
|
||||
return this.runCount;
|
||||
}
|
||||
|
||||
public void setRunCount(int runCount) {
|
||||
this.runCount = runCount;
|
||||
}
|
||||
|
||||
public int getFailureCount() {
|
||||
return this.failures.size();
|
||||
}
|
||||
|
||||
public List<Failure> getFailures() {
|
||||
return this.failures;
|
||||
}
|
||||
|
||||
public void setFailures(List<Failure> failures) {
|
||||
this.failures = failures;
|
||||
}
|
||||
|
||||
}
|
|
@ -177,15 +177,16 @@ public class DependencyCustomizer {
|
|||
}
|
||||
|
||||
/**
|
||||
* Add a single dependency and all of its dependencies. The group ID and version of
|
||||
* the dependency are resolves using the customizer's
|
||||
* {@link ArtifactCoordinatesResolver}.
|
||||
* @param module The module ID
|
||||
* Add dependencies and all of their dependencies. The group ID and version of the
|
||||
* dependency are resolves using the customizer's {@link ArtifactCoordinatesResolver}.
|
||||
* @param modules The module IDs
|
||||
* @return this {@link DependencyCustomizer} for continued use
|
||||
*/
|
||||
public DependencyCustomizer add(String module) {
|
||||
return this.add(this.coordinatesResolver.getGroupId(module), module,
|
||||
this.coordinatesResolver.getVersion(module), true);
|
||||
public DependencyCustomizer add(String... modules) {
|
||||
for (String module : modules) {
|
||||
add(module, true);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -198,15 +199,10 @@ public class DependencyCustomizer {
|
|||
* @return this {@link DependencyCustomizer} for continued use
|
||||
*/
|
||||
public DependencyCustomizer add(String module, boolean transitive) {
|
||||
return this.add(this.coordinatesResolver.getGroupId(module), module,
|
||||
this.coordinatesResolver.getVersion(module), transitive);
|
||||
}
|
||||
|
||||
private DependencyCustomizer add(String group, String module, String version,
|
||||
boolean transitive) {
|
||||
if (canAdd()) {
|
||||
this.classNode.addAnnotation(createGrabAnnotation(group, module, version,
|
||||
transitive));
|
||||
this.classNode.addAnnotation(createGrabAnnotation(
|
||||
this.coordinatesResolver.getGroupId(module), module,
|
||||
this.coordinatesResolver.getVersion(module), transitive));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -19,7 +19,11 @@ package org.springframework.boot.cli.compiler;
|
|||
import groovy.lang.GroovyClassLoader;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.util.HashMap;
|
||||
|
@ -29,6 +33,8 @@ import org.codehaus.groovy.ast.ClassNode;
|
|||
import org.codehaus.groovy.control.CompilationUnit;
|
||||
import org.codehaus.groovy.control.CompilerConfiguration;
|
||||
import org.codehaus.groovy.control.SourceUnit;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
/**
|
||||
* Extension of the {@link GroovyClassLoader} with support for obtaining '.class' files as
|
||||
|
@ -39,13 +45,67 @@ import org.codehaus.groovy.control.SourceUnit;
|
|||
*/
|
||||
class ExtendedGroovyClassLoader extends GroovyClassLoader {
|
||||
|
||||
private Map<String, byte[]> classResources = new HashMap<String, byte[]>();
|
||||
private static final String SHARED_PACKAGE = "org.springframework.boot.groovy";
|
||||
|
||||
private CompilerConfiguration configuration;
|
||||
private final Map<String, byte[]> classResources = new HashMap<String, byte[]>();
|
||||
|
||||
public ExtendedGroovyClassLoader(ClassLoader loader, CompilerConfiguration config) {
|
||||
super(loader, config);
|
||||
this.configuration = config;
|
||||
private final GroovyCompilerScope scope;
|
||||
|
||||
private final CompilerConfiguration configuration;
|
||||
|
||||
public ExtendedGroovyClassLoader(GroovyCompilerScope scope) {
|
||||
this(scope, createParentClassLoader(scope), new CompilerConfiguration());
|
||||
}
|
||||
|
||||
private static ClassLoader createParentClassLoader(GroovyCompilerScope scope) {
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
if (scope == GroovyCompilerScope.DEFAULT) {
|
||||
classLoader = new DefaultScopeParentClassLoader(classLoader);
|
||||
}
|
||||
return classLoader;
|
||||
}
|
||||
|
||||
private ExtendedGroovyClassLoader(GroovyCompilerScope scope, ClassLoader parent,
|
||||
CompilerConfiguration configuration) {
|
||||
super(parent, configuration);
|
||||
this.configuration = configuration;
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> findClass(String name) throws ClassNotFoundException {
|
||||
try {
|
||||
return super.findClass(name);
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
if (this.scope == GroovyCompilerScope.DEFAULT
|
||||
&& name.startsWith(SHARED_PACKAGE)) {
|
||||
Class<?> sharedClass = findSharedClass(name);
|
||||
if (sharedClass != null) {
|
||||
return sharedClass;
|
||||
}
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
private Class<?> findSharedClass(String name) {
|
||||
try {
|
||||
String path = name.replace('.', '/').concat(".class");
|
||||
InputStream inputStream = getParent().getResourceAsStream(path);
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
return defineClass(name, FileCopyUtils.copyToByteArray(inputStream));
|
||||
}
|
||||
finally {
|
||||
inputStream.close();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -58,10 +118,6 @@ class ExtendedGroovyClassLoader extends GroovyClassLoader {
|
|||
return resourceStream;
|
||||
}
|
||||
|
||||
public CompilerConfiguration getConfiguration() {
|
||||
return this.configuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassCollector createCollector(CompilationUnit unit, SourceUnit su) {
|
||||
InnerLoader loader = AccessController
|
||||
|
@ -74,6 +130,10 @@ class ExtendedGroovyClassLoader extends GroovyClassLoader {
|
|||
return new ExtendedClassCollector(loader, unit, su);
|
||||
}
|
||||
|
||||
public CompilerConfiguration getConfiguration() {
|
||||
return this.configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inner collector class used to track as classes are added.
|
||||
*/
|
||||
|
@ -93,4 +153,73 @@ class ExtendedGroovyClassLoader extends GroovyClassLoader {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ClassLoader used for a parent that filters so that only classes from groovy-all.jar
|
||||
* are exposed.
|
||||
*/
|
||||
private static class DefaultScopeParentClassLoader extends ClassLoader {
|
||||
|
||||
private final URLClassLoader groovyOnlyClassLoader;
|
||||
|
||||
public DefaultScopeParentClassLoader(ClassLoader parent) {
|
||||
super(parent);
|
||||
this.groovyOnlyClassLoader = new URLClassLoader(
|
||||
new URL[] { getGroovyJar(parent) }, null);
|
||||
}
|
||||
|
||||
private URL getGroovyJar(final ClassLoader parent) {
|
||||
URL result = findGroovyJarDirectly(parent);
|
||||
if (result == null) {
|
||||
result = findGroovyJarFromClassPath(parent);
|
||||
}
|
||||
Assert.state(result != null, "Unable to find groovy JAR");
|
||||
return result;
|
||||
}
|
||||
|
||||
private URL findGroovyJarDirectly(ClassLoader classLoader) {
|
||||
while (classLoader != null) {
|
||||
if (classLoader instanceof URLClassLoader) {
|
||||
URL[] urls = ((URLClassLoader) classLoader).getURLs();
|
||||
for (URL url : urls) {
|
||||
if (isGroovyJar(url.toString())) {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
}
|
||||
classLoader = classLoader.getParent();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private URL findGroovyJarFromClassPath(ClassLoader parent) {
|
||||
String classpath = System.getProperty("java.class.path");
|
||||
String[] entries = classpath.split(System.getProperty("path.separator"));
|
||||
for (String entry : entries) {
|
||||
if (isGroovyJar(entry)) {
|
||||
File file = new File(entry);
|
||||
if (file.canRead()) {
|
||||
try {
|
||||
return file.toURI().toURL();
|
||||
}
|
||||
catch (MalformedURLException ex) {
|
||||
// Swallow and continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isGroovyJar(String entry) {
|
||||
return entry.contains("/groovy-all");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> loadClass(String name, boolean resolve)
|
||||
throws ClassNotFoundException {
|
||||
this.groovyOnlyClassLoader.loadClass(name);
|
||||
return super.loadClass(name, resolve);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,16 +16,12 @@
|
|||
|
||||
package org.springframework.boot.cli.compiler;
|
||||
|
||||
import groovy.grape.GrapeEngine;
|
||||
import groovy.lang.GroovyClassLoader;
|
||||
import groovy.lang.GroovyClassLoader.ClassCollector;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
|
@ -45,6 +41,10 @@ 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.grape.AetherGrapeEngine;
|
||||
import org.springframework.boot.cli.compiler.grape.GrapeEngineInstaller;
|
||||
import org.springframework.boot.cli.compiler.transformation.DependencyAutoConfigurationTransformation;
|
||||
import org.springframework.boot.cli.compiler.transformation.ResolveDependencyCoordinatesTransformation;
|
||||
|
||||
/**
|
||||
* Compiler for Groovy source files. Primarily a simple Facade for
|
||||
|
@ -67,15 +67,12 @@ import org.codehaus.groovy.transform.ASTTransformationVisitor;
|
|||
*/
|
||||
public class GroovyCompiler {
|
||||
|
||||
private static final ClassLoader AETHER_CLASS_LOADER = new URLClassLoader(
|
||||
new URL[] { GroovyCompiler.class.getResource("/internal/") });
|
||||
private final ArtifactCoordinatesResolver coordinatesResolver;
|
||||
|
||||
private final GroovyCompilerConfiguration configuration;
|
||||
|
||||
private final ExtendedGroovyClassLoader loader;
|
||||
|
||||
private final ArtifactCoordinatesResolver coordinatesResolver;
|
||||
|
||||
private final ServiceLoader<CompilerAutoConfiguration> compilerAutoConfigurations;
|
||||
|
||||
private final List<ASTTransformation> transformations;
|
||||
|
@ -85,44 +82,35 @@ public class GroovyCompiler {
|
|||
* @param configuration the compiler configuration
|
||||
*/
|
||||
public GroovyCompiler(final GroovyCompilerConfiguration configuration) {
|
||||
|
||||
this.configuration = configuration;
|
||||
this.loader = new ExtendedGroovyClassLoader(getClass().getClassLoader(),
|
||||
new CompilerConfiguration());
|
||||
if (configuration.getClasspath().length() > 0) {
|
||||
this.loader.addClasspath(configuration.getClasspath());
|
||||
}
|
||||
this.loader = createLoader(configuration);
|
||||
|
||||
this.coordinatesResolver = new PropertiesArtifactCoordinatesResolver(this.loader);
|
||||
installGrapeEngine();
|
||||
GrapeEngineInstaller.install(new AetherGrapeEngine(this.loader));
|
||||
|
||||
this.loader.getConfiguration().addCompilationCustomizers(
|
||||
new CompilerAutoConfigureCustomizer());
|
||||
this.compilerAutoConfigurations = ServiceLoader.load(
|
||||
CompilerAutoConfiguration.class, GroovyCompiler.class.getClassLoader());
|
||||
|
||||
this.transformations = new ArrayList<ASTTransformation>();
|
||||
this.transformations.add(new DependencyAutoConfigurationTransformation(this.loader,
|
||||
this.coordinatesResolver, this.compilerAutoConfigurations));
|
||||
this.transformations.add(new DependencyAutoConfigurationTransformation(
|
||||
this.loader, this.coordinatesResolver, this.compilerAutoConfigurations));
|
||||
if (this.configuration.isGuessDependencies()) {
|
||||
this.transformations.add(new ResolveDependencyCoordinatesTransformation(
|
||||
this.coordinatesResolver));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void installGrapeEngine() {
|
||||
try {
|
||||
Class<GrapeEngine> grapeEngineClass = (Class<GrapeEngine>) AETHER_CLASS_LOADER
|
||||
.loadClass("org.springframework.boot.cli.compiler.AetherGrapeEngine");
|
||||
Constructor<GrapeEngine> constructor = grapeEngineClass.getConstructor(
|
||||
GroovyClassLoader.class, String.class, String.class, String.class);
|
||||
GrapeEngine grapeEngine = constructor.newInstance(this.loader,
|
||||
"org.springframework.boot", "spring-boot-starter-parent",
|
||||
this.coordinatesResolver.getVersion("spring-boot"));
|
||||
|
||||
new GrapeEngineInstaller(grapeEngine).install();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Failed to install custom GrapeEngine", ex);
|
||||
private ExtendedGroovyClassLoader createLoader(
|
||||
GroovyCompilerConfiguration configuration) {
|
||||
ExtendedGroovyClassLoader loader = new ExtendedGroovyClassLoader(
|
||||
configuration.getScope());
|
||||
for (String classpath : configuration.getClasspath()) {
|
||||
loader.addClasspath(classpath);
|
||||
}
|
||||
return loader;
|
||||
}
|
||||
|
||||
public void addCompilationCustomizers(CompilationCustomizer... customizers) {
|
||||
|
@ -201,7 +189,7 @@ public class GroovyCompiler {
|
|||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private LinkedList[] getPhaseOperations(final CompilationUnit compilationUnit) {
|
||||
private LinkedList[] getPhaseOperations(CompilationUnit compilationUnit) {
|
||||
try {
|
||||
Field field = CompilationUnit.class.getDeclaredField("phaseOperations");
|
||||
field.setAccessible(true);
|
||||
|
|
|
@ -23,6 +23,16 @@ package org.springframework.boot.cli.compiler;
|
|||
*/
|
||||
public interface GroovyCompilerConfiguration {
|
||||
|
||||
/**
|
||||
* Constant to be used when there is not {@link #getClasspath() classpath}.
|
||||
*/
|
||||
public static final String[] NO_CLASSPATH = {};
|
||||
|
||||
/**
|
||||
* Returns the scope in which the compiler operates.
|
||||
*/
|
||||
GroovyCompilerScope getScope();
|
||||
|
||||
/**
|
||||
* Returns if import declarations should be guessed.
|
||||
*/
|
||||
|
@ -34,8 +44,8 @@ public interface GroovyCompilerConfiguration {
|
|||
boolean isGuessDependencies();
|
||||
|
||||
/**
|
||||
* @return a path for local resources (colon separated)
|
||||
* @return a path for local resources
|
||||
*/
|
||||
String getClasspath();
|
||||
String[] getClasspath();
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.cli.compiler;
|
||||
|
||||
/**
|
||||
* The scope in which a groovy compiler operates.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public enum GroovyCompilerScope {
|
||||
|
||||
/**
|
||||
* Default scope, exposes groovy-all.jar (loaded from the parent) and the shared cli
|
||||
* package (loaded via groovy classloader).
|
||||
*/
|
||||
DEFAULT,
|
||||
|
||||
/**
|
||||
* Extension scope, allows full access to internal CLI classes.
|
||||
*/
|
||||
EXTENSION
|
||||
|
||||
}
|
|
@ -16,27 +16,25 @@
|
|||
|
||||
package org.springframework.boot.cli.compiler;
|
||||
|
||||
import groovy.lang.GroovyClassLoader;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link ArtifactCoordinatesResolver} backed by a properties file.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
final class PropertiesArtifactCoordinatesResolver implements ArtifactCoordinatesResolver {
|
||||
public final class PropertiesArtifactCoordinatesResolver implements
|
||||
ArtifactCoordinatesResolver {
|
||||
|
||||
private final GroovyClassLoader loader;
|
||||
private final ClassLoader loader;
|
||||
|
||||
private Properties properties = null;
|
||||
|
||||
public PropertiesArtifactCoordinatesResolver(GroovyClassLoader loader) {
|
||||
public PropertiesArtifactCoordinatesResolver(ClassLoader loader) {
|
||||
this.loader = loader;
|
||||
}
|
||||
|
||||
|
@ -52,34 +50,24 @@ final class PropertiesArtifactCoordinatesResolver implements ArtifactCoordinates
|
|||
|
||||
private String getProperty(String name) {
|
||||
if (this.properties == null) {
|
||||
loadProperties();
|
||||
this.properties = loadProperties();
|
||||
}
|
||||
String property = this.properties.getProperty(name);
|
||||
return property;
|
||||
}
|
||||
|
||||
private void loadProperties() {
|
||||
private Properties loadProperties() {
|
||||
Properties properties = new Properties();
|
||||
InputStream inputStream = this.loader
|
||||
.getResourceAsStream("META-INF/springcli.properties");
|
||||
Assert.state(inputStream != null, "Unable to load springcli properties");
|
||||
try {
|
||||
ArrayList<URL> urls = Collections.list(this.loader
|
||||
.getResources("META-INF/springcli.properties"));
|
||||
for (URL url : urls) {
|
||||
InputStream inputStream = url.openStream();
|
||||
try {
|
||||
properties.load(inputStream);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// Swallow and continue
|
||||
}
|
||||
finally {
|
||||
inputStream.close();
|
||||
}
|
||||
}
|
||||
properties.load(inputStream);
|
||||
return properties;
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// Swallow and continue
|
||||
throw new IllegalStateException("Unable to load springcli properties", ex);
|
||||
}
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ public class JUnitCompilerAutoConfiguration extends CompilerAutoConfiguration {
|
|||
@Override
|
||||
public void applyDependencies(DependencyCustomizer dependencies)
|
||||
throws CompilationFailedException {
|
||||
dependencies.add("junit").add("spring-test").add("hamcrest-library");
|
||||
dependencies.add("junit").add("spring-test", "hamcrest-library");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -16,18 +16,13 @@
|
|||
|
||||
package org.springframework.boot.cli.compiler.autoconfigure;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.codehaus.groovy.ast.ClassNode;
|
||||
import org.codehaus.groovy.control.CompilationFailedException;
|
||||
import org.codehaus.groovy.control.customizers.ImportCustomizer;
|
||||
import org.springframework.boot.cli.compiler.AstUtils;
|
||||
import org.springframework.boot.cli.compiler.CompilerAutoConfiguration;
|
||||
import org.springframework.boot.cli.compiler.DependencyCustomizer;
|
||||
import org.springframework.boot.groovy.EnableJmsMessaging;
|
||||
|
||||
/**
|
||||
* {@link CompilerAutoConfiguration} for Spring JMS.
|
||||
|
@ -46,7 +41,7 @@ public class JmsCompilerAutoConfiguration extends CompilerAutoConfiguration {
|
|||
@Override
|
||||
public void applyDependencies(DependencyCustomizer dependencies)
|
||||
throws CompilationFailedException {
|
||||
dependencies.add("spring-jms").add("geronimo-jms_1.1_spec");
|
||||
dependencies.add("spring-jms", "geronimo-jms_1.1_spec");
|
||||
|
||||
}
|
||||
|
||||
|
@ -58,11 +53,4 @@ public class JmsCompilerAutoConfiguration extends CompilerAutoConfiguration {
|
|||
EnableJmsMessaging.class.getCanonicalName());
|
||||
}
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public static @interface EnableJmsMessaging {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ public class SpringBatchCompilerAutoConfiguration extends CompilerAutoConfigurat
|
|||
@Override
|
||||
public void applyDependencies(DependencyCustomizer dependencies) {
|
||||
dependencies.ifAnyMissingClasses("org.springframework.batch.core.Job").add(
|
||||
"spring-batch-core");
|
||||
"spring-boot-starter-batch");
|
||||
dependencies.ifAnyMissingClasses("org.springframework.jdbc.core.JdbcTemplate")
|
||||
.add("spring-jdbc");
|
||||
}
|
||||
|
|
|
@ -71,7 +71,6 @@ public class SpringBootCompilerAutoConfiguration extends CompilerAutoConfigurati
|
|||
public void applyToMainClass(GroovyClassLoader loader,
|
||||
GroovyCompilerConfiguration configuration, GeneratorContext generatorContext,
|
||||
SourceUnit source, ClassNode classNode) throws CompilationFailedException {
|
||||
// Could add switch for auto config, but it seems like it wouldn't get used much
|
||||
addEnableAutoConfigurationAnnotation(source, classNode);
|
||||
}
|
||||
|
||||
|
|
|
@ -16,17 +16,12 @@
|
|||
|
||||
package org.springframework.boot.cli.compiler.autoconfigure;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.codehaus.groovy.ast.ClassNode;
|
||||
import org.codehaus.groovy.control.customizers.ImportCustomizer;
|
||||
import org.springframework.boot.cli.compiler.AstUtils;
|
||||
import org.springframework.boot.cli.compiler.CompilerAutoConfiguration;
|
||||
import org.springframework.boot.cli.compiler.DependencyCustomizer;
|
||||
import org.springframework.boot.groovy.EnableIntegrationPatterns;
|
||||
|
||||
/**
|
||||
* {@link CompilerAutoConfiguration} for Spring Integration.
|
||||
|
@ -45,8 +40,8 @@ public class SpringIntegrationCompilerAutoConfiguration extends CompilerAutoConf
|
|||
|
||||
@Override
|
||||
public void applyDependencies(DependencyCustomizer dependencies) {
|
||||
dependencies.ifAnyMissingClasses("org.springframework.integration.Message")
|
||||
.add("spring-integration-core").add("spring-integration-dsl-groovy-core");
|
||||
dependencies.ifAnyMissingClasses("org.springframework.integration.Message").add(
|
||||
"spring-boot-starter-integration", "spring-integration-dsl-groovy-core");
|
||||
dependencies.ifAnyMissingClasses("groovy.util.XmlParser").add("groovy-xml");
|
||||
}
|
||||
|
||||
|
@ -61,15 +56,8 @@ public class SpringIntegrationCompilerAutoConfiguration extends CompilerAutoConf
|
|||
"org.springframework.integration.annotation.Headers",
|
||||
"org.springframework.integration.annotation.Payload",
|
||||
"org.springframework.integration.annotation.Payloads",
|
||||
EnableIntegrationPatterns.class.getCanonicalName(),
|
||||
"org.springframework.integration.dsl.groovy.MessageFlow",
|
||||
"org.springframework.integration.dsl.groovy.builder.IntegrationBuilder");
|
||||
}
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public static @interface EnableIntegrationPatterns {
|
||||
|
||||
"org.springframework.integration.dsl.groovy.builder.IntegrationBuilder",
|
||||
EnableIntegrationPatterns.class.getCanonicalName());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,18 +16,13 @@
|
|||
|
||||
package org.springframework.boot.cli.compiler.autoconfigure;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.codehaus.groovy.ast.ClassNode;
|
||||
import org.codehaus.groovy.control.CompilationFailedException;
|
||||
import org.codehaus.groovy.control.customizers.ImportCustomizer;
|
||||
import org.springframework.boot.cli.compiler.AstUtils;
|
||||
import org.springframework.boot.cli.compiler.CompilerAutoConfiguration;
|
||||
import org.springframework.boot.cli.compiler.DependencyCustomizer;
|
||||
import org.springframework.boot.groovy.EnableDeviceResolver;
|
||||
|
||||
/**
|
||||
* {@link CompilerAutoConfiguration} for Spring Mobile.
|
||||
|
@ -45,7 +40,7 @@ public class SpringMobileCompilerAutoConfiguration extends CompilerAutoConfigura
|
|||
@Override
|
||||
public void applyDependencies(DependencyCustomizer dependencies)
|
||||
throws CompilationFailedException {
|
||||
dependencies.add("spring-mobile-device");
|
||||
dependencies.add("spring-boot-starter-mobile");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -54,11 +49,4 @@ public class SpringMobileCompilerAutoConfiguration extends CompilerAutoConfigura
|
|||
imports.addImports(EnableDeviceResolver.class.getCanonicalName());
|
||||
}
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public static @interface EnableDeviceResolver {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,17 +16,12 @@
|
|||
|
||||
package org.springframework.boot.cli.compiler.autoconfigure;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.codehaus.groovy.ast.ClassNode;
|
||||
import org.codehaus.groovy.control.customizers.ImportCustomizer;
|
||||
import org.springframework.boot.cli.compiler.AstUtils;
|
||||
import org.springframework.boot.cli.compiler.CompilerAutoConfiguration;
|
||||
import org.springframework.boot.cli.compiler.DependencyCustomizer;
|
||||
import org.springframework.boot.groovy.GroovyTemplate;
|
||||
|
||||
/**
|
||||
* {@link CompilerAutoConfiguration} for Spring MVC.
|
||||
|
@ -39,7 +34,7 @@ public class SpringMvcCompilerAutoConfiguration extends CompilerAutoConfiguratio
|
|||
@Override
|
||||
public boolean matches(ClassNode classNode) {
|
||||
return AstUtils.hasAtLeastOneAnnotation(classNode, "Controller",
|
||||
"RestController", "EnableWebMvc", "WebConfiguration");
|
||||
"RestController", "EnableWebMvc");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -47,7 +42,6 @@ public class SpringMvcCompilerAutoConfiguration extends CompilerAutoConfiguratio
|
|||
dependencies
|
||||
.ifAnyMissingClasses("org.springframework.web.servlet.mvc.Controller")
|
||||
.add("spring-boot-starter-web");
|
||||
|
||||
dependencies.add("groovy-templates");
|
||||
}
|
||||
|
||||
|
@ -57,15 +51,7 @@ public class SpringMvcCompilerAutoConfiguration extends CompilerAutoConfiguratio
|
|||
"org.springframework.web.servlet.config.annotation",
|
||||
"org.springframework.web.servlet",
|
||||
"org.springframework.web.servlet.handler", "org.springframework.http");
|
||||
imports.addStaticImport("org.springframework.boot.cli.template.GroovyTemplate",
|
||||
"template");
|
||||
imports.addImports(WebConfiguration.class.getCanonicalName());
|
||||
}
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Documented
|
||||
public static @interface WebConfiguration {
|
||||
imports.addStaticImport(GroovyTemplate.class.getName(), "template");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -38,10 +38,9 @@ public class TransactionManagementCompilerAutoConfiguration extends
|
|||
|
||||
@Override
|
||||
public void applyDependencies(DependencyCustomizer dependencies) {
|
||||
dependencies
|
||||
.ifAnyMissingClasses(
|
||||
"org.springframework.transaction.annotation.Transactional")
|
||||
.add("spring-tx").add("spring-boot-starter-aop");
|
||||
dependencies.ifAnyMissingClasses(
|
||||
"org.springframework.transaction.annotation.Transactional").add(
|
||||
"spring-tx", "spring-boot-starter-aop");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,249 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.cli.compiler.grape;
|
||||
|
||||
import groovy.grape.GrapeEngine;
|
||||
import groovy.lang.GroovyClassLoader;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
|
||||
import org.eclipse.aether.DefaultRepositorySystemSession;
|
||||
import org.eclipse.aether.RepositorySystem;
|
||||
import org.eclipse.aether.RepositorySystemSession;
|
||||
import org.eclipse.aether.artifact.Artifact;
|
||||
import org.eclipse.aether.artifact.DefaultArtifact;
|
||||
import org.eclipse.aether.collection.CollectRequest;
|
||||
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
|
||||
import org.eclipse.aether.graph.Dependency;
|
||||
import org.eclipse.aether.graph.Exclusion;
|
||||
import org.eclipse.aether.impl.DefaultServiceLocator;
|
||||
import org.eclipse.aether.internal.impl.DefaultRepositorySystem;
|
||||
import org.eclipse.aether.repository.LocalRepository;
|
||||
import org.eclipse.aether.repository.LocalRepositoryManager;
|
||||
import org.eclipse.aether.repository.RemoteRepository;
|
||||
import org.eclipse.aether.resolution.ArtifactResolutionException;
|
||||
import org.eclipse.aether.resolution.ArtifactResult;
|
||||
import org.eclipse.aether.resolution.DependencyRequest;
|
||||
import org.eclipse.aether.resolution.DependencyResult;
|
||||
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
|
||||
import org.eclipse.aether.spi.connector.transport.TransporterFactory;
|
||||
import org.eclipse.aether.spi.locator.ServiceLocator;
|
||||
import org.eclipse.aether.transport.file.FileTransporterFactory;
|
||||
import org.eclipse.aether.transport.http.HttpTransporterFactory;
|
||||
import org.eclipse.aether.util.artifact.JavaScopes;
|
||||
import org.eclipse.aether.util.filter.DependencyFilterUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class AetherGrapeEngine implements GrapeEngine {
|
||||
|
||||
private static final Collection<Exclusion> WILDCARD_EXCLUSION = Arrays
|
||||
.asList(new Exclusion("*", "*", "*", "*"));
|
||||
|
||||
private final ProgressReporter progressReporter;
|
||||
|
||||
private final GroovyClassLoader classLoader;
|
||||
|
||||
private final RepositorySystemSession session;
|
||||
|
||||
private final RepositorySystem repositorySystem;
|
||||
|
||||
private final List<RemoteRepository> repositories;
|
||||
|
||||
public AetherGrapeEngine(GroovyClassLoader classLoader) {
|
||||
this.classLoader = classLoader;
|
||||
this.repositorySystem = createServiceLocator().getService(RepositorySystem.class);
|
||||
DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession();
|
||||
LocalRepository localRepository = new LocalRepository(getM2RepoDirectory());
|
||||
LocalRepositoryManager localRepositoryManager = this.repositorySystem
|
||||
.newLocalRepositoryManager(session, localRepository);
|
||||
session.setLocalRepositoryManager(localRepositoryManager);
|
||||
this.session = session;
|
||||
this.repositories = getRemoteRepositories();
|
||||
this.progressReporter = new ProgressReporter(session);
|
||||
}
|
||||
|
||||
private ServiceLocator createServiceLocator() {
|
||||
DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator();
|
||||
locator.addService(RepositorySystem.class, DefaultRepositorySystem.class);
|
||||
locator.addService(RepositoryConnectorFactory.class,
|
||||
BasicRepositoryConnectorFactory.class);
|
||||
locator.addService(TransporterFactory.class, HttpTransporterFactory.class);
|
||||
locator.addService(TransporterFactory.class, FileTransporterFactory.class);
|
||||
return locator;
|
||||
}
|
||||
|
||||
private File getM2RepoDirectory() {
|
||||
return new File(getM2HomeDirectory(), "repository");
|
||||
}
|
||||
|
||||
private File getM2HomeDirectory() {
|
||||
String grapeRoot = System.getProperty("grape.root");
|
||||
if (StringUtils.hasLength(grapeRoot)) {
|
||||
return new File(grapeRoot);
|
||||
}
|
||||
return new File(System.getProperty("user.home"), ".m2");
|
||||
}
|
||||
|
||||
private List<RemoteRepository> getRemoteRepositories() {
|
||||
List<RemoteRepository> repositories = new ArrayList<RemoteRepository>();
|
||||
addRemoteRepository(repositories, "central", "http://repo1.maven.org/maven2/");
|
||||
if (!Boolean.getBoolean("disableSpringSnapshotRepos")) {
|
||||
addRemoteRepository(repositories, "spring-snapshot",
|
||||
"http://repo.spring.io/snapshot");
|
||||
addRemoteRepository(repositories, "spring-milestone",
|
||||
"http://repo.spring.io/milestone");
|
||||
}
|
||||
return repositories;
|
||||
}
|
||||
|
||||
private void addRemoteRepository(List<RemoteRepository> repositories, String id,
|
||||
String url) {
|
||||
repositories.add(new RemoteRepository.Builder(id, "default", url).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object grab(Map args) {
|
||||
return grab(args, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object grab(Map args, Map... dependencyMaps) {
|
||||
try {
|
||||
List<Dependency> dependencies = createDependencies(dependencyMaps);
|
||||
List<File> files = resolve(dependencies);
|
||||
GroovyClassLoader classLoader = getClassLoader(args);
|
||||
for (File file : files) {
|
||||
classLoader.addURL(file.toURI().toURL());
|
||||
}
|
||||
}
|
||||
catch (ArtifactResolutionException ex) {
|
||||
throw new DependencyResolutionFailedException(ex);
|
||||
}
|
||||
catch (MalformedURLException ex) {
|
||||
throw new DependencyResolutionFailedException(ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private GroovyClassLoader getClassLoader(Map args) {
|
||||
GroovyClassLoader classLoader = (GroovyClassLoader) args.get("classLoader");
|
||||
return (classLoader == null ? this.classLoader : classLoader);
|
||||
}
|
||||
|
||||
private List<Dependency> createDependencies(Map<?, ?>... dependencyMaps) {
|
||||
List<Dependency> dependencies = new ArrayList<Dependency>(dependencyMaps.length);
|
||||
for (Map<?, ?> dependencyMap : dependencyMaps) {
|
||||
dependencies.add(createDependency(dependencyMap));
|
||||
}
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
private Dependency createDependency(Map<?, ?> dependencyMap) {
|
||||
Artifact artifact = createArtifact(dependencyMap);
|
||||
if (isTransitive(dependencyMap)) {
|
||||
return new Dependency(artifact, JavaScopes.COMPILE);
|
||||
}
|
||||
return new Dependency(artifact, JavaScopes.COMPILE, null, WILDCARD_EXCLUSION);
|
||||
}
|
||||
|
||||
private Artifact createArtifact(Map<?, ?> dependencyMap) {
|
||||
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);
|
||||
}
|
||||
|
||||
private boolean isTransitive(Map<?, ?> dependencyMap) {
|
||||
Boolean transitive = (Boolean) dependencyMap.get("transitive");
|
||||
return (transitive == null ? true : transitive);
|
||||
}
|
||||
|
||||
private List<File> resolve(List<Dependency> dependencies)
|
||||
throws ArtifactResolutionException {
|
||||
try {
|
||||
CollectRequest collectRequest = new CollectRequest((Dependency) null,
|
||||
dependencies, this.repositories);
|
||||
DependencyRequest dependencyRequest = new DependencyRequest(collectRequest,
|
||||
DependencyFilterUtils.classpathFilter(JavaScopes.COMPILE));
|
||||
DependencyResult dependencyResult = this.repositorySystem
|
||||
.resolveDependencies(this.session, dependencyRequest);
|
||||
return getFiles(dependencyResult);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new DependencyResolutionFailedException(ex);
|
||||
}
|
||||
finally {
|
||||
this.progressReporter.finished();
|
||||
}
|
||||
}
|
||||
|
||||
private List<File> getFiles(DependencyResult dependencyResult) {
|
||||
List<File> files = new ArrayList<File>();
|
||||
for (ArtifactResult result : dependencyResult.getArtifactResults()) {
|
||||
files.add(result.getArtifact().getFile());
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Map<String, List<String>>> enumerateGrapes() {
|
||||
throw new UnsupportedOperationException("Grape enumeration is not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI[] resolve(Map args, Map... dependencies) {
|
||||
throw new UnsupportedOperationException("Resolving to URIs is not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI[] resolve(Map args, List depsInfo, Map... dependencies) {
|
||||
throw new UnsupportedOperationException("Resolving to URIs is not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map[] listDependencies(ClassLoader classLoader) {
|
||||
throw new UnsupportedOperationException("Listing dependencies is not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addResolver(Map<String, Object> args) {
|
||||
throw new UnsupportedOperationException("Adding a resolver is not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object grab(String endorsedModule) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Grabbing an endorsed module is not supported");
|
||||
}
|
||||
}
|
|
@ -14,10 +14,11 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.cli.compiler;
|
||||
package org.springframework.boot.cli.compiler.grape;
|
||||
|
||||
/**
|
||||
* Thrown to indicate a failure during dependency resolution.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class DependencyResolutionFailedException extends RuntimeException {
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.cli.compiler;
|
||||
package org.springframework.boot.cli.compiler.grape;
|
||||
|
||||
import groovy.grape.Grape;
|
||||
import groovy.grape.GrapeEngine;
|
||||
|
@ -22,22 +22,18 @@ import groovy.grape.GrapeEngine;
|
|||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* Utility to install a specific {@link Grape} engine with Groovy.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class GrapeEngineInstaller {
|
||||
public abstract class GrapeEngineInstaller {
|
||||
|
||||
private final GrapeEngine grapeEngine;
|
||||
|
||||
public GrapeEngineInstaller(GrapeEngine grapeEngine) {
|
||||
this.grapeEngine = grapeEngine;
|
||||
}
|
||||
|
||||
public void install() {
|
||||
public static void install(GrapeEngine engine) {
|
||||
synchronized (Grape.class) {
|
||||
try {
|
||||
Field instanceField = Grape.class.getDeclaredField("instance");
|
||||
instanceField.setAccessible(true);
|
||||
instanceField.set(null, this.grapeEngine);
|
||||
Field field = Grape.class.getDeclaredField("instance");
|
||||
field.setAccessible(true);
|
||||
field.set(null, engine);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Failed to install GrapeEngine", ex);
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.cli.compiler.grape;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.aether.AbstractRepositoryListener;
|
||||
import org.eclipse.aether.DefaultRepositorySystemSession;
|
||||
import org.eclipse.aether.RepositoryEvent;
|
||||
import org.eclipse.aether.transfer.AbstractTransferListener;
|
||||
import org.eclipse.aether.transfer.TransferCancelledException;
|
||||
import org.eclipse.aether.transfer.TransferEvent;
|
||||
|
||||
/**
|
||||
* Provide console progress feedback for long running resolves.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
final class ProgressReporter {
|
||||
|
||||
private static final long INITIAL_DELAY = TimeUnit.SECONDS.toMillis(3);
|
||||
|
||||
private static final long PROGRESS_DELAY = TimeUnit.SECONDS.toMillis(1);
|
||||
|
||||
private long startTime = System.currentTimeMillis();
|
||||
|
||||
private long lastProgressTime = System.currentTimeMillis();
|
||||
|
||||
private boolean started;
|
||||
|
||||
private boolean finished;
|
||||
|
||||
public ProgressReporter(DefaultRepositorySystemSession session) {
|
||||
session.setTransferListener(new AbstractTransferListener() {
|
||||
@Override
|
||||
public void transferStarted(TransferEvent event)
|
||||
throws TransferCancelledException {
|
||||
reportProgress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transferProgressed(TransferEvent event)
|
||||
throws TransferCancelledException {
|
||||
reportProgress();
|
||||
}
|
||||
});
|
||||
|
||||
session.setRepositoryListener(new AbstractRepositoryListener() {
|
||||
@Override
|
||||
public void artifactResolved(RepositoryEvent event) {
|
||||
reportProgress();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void reportProgress() {
|
||||
if (!this.finished && System.currentTimeMillis() - this.startTime > INITIAL_DELAY) {
|
||||
if (!this.started) {
|
||||
this.started = true;
|
||||
System.out.print("Resolving dependencies..");
|
||||
this.lastProgressTime = System.currentTimeMillis();
|
||||
}
|
||||
else if (System.currentTimeMillis() - this.lastProgressTime > PROGRESS_DELAY) {
|
||||
System.out.print(".");
|
||||
this.lastProgressTime = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void finished() {
|
||||
if (this.started && !this.finished) {
|
||||
this.finished = true;
|
||||
System.out.println("");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.cli.compiler;
|
||||
package org.springframework.boot.cli.compiler.transformation;
|
||||
|
||||
import groovy.lang.GroovyClassLoader;
|
||||
|
||||
|
@ -23,6 +23,9 @@ 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.ArtifactCoordinatesResolver;
|
||||
import org.springframework.boot.cli.compiler.CompilerAutoConfiguration;
|
||||
import org.springframework.boot.cli.compiler.DependencyCustomizer;
|
||||
|
||||
/**
|
||||
* {@link ASTTransformation} to apply
|
||||
|
@ -33,7 +36,7 @@ import org.codehaus.groovy.transform.ASTTransformation;
|
|||
* @author Dave Syer
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class DependencyAutoConfigurationTransformation implements ASTTransformation {
|
||||
public class DependencyAutoConfigurationTransformation implements ASTTransformation {
|
||||
|
||||
private final GroovyClassLoader loader;
|
||||
|
||||
|
@ -41,7 +44,7 @@ class DependencyAutoConfigurationTransformation implements ASTTransformation {
|
|||
|
||||
private final Iterable<CompilerAutoConfiguration> compilerAutoConfigurations;
|
||||
|
||||
DependencyAutoConfigurationTransformation(GroovyClassLoader loader,
|
||||
public DependencyAutoConfigurationTransformation(GroovyClassLoader loader,
|
||||
ArtifactCoordinatesResolver coordinatesResolver,
|
||||
Iterable<CompilerAutoConfiguration> compilerAutoConfigurations) {
|
||||
this.loader = loader;
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.cli.compiler;
|
||||
package org.springframework.boot.cli.compiler.transformation;
|
||||
|
||||
import groovy.lang.Grab;
|
||||
|
||||
|
@ -34,13 +34,14 @@ 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.ArtifactCoordinatesResolver;
|
||||
|
||||
/**
|
||||
* {@link ASTTransformation} to resolve {@link Grab} artifact coordinates.
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class ResolveDependencyCoordinatesTransformation implements ASTTransformation {
|
||||
public class ResolveDependencyCoordinatesTransformation implements ASTTransformation {
|
||||
|
||||
private static final Set<String> GRAB_ANNOTATION_NAMES = Collections
|
||||
.unmodifiableSet(new HashSet<String>(Arrays.asList(Grab.class.getName(),
|
||||
|
@ -48,7 +49,7 @@ class ResolveDependencyCoordinatesTransformation implements ASTTransformation {
|
|||
|
||||
private final ArtifactCoordinatesResolver coordinatesResolver;
|
||||
|
||||
ResolveDependencyCoordinatesTransformation(
|
||||
public ResolveDependencyCoordinatesTransformation(
|
||||
ArtifactCoordinatesResolver coordinatesResolver) {
|
||||
this.coordinatesResolver = coordinatesResolver;
|
||||
}
|
|
@ -37,7 +37,7 @@ public class SpringApplicationRunner {
|
|||
|
||||
private static int runnerCounter = 0;
|
||||
|
||||
private SpringApplicationRunnerConfiguration configuration;
|
||||
private final SpringApplicationRunnerConfiguration configuration;
|
||||
|
||||
private final File[] files;
|
||||
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.cli.testrunner;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.cli.compiler.GroovyCompiler;
|
||||
import org.springframework.boot.groovy.DelegateTestRunner;
|
||||
|
||||
/**
|
||||
* Compile and run groovy based tests.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class TestRunner {
|
||||
|
||||
private static final String DELEGATE_RUNNER = DelegateTestRunner.class.getName();
|
||||
|
||||
private static final String JUNIT_TEST_ANNOTATION = "org.junit.Test";
|
||||
|
||||
private final File[] files;
|
||||
|
||||
private final GroovyCompiler compiler;
|
||||
|
||||
/**
|
||||
* Create a new {@link TestRunner} instance.
|
||||
* @param configuration
|
||||
* @param files
|
||||
* @param args
|
||||
*/
|
||||
public TestRunner(TestRunnerConfiguration configuration, File[] files, String[] args) {
|
||||
this.files = files.clone();
|
||||
this.compiler = new GroovyCompiler(configuration);
|
||||
}
|
||||
|
||||
public void compileAndRunTests() throws Exception {
|
||||
Object[] sources = this.compiler.sources(this.files);
|
||||
if (sources.length == 0) {
|
||||
throw new RuntimeException("No classes found in '" + this.files + "'");
|
||||
}
|
||||
|
||||
// Run in new thread to ensure that the context classloader is setup
|
||||
RunThread runThread = new RunThread(sources);
|
||||
runThread.start();
|
||||
runThread.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread used to launch the Spring Application with the correct context classloader.
|
||||
*/
|
||||
private class RunThread extends Thread {
|
||||
|
||||
private final Class<?>[] testClasses;
|
||||
|
||||
private final Class<?> spockSpecificationClass;
|
||||
|
||||
/**
|
||||
* Create a new {@link RunThread} instance.
|
||||
* @param sources the sources to launch
|
||||
*/
|
||||
public RunThread(Object... sources) {
|
||||
super("testrunner");
|
||||
setDaemon(true);
|
||||
if (sources.length != 0 && sources[0] instanceof Class) {
|
||||
setContextClassLoader(((Class<?>) sources[0]).getClassLoader());
|
||||
}
|
||||
this.spockSpecificationClass = loadSpockSpecificationClass(getContextClassLoader());
|
||||
this.testClasses = getTestClasses(sources);
|
||||
}
|
||||
|
||||
private Class<?> loadSpockSpecificationClass(ClassLoader contextClassLoader) {
|
||||
try {
|
||||
return getContextClassLoader().loadClass("spock.lang.Specification");
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Class<?>[] getTestClasses(Object[] sources) {
|
||||
List<Class<?>> testClasses = new ArrayList<Class<?>>();
|
||||
for (Object source : sources) {
|
||||
if ((source instanceof Class) && isTestable((Class<?>) source)) {
|
||||
testClasses.add((Class<?>) source);
|
||||
}
|
||||
}
|
||||
return testClasses.toArray(new Class<?>[testClasses.size()]);
|
||||
}
|
||||
|
||||
private boolean isTestable(Class<?> sourceClass) {
|
||||
return (isJunitTest(sourceClass) || isSpockTest(sourceClass));
|
||||
}
|
||||
|
||||
private boolean isJunitTest(Class<?> sourceClass) {
|
||||
for (Method method : sourceClass.getMethods()) {
|
||||
for (Annotation annotation : method.getAnnotations()) {
|
||||
if (annotation.annotationType().getName()
|
||||
.equals(JUNIT_TEST_ANNOTATION)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isSpockTest(Class<?> sourceClass) {
|
||||
return (this.spockSpecificationClass != null && this.spockSpecificationClass
|
||||
.isAssignableFrom(sourceClass));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (this.testClasses.length == 0) {
|
||||
System.out.println("No tests found");
|
||||
}
|
||||
else {
|
||||
Class<?> delegateClass = Thread.currentThread()
|
||||
.getContextClassLoader()
|
||||
.loadClass(DelegateTestRunner.class.getName());
|
||||
Method runMethod = delegateClass.getMethod("run", Class[].class);
|
||||
runMethod.invoke(null, new Object[] { this.testClasses });
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.cli.testrunner;
|
||||
|
||||
import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
|
||||
|
||||
/**
|
||||
* Configuration for {@link TestRunner}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public interface TestRunnerConfiguration extends GroovyCompilerConfiguration {
|
||||
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.cli.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* File utility methods
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class FileUtils {
|
||||
|
||||
private FileUtils() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively deletes the given {@code file} and all files beneath it.
|
||||
* @param file The root of the structure to delete
|
||||
* @throw IllegalStateException if the delete fails
|
||||
*/
|
||||
public static void recursiveDelete(File file) {
|
||||
if (file.exists()) {
|
||||
if (file.isDirectory()) {
|
||||
for (File inDir : file.listFiles()) {
|
||||
recursiveDelete(inDir);
|
||||
}
|
||||
}
|
||||
if (!file.delete()) {
|
||||
throw new IllegalStateException("Failed to delete " + file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists the given {@code file} and all the files beneath it.
|
||||
* @param file The root of the structure to delete
|
||||
* @return The list of files and directories
|
||||
*/
|
||||
public static List<File> recursiveList(File file) {
|
||||
List<File> files = new ArrayList<File>();
|
||||
if (file.isDirectory()) {
|
||||
for (File inDir : file.listFiles()) {
|
||||
files.addAll(recursiveList(inDir));
|
||||
}
|
||||
}
|
||||
files.add(file);
|
||||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the data read from the given {@code source} {@link URL} to the given
|
||||
* {@code target} {@link File}.
|
||||
* @param source The source to copy from
|
||||
* @param target The target to copy to
|
||||
*/
|
||||
public static void copy(URL source, File target) {
|
||||
InputStream input = null;
|
||||
OutputStream output = null;
|
||||
try {
|
||||
input = source.openStream();
|
||||
output = new FileOutputStream(target);
|
||||
IoUtils.copy(input, output);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException("Failed to copy '" + source + "' to '"
|
||||
+ target + "'", ex);
|
||||
}
|
||||
finally {
|
||||
IoUtils.closeQuietly(input, output);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.cli.util;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* General IO utility methods
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class IoUtils {
|
||||
|
||||
private IoUtils() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the entire contents of the resource referenced by {@code uri} and returns
|
||||
* them as a String.
|
||||
* @param uri The resource to read
|
||||
* @return The contents of the resource
|
||||
*/
|
||||
public static String readEntirely(String uri) {
|
||||
try {
|
||||
InputStream stream = URI.create(uri).toURL().openStream();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
|
||||
String line;
|
||||
StringBuilder result = new StringBuilder();
|
||||
while ((line = reader.readLine()) != null) {
|
||||
result.append(line);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the data read from {@code input} into {@code output}.
|
||||
* <p>
|
||||
* <strong>Note:</strong> it is the caller's responsibility to close the streams
|
||||
* @param input The stream to read data from
|
||||
* @param output The stream to write data to
|
||||
* @throws IOException if the copy fails
|
||||
*/
|
||||
public static void copy(InputStream input, OutputStream output) throws IOException {
|
||||
byte[] buffer = new byte[4096];
|
||||
int read;
|
||||
while ((read = input.read(buffer)) >= 0) {
|
||||
output.write(buffer, 0, read);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Quietly closes the given {@link Closeable Closeables}. Any exceptions thrown by
|
||||
* {@link Closeable#close() close()} are swallowed. Any {@code null}
|
||||
* {@code Closeables} are ignored.
|
||||
* @param closeables The {@link Closeable closeables} to close
|
||||
*/
|
||||
public static void closeQuietly(Closeable... closeables) {
|
||||
for (Closeable closeable : closeables) {
|
||||
if (closeable != null) {
|
||||
try {
|
||||
closeable.close();
|
||||
}
|
||||
catch (IOException ioe) {
|
||||
// Closing quietly
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.groovy;
|
||||
|
||||
import org.junit.internal.TextListener;
|
||||
import org.junit.runner.JUnitCore;
|
||||
import org.springframework.boot.cli.testrunner.TestRunner;
|
||||
|
||||
/**
|
||||
* Delegate test runner to launch tests in user application classpath.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @see TestRunner
|
||||
*/
|
||||
public class DelegateTestRunner {
|
||||
|
||||
public static void run(Class<?>[] testClasses) {
|
||||
JUnitCore jUnitCore = new JUnitCore();
|
||||
jUnitCore.addListener(new TextListener(System.out));
|
||||
jUnitCore.run(testClasses);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.groovy;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface EnableDeviceResolver {
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.groovy;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface EnableIntegrationPatterns {
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.groovy;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface EnableJmsMessaging {
|
||||
|
||||
}
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.cli.template;
|
||||
package org.springframework.boot.groovy;
|
||||
|
||||
import groovy.text.GStringTemplateEngine;
|
||||
import groovy.text.Template;
|
||||
|
@ -39,23 +39,25 @@ public abstract class GroovyTemplate {
|
|||
|
||||
public static String template(String name, Map<String, ?> model) throws IOException,
|
||||
CompilationFailedException, ClassNotFoundException {
|
||||
return getTemplate(name).make(model).toString();
|
||||
}
|
||||
|
||||
private static Template getTemplate(String name) throws CompilationFailedException,
|
||||
ClassNotFoundException, IOException {
|
||||
GStringTemplateEngine engine = new GStringTemplateEngine();
|
||||
|
||||
File file = new File("templates", name);
|
||||
URL resource = GroovyTemplate.class.getClassLoader().getResource(
|
||||
"templates/" + name);
|
||||
Template template;
|
||||
if (file.exists()) {
|
||||
template = engine.createTemplate(file);
|
||||
return engine.createTemplate(file);
|
||||
}
|
||||
else {
|
||||
if (resource != null) {
|
||||
template = engine.createTemplate(resource);
|
||||
}
|
||||
else {
|
||||
template = engine.createTemplate(name);
|
||||
}
|
||||
|
||||
ClassLoader classLoader = GroovyTemplate.class.getClassLoader();
|
||||
URL resource = classLoader.getResource("templates/" + name);
|
||||
if (resource != null) {
|
||||
return engine.createTemplate(resource);
|
||||
}
|
||||
return template.make(model).toString();
|
||||
|
||||
return engine.createTemplate(name);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Groovy util classes that are "shared" between the CLI and user applications. Classes is
|
||||
* this package can be loaded from compiled user code. Not under the cli package in case
|
||||
* we want to extract into a separate jar at a future date.
|
||||
*/
|
||||
package org.springframework.boot.groovy;
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.cli;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for CLI Classloader issues.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class ClassLoaderIntegrationTests {
|
||||
|
||||
@Rule
|
||||
public CliTester cli = new CliTester();
|
||||
|
||||
@Test
|
||||
public void runWithIsolatedClassLoader() throws Exception {
|
||||
// CLI classes or dependencies should not be exposed to the app
|
||||
String output = this.cli.run("src/test/resources/classloader-test-app.groovy",
|
||||
SpringCli.class.getName());
|
||||
assertThat(output, containsString("HasClasses-false-true-false"));
|
||||
}
|
||||
}
|
|
@ -27,8 +27,10 @@ import org.junit.rules.TestRule;
|
|||
import org.junit.runner.Description;
|
||||
import org.junit.runners.model.Statement;
|
||||
import org.springframework.boot.OutputCapture;
|
||||
import org.springframework.boot.cli.command.AbstractCommand;
|
||||
import org.springframework.boot.cli.command.CleanCommand;
|
||||
import org.springframework.boot.cli.command.RunCommand;
|
||||
import org.springframework.boot.cli.command.TestCommand;
|
||||
|
||||
/**
|
||||
* {@link TestRule} that can be used to invoke CLI commands.
|
||||
|
@ -41,7 +43,7 @@ public class CliTester implements TestRule {
|
|||
|
||||
private long timeout = TimeUnit.MINUTES.toMillis(6);
|
||||
|
||||
private List<RunCommand> commands = new ArrayList<RunCommand>();
|
||||
private List<AbstractCommand> commands = new ArrayList<AbstractCommand>();
|
||||
|
||||
public void setTimeout(long timeout) {
|
||||
this.timeout = timeout;
|
||||
|
@ -61,6 +63,20 @@ public class CliTester implements TestRule {
|
|||
return getOutput();
|
||||
}
|
||||
|
||||
public String test(final String... args) throws Exception {
|
||||
Future<TestCommand> future = Executors.newSingleThreadExecutor().submit(
|
||||
new Callable<TestCommand>() {
|
||||
@Override
|
||||
public TestCommand call() throws Exception {
|
||||
TestCommand command = new TestCommand();
|
||||
command.run(args);
|
||||
return command;
|
||||
}
|
||||
});
|
||||
this.commands.add(future.get(this.timeout, TimeUnit.MILLISECONDS));
|
||||
return getOutput();
|
||||
}
|
||||
|
||||
public String getOutput() {
|
||||
return this.outputCapture.toString();
|
||||
}
|
||||
|
@ -87,9 +103,9 @@ public class CliTester implements TestRule {
|
|||
this.base.evaluate();
|
||||
}
|
||||
finally {
|
||||
for (RunCommand command : CliTester.this.commands) {
|
||||
if (command != null) {
|
||||
command.stop();
|
||||
for (AbstractCommand command : CliTester.this.commands) {
|
||||
if (command != null && command instanceof RunCommand) {
|
||||
((RunCommand) command).stop();
|
||||
}
|
||||
}
|
||||
System.clearProperty("disableSpringSnapshotRepos");
|
||||
|
|
|
@ -16,14 +16,17 @@
|
|||
|
||||
package org.springframework.boot.cli;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URI;
|
||||
|
||||
import org.codehaus.plexus.util.FileUtils;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.cli.util.FileUtils;
|
||||
import org.springframework.boot.cli.util.IoUtils;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
@ -38,14 +41,14 @@ import static org.junit.Assert.assertTrue;
|
|||
*/
|
||||
public class SampleIntegrationTests {
|
||||
|
||||
@Rule
|
||||
public CliTester cli = new CliTester();
|
||||
|
||||
@BeforeClass
|
||||
public static void cleanGrapes() throws Exception {
|
||||
GrapesCleaner.cleanIfNecessary();
|
||||
}
|
||||
|
||||
@Rule
|
||||
public CliTester cli = new CliTester();
|
||||
|
||||
@Test
|
||||
public void appSample() throws Exception {
|
||||
String output = this.cli.run("samples/app.groovy");
|
||||
|
@ -61,7 +64,6 @@ public class SampleIntegrationTests {
|
|||
@Test
|
||||
public void jobSample() throws Exception {
|
||||
String output = this.cli.run("samples/job.groovy", "foo=bar");
|
||||
System.out.println(output);
|
||||
assertTrue("Wrong output: " + output,
|
||||
output.contains("completed with the following parameters"));
|
||||
}
|
||||
|
@ -83,30 +85,30 @@ public class SampleIntegrationTests {
|
|||
"foo=bar");
|
||||
assertTrue("Wrong output: " + output,
|
||||
output.contains("completed with the following parameters"));
|
||||
String result = IoUtils.readEntirely("http://localhost:8080");
|
||||
String result = readEntirely("http://localhost:8080");
|
||||
assertEquals("World!", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void webSample() throws Exception {
|
||||
this.cli.run("samples/web.groovy");
|
||||
String result = IoUtils.readEntirely("http://localhost:8080");
|
||||
String result = readEntirely("http://localhost:8080");
|
||||
assertEquals("World!", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void uiSample() throws Exception {
|
||||
this.cli.run("samples/ui.groovy", "--classpath=.:src/test/resources");
|
||||
String result = IoUtils.readEntirely("http://localhost:8080");
|
||||
String result = readEntirely("http://localhost:8080");
|
||||
assertTrue("Wrong output: " + result, result.contains("Hello World"));
|
||||
result = IoUtils.readEntirely("http://localhost:8080/css/bootstrap.min.css");
|
||||
result = readEntirely("http://localhost:8080/css/bootstrap.min.css");
|
||||
assertTrue("Wrong output: " + result, result.contains("container"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void actuatorSample() throws Exception {
|
||||
this.cli.run("samples/actuator.groovy");
|
||||
String result = IoUtils.readEntirely("http://localhost:8080");
|
||||
String result = readEntirely("http://localhost:8080");
|
||||
assertEquals("{\"message\":\"Hello World!\"}", result);
|
||||
}
|
||||
|
||||
|
@ -139,7 +141,7 @@ public class SampleIntegrationTests {
|
|||
String output = this.cli.run("samples/jms.groovy");
|
||||
assertTrue("Wrong output: " + output,
|
||||
output.contains("Received Greetings from Spring Boot via ActiveMQ"));
|
||||
FileUtils.recursiveDelete(new File("activemq-data")); // cleanup ActiveMQ cruft
|
||||
FileUtils.deleteDirectory(new File("activemq-data"));// cleanup ActiveMQ cruft
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -154,8 +156,24 @@ public class SampleIntegrationTests {
|
|||
@Test
|
||||
public void deviceSample() throws Exception {
|
||||
this.cli.run("samples/device.groovy");
|
||||
String result = IoUtils.readEntirely("http://localhost:8080");
|
||||
String result = readEntirely("http://localhost:8080");
|
||||
assertEquals("Hello Normal Device!", result);
|
||||
}
|
||||
|
||||
private static String readEntirely(String uri) {
|
||||
try {
|
||||
InputStream stream = URI.create(uri).toURL().openStream();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
|
||||
String line;
|
||||
StringBuilder result = new StringBuilder();
|
||||
while ((line = reader.readLine()) != null) {
|
||||
result.append(line);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,22 +19,29 @@ package org.springframework.boot.cli;
|
|||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.boot.cli.command.CleanCommand;
|
||||
import org.springframework.boot.cli.command.TestCommand;
|
||||
import org.springframework.boot.cli.command.tester.TestResults;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests to exercise the CLI's test command.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class TestCommandIntegrationTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Rule
|
||||
public CliTester cli = new CliTester();
|
||||
|
||||
@BeforeClass
|
||||
public static void cleanGrapes() throws Exception {
|
||||
GrapesCleaner.cleanIfNecessary();
|
||||
|
@ -53,84 +60,53 @@ public class TestCommandIntegrationTests {
|
|||
|
||||
@Test
|
||||
public void noTests() throws Throwable {
|
||||
TestCommand command = new TestCommand();
|
||||
command.run("test-samples/book.groovy");
|
||||
TestResults results = command.getResults();
|
||||
assertEquals(0, results.getRunCount());
|
||||
assertEquals(0, results.getFailureCount());
|
||||
assertTrue(results.wasSuccessful());
|
||||
String output = this.cli.test("test-samples/book.groovy");
|
||||
assertThat(output, containsString("No tests found"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void empty() throws Exception {
|
||||
TestCommand command = new TestCommand();
|
||||
command.run("test-samples/empty.groovy");
|
||||
TestResults results = command.getResults();
|
||||
assertEquals(0, results.getRunCount());
|
||||
assertEquals(0, results.getFailureCount());
|
||||
assertTrue(results.wasSuccessful());
|
||||
String output = this.cli.test("test-samples/empty.groovy");
|
||||
assertThat(output, containsString("No tests found"));
|
||||
}
|
||||
|
||||
@Test(expected = RuntimeException.class)
|
||||
@Test
|
||||
public void noFile() throws Exception {
|
||||
try {
|
||||
TestCommand command = new TestCommand();
|
||||
command.run("test-samples/nothing.groovy");
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
assertEquals("Can't find test-samples/nothing.groovy", ex.getMessage());
|
||||
throw ex;
|
||||
}
|
||||
TestCommand command = new TestCommand();
|
||||
this.thrown.expect(RuntimeException.class);
|
||||
this.thrown.expectMessage("Can't find test-samples/nothing.groovy");
|
||||
command.run("test-samples/nothing.groovy");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void appAndTestsInOneFile() throws Exception {
|
||||
TestCommand command = new TestCommand();
|
||||
command.run("test-samples/book_and_tests.groovy");
|
||||
TestResults results = command.getResults();
|
||||
assertEquals(1, results.getRunCount());
|
||||
assertEquals(0, results.getFailureCount());
|
||||
assertTrue(results.wasSuccessful());
|
||||
String output = this.cli.test("test-samples/book_and_tests.groovy");
|
||||
assertThat(output, containsString("OK (1 test)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void appInOneFileTestsInAnotherFile() throws Exception {
|
||||
TestCommand command = new TestCommand();
|
||||
command.run("test-samples/book.groovy", "test-samples/test.groovy");
|
||||
TestResults results = command.getResults();
|
||||
assertEquals(1, results.getRunCount());
|
||||
assertEquals(0, results.getFailureCount());
|
||||
assertTrue(results.wasSuccessful());
|
||||
String output = this.cli.test("test-samples/book.groovy",
|
||||
"test-samples/test.groovy");
|
||||
assertThat(output, containsString("OK (1 test)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void spockTester() throws Exception {
|
||||
TestCommand command = new TestCommand();
|
||||
command.run("test-samples/spock.groovy");
|
||||
TestResults results = command.getResults();
|
||||
assertEquals(1, results.getRunCount());
|
||||
assertEquals(0, results.getFailureCount());
|
||||
assertTrue(results.wasSuccessful());
|
||||
String output = this.cli.test("test-samples/spock.groovy");
|
||||
assertThat(output, containsString("OK (1 test)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void spockAndJunitTester() throws Exception {
|
||||
TestCommand command = new TestCommand();
|
||||
command.run("test-samples/spock.groovy", "test-samples/book_and_tests.groovy");
|
||||
TestResults results = command.getResults();
|
||||
assertEquals(2, results.getRunCount());
|
||||
assertEquals(0, results.getFailureCount());
|
||||
assertTrue(results.wasSuccessful());
|
||||
String output = this.cli.test("test-samples/spock.groovy",
|
||||
"test-samples/book_and_tests.groovy");
|
||||
assertThat(output, containsString("OK (2 tests)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyFailures() throws Exception {
|
||||
TestCommand command = new TestCommand();
|
||||
command.run("test-samples/failures.groovy");
|
||||
TestResults results = command.getResults();
|
||||
assertEquals(5, results.getRunCount());
|
||||
assertEquals(3, results.getFailureCount());
|
||||
assertFalse(results.wasSuccessful());
|
||||
String output = this.cli.test("test-samples/failures.groovy");
|
||||
assertThat(output, containsString("Tests run: 5, Failures: 3"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.cli;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.internal.TextListener;
|
||||
import org.junit.runner.JUnitCore;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* @author pwebb
|
||||
*/
|
||||
public class TestTest {
|
||||
|
||||
@Test
|
||||
public void testName() throws Exception {
|
||||
fail("Arse");
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
JUnitCore core = new JUnitCore();
|
||||
core.addListener(new TextListener(System.out));
|
||||
core.run(TestTest.class);
|
||||
|
||||
}
|
||||
}
|
|
@ -1,129 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.cli.command;
|
||||
|
||||
import groovy.lang.GroovyObjectSupport;
|
||||
import groovy.lang.Script;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.cli.GrapesCleaner;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* Tests for {@link ScriptCommand}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class ScriptCommandTests {
|
||||
|
||||
public static boolean executed = false;
|
||||
|
||||
@BeforeClass
|
||||
public static void cleanGrapes() throws Exception {
|
||||
GrapesCleaner.cleanIfNecessary();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testMissing() throws Exception {
|
||||
ScriptCommand command = new ScriptCommand("missing");
|
||||
command.run("World");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testScript() throws Exception {
|
||||
ScriptCommand command = new ScriptCommand("script");
|
||||
command.run("World");
|
||||
assertEquals("World",
|
||||
((String[]) ((Script) command.getMain()).getProperty("args"))[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocateFile() throws Exception {
|
||||
ScriptCommand command = new ScriptCommand(
|
||||
"src/test/resources/commands/script.groovy");
|
||||
command.setPaths(new String[] { "." });
|
||||
command.run("World");
|
||||
assertEquals("World",
|
||||
((String[]) ((Script) command.getMain()).getProperty("args"))[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRunnable() throws Exception {
|
||||
ScriptCommand command = new ScriptCommand("runnable");
|
||||
command.run("World");
|
||||
assertTrue(executed);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClosure() throws Exception {
|
||||
ScriptCommand command = new ScriptCommand("closure");
|
||||
command.run("World");
|
||||
assertTrue(executed);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCommand() throws Exception {
|
||||
ScriptCommand command = new ScriptCommand("command");
|
||||
assertEquals("My script command", command.getUsageHelp());
|
||||
command.run("World");
|
||||
assertTrue(executed);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDuplicateClassName() throws Exception {
|
||||
ScriptCommand command1 = new ScriptCommand("handler");
|
||||
ScriptCommand command2 = new ScriptCommand("command");
|
||||
assertNotSame(command1.getMain().getClass(), command2.getMain().getClass());
|
||||
assertEquals(command1.getMain().getClass().getName(), command2.getMain()
|
||||
.getClass().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptions() throws Exception {
|
||||
ScriptCommand command = new ScriptCommand("handler");
|
||||
String out = ((OptionHandler) command.getMain()).getHelp();
|
||||
assertTrue("Wrong output: " + out, out.contains("--foo"));
|
||||
command.run("World", "--foo");
|
||||
assertTrue(executed);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMixin() throws Exception {
|
||||
ScriptCommand command = new ScriptCommand("mixin");
|
||||
GroovyObjectSupport object = (GroovyObjectSupport) command.getMain();
|
||||
String out = (String) object.getProperty("help");
|
||||
assertTrue("Wrong output: " + out, out.contains("--foo"));
|
||||
command.run("World", "--foo");
|
||||
assertTrue(executed);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMixinWithBlock() throws Exception {
|
||||
ScriptCommand command = new ScriptCommand("test");
|
||||
GroovyObjectSupport object = (GroovyObjectSupport) command.getMain();
|
||||
String out = (String) object.getProperty("help");
|
||||
System.err.println(out);
|
||||
assertTrue("Wrong output: " + out, out.contains("--foo"));
|
||||
command.run("World", "--foo", "--bar=2");
|
||||
assertTrue(executed);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.cli.compiler;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import static org.hamcrest.Matchers.sameInstance;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ExtendedGroovyClassLoader}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class ExtendedGroovyClassLoaderTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private ClassLoader contextClassLoader;
|
||||
|
||||
private ExtendedGroovyClassLoader defaultScopeGroovyClassLoader;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.contextClassLoader = Thread.currentThread().getContextClassLoader();
|
||||
this.defaultScopeGroovyClassLoader = new ExtendedGroovyClassLoader(
|
||||
GroovyCompilerScope.DEFAULT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadsGroovyFromSameClassLoader() throws Exception {
|
||||
Class<?> c1 = this.contextClassLoader.loadClass("groovy.lang.Script");
|
||||
Class<?> c2 = this.defaultScopeGroovyClassLoader.loadClass("groovy.lang.Script");
|
||||
assertThat(c1.getClassLoader(), sameInstance(c2.getClassLoader()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void filteresNonGroovy() throws Exception {
|
||||
this.contextClassLoader.loadClass("org.springframework.util.StringUtils");
|
||||
this.thrown.expect(ClassNotFoundException.class);
|
||||
this.defaultScopeGroovyClassLoader
|
||||
.loadClass("org.springframework.util.StringUtils");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadsJavaTypes() throws Exception {
|
||||
this.defaultScopeGroovyClassLoader.loadClass("java.lang.Boolean");
|
||||
}
|
||||
|
||||
}
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.cli.compiler;
|
||||
package org.springframework.boot.cli.compiler.grape;
|
||||
|
||||
import groovy.lang.GroovyClassLoader;
|
||||
|
||||
|
@ -26,6 +26,8 @@ import org.junit.Test;
|
|||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Tests for {@link AetherGrapeEngine}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class AetherGrapeEngineTests {
|
||||
|
@ -33,7 +35,7 @@ public class AetherGrapeEngineTests {
|
|||
private final GroovyClassLoader groovyClassLoader = new GroovyClassLoader();
|
||||
|
||||
private final AetherGrapeEngine grapeEngine = new AetherGrapeEngine(
|
||||
this.groovyClassLoader, null, null, null);
|
||||
this.groovyClassLoader);
|
||||
|
||||
@Test
|
||||
public void dependencyResolution() {
|
||||
|
@ -75,7 +77,7 @@ public class AetherGrapeEngineTests {
|
|||
Map<String, Object> args = new HashMap<String, Object>();
|
||||
System.setProperty("disableSpringSnapshotRepos", "true");
|
||||
try {
|
||||
new AetherGrapeEngine(this.groovyClassLoader, null, null, null).grab(args,
|
||||
new AetherGrapeEngine(this.groovyClassLoader).grab(args,
|
||||
createDependency("org.springframework", "spring-jdbc", "3.2.0.M1"));
|
||||
}
|
||||
finally {
|
|
@ -0,0 +1,13 @@
|
|||
import org.springframework.util.*
|
||||
|
||||
@Component
|
||||
public class Test implements CommandLineRunner {
|
||||
|
||||
public void run(String... args) throws Exception {
|
||||
println "HasClasses-" + ClassUtils.isPresent("missing", null) + "-" +
|
||||
ClassUtils.isPresent("org.springframework.boot.SpringApplication", null) + "-" +
|
||||
ClassUtils.isPresent(args[0], null);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -16,7 +16,6 @@
|
|||
<module>../spring-boot-actuator</module>
|
||||
<module>../spring-boot-starters</module>
|
||||
<module>../spring-boot-cli</module>
|
||||
<module>../spring-boot-cli-grape</module>
|
||||
<module>../spring-boot-samples</module>
|
||||
<module>../spring-boot-integration-tests</module>
|
||||
<module>../spring-boot-javadoc</module>
|
||||
|
|
Loading…
Reference in New Issue