diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/SpringBootPlugin.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/SpringBootPlugin.java index d2663fa49d..ba5ed87807 100644 --- a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/SpringBootPlugin.java +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/SpringBootPlugin.java @@ -78,6 +78,14 @@ public class SpringBootPlugin implements Plugin { project.getPlugins().withType(action.getPluginClass(), plugin -> action.execute(project)); } + UnresolvedDependenciesAnalyzer unresolvedDependenciesAnalyzer = new UnresolvedDependenciesAnalyzer(); + project.getConfigurations().all(configuration -> configuration.getIncoming() + .afterResolve(resolvableDependencies -> unresolvedDependenciesAnalyzer + .analyze(configuration.getResolvedConfiguration() + .getLenientConfiguration() + .getUnresolvedModuleDependencies()))); + project.getGradle().buildFinished( + buildResult -> unresolvedDependenciesAnalyzer.buildFinished(project)); } } diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/UnresolvedDependenciesAnalyzer.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/UnresolvedDependenciesAnalyzer.java new file mode 100644 index 0000000000..621d01d45b --- /dev/null +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/UnresolvedDependenciesAnalyzer.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012-2017 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.gradle.plugin; + +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +import io.spring.gradle.dependencymanagement.DependencyManagementPlugin; +import org.gradle.api.Project; +import org.gradle.api.artifacts.ModuleVersionSelector; +import org.gradle.api.artifacts.UnresolvedDependency; +import org.slf4j.LoggerFactory; + +/** + * An analyzer for {@link UnresolvedDependency unresolvable dependencies} that logs a + * warning suggesting that the {@code io.spring.dependency-management} plugin is applied + * when one or more versionless dependencies fails to resolve. + * + * @author Andy Wilkinson + */ +class UnresolvedDependenciesAnalyzer { + + private static final org.slf4j.Logger logger = LoggerFactory + .getLogger(SpringBootPlugin.class); + + private Set dependenciesWithNoVersion = new HashSet<>(); + + public void analyze(Set unresolvedDependencies) { + this.dependenciesWithNoVersion = unresolvedDependencies.stream() + .map(unresolvedDependency -> unresolvedDependency.getSelector()) + .filter(this::hasNoVersion).collect(Collectors.toSet()); + } + + void buildFinished(Project project) { + if (!this.dependenciesWithNoVersion.isEmpty() + && !project.getPlugins().hasPlugin(DependencyManagementPlugin.class)) { + StringBuilder message = new StringBuilder(); + message.append("\nDuring the build, one or more dependencies that were " + + "declared without a version failed to resolve:\n"); + this.dependenciesWithNoVersion.stream() + .forEach(dependency -> message.append(" " + dependency + "\n")); + message.append("\nDid you forget to apply the " + + "io.spring.dependency-management plugin to the " + project.getName() + + " project?\n"); + logger.warn(message.toString()); + } + } + + private boolean hasNoVersion(ModuleVersionSelector selector) { + String version = selector.getVersion(); + return version == null || version.trim().length() == 0; + } + +} diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/DependencyManagementPluginActionIntegrationTests.java b/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/DependencyManagementPluginActionIntegrationTests.java index 42b5e87f35..b22c0a718d 100644 --- a/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/DependencyManagementPluginActionIntegrationTests.java +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/DependencyManagementPluginActionIntegrationTests.java @@ -16,11 +16,16 @@ package org.springframework.boot.gradle.plugin; +import java.io.File; +import java.io.IOException; + +import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.TaskOutcome; import org.junit.Rule; import org.junit.Test; import org.springframework.boot.gradle.testkit.GradleBuild; +import org.springframework.util.FileSystemUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -50,4 +55,23 @@ public class DependencyManagementPluginActionIntegrationTests { .isEqualTo(TaskOutcome.SUCCESS); } + @Test + public void helpfulErrorWhenVersionlessDependencyFailsToResolve() throws IOException { + File examplePackage = new File(this.gradleBuild.getProjectDir(), + "src/main/java/com/example"); + examplePackage.mkdirs(); + FileSystemUtils.copyRecursively(new File("src/test/java/com/example"), + examplePackage); + BuildResult result = this.gradleBuild.buildAndFail("compileJava"); + assertThat(result.task(":compileJava").getOutcome()) + .isEqualTo(TaskOutcome.FAILED); + String output = result.getOutput(); + assertThat(output).contains("During the build, one or more dependencies that " + + "were declared without a version failed to resolve:"); + assertThat(output).contains("org.springframework.boot:spring-boot-starter-web:"); + assertThat(output).contains("Did you forget to apply the " + + "io.spring.dependency-management plugin to the " + + this.gradleBuild.getProjectDir().getName() + " project?"); + } + } diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/testkit/GradleBuild.java b/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/testkit/GradleBuild.java index 7d5f5eb6c0..cc8b8cb389 100644 --- a/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/testkit/GradleBuild.java +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/testkit/GradleBuild.java @@ -126,22 +126,35 @@ public class GradleBuild implements TestRule { public BuildResult build(String... arguments) { try { - Files.copy(new File(this.script).toPath(), - new File(this.projectDir, "build.gradle").toPath(), - StandardCopyOption.REPLACE_EXISTING); - GradleRunner gradleRunner = GradleRunner.create() - .withProjectDir(this.projectDir).forwardOutput(); - List allArguments = new ArrayList(); - allArguments.add("-PpluginClasspath=" + pluginClasspath()); - allArguments.add("-PbootVersion=" + getBootVersion()); - allArguments.addAll(Arrays.asList(arguments)); - return gradleRunner.withArguments(allArguments).build(); + return prepareRunner(arguments).build(); } catch (Exception ex) { throw new RuntimeException(ex); } } + public BuildResult buildAndFail(String... arguments) { + try { + return prepareRunner(arguments).buildAndFail(); + } + catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + public GradleRunner prepareRunner(String... arguments) throws IOException { + Files.copy(new File(this.script).toPath(), + new File(this.projectDir, "build.gradle").toPath(), + StandardCopyOption.REPLACE_EXISTING); + GradleRunner gradleRunner = GradleRunner.create().withProjectDir(this.projectDir) + .forwardOutput(); + List allArguments = new ArrayList(); + allArguments.add("-PpluginClasspath=" + pluginClasspath()); + allArguments.add("-PbootVersion=" + getBootVersion()); + allArguments.addAll(Arrays.asList(arguments)); + return gradleRunner.withArguments(allArguments); + } + public File getProjectDir() { return this.projectDir; } diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/DependencyManagementPluginActionIntegrationTests-helpfulErrorWhenVersionlessDependencyFailsToResolve.gradle b/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/DependencyManagementPluginActionIntegrationTests-helpfulErrorWhenVersionlessDependencyFailsToResolve.gradle new file mode 100644 index 0000000000..2aeaadbc4a --- /dev/null +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/DependencyManagementPluginActionIntegrationTests-helpfulErrorWhenVersionlessDependencyFailsToResolve.gradle @@ -0,0 +1,16 @@ +buildscript { + dependencies { + classpath files(pluginClasspath.split(',')) + } +} + +apply plugin: 'java' +apply plugin: 'org.springframework.boot' + +repositories { + mavenLocal() +} + +dependencies { + compile 'org.springframework.boot:spring-boot-starter-web' +}