diff --git a/spring-boot-parent/pom.xml b/spring-boot-parent/pom.xml index 4c223f882b..4949c3ba7e 100644 --- a/spring-boot-parent/pom.xml +++ b/spring-boot-parent/pom.xml @@ -224,6 +224,11 @@ gradle-language-jvm ${gradle.version} + + org.gradle + gradle-maven + ${gradle.version} + org.gradle gradle-platform-jvm diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/bundling/BundlingPluginFeatures.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/bundling/BundlingPluginFeatures.java index f49ff1bf90..2e42d7d1be 100644 --- a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/bundling/BundlingPluginFeatures.java +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/bundling/BundlingPluginFeatures.java @@ -20,11 +20,14 @@ import java.util.concurrent.Callable; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.maven.MavenResolver; import org.gradle.api.file.FileCollection; +import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.plugins.WarPlugin; import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.Upload; import org.springframework.boot.gradle.PluginFeatures; @@ -35,17 +38,23 @@ import org.springframework.boot.gradle.PluginFeatures; */ public class BundlingPluginFeatures implements PluginFeatures { + private SinglePublishedArtifact singlePublishedArtifact; + @Override public void apply(Project project) { + this.singlePublishedArtifact = new SinglePublishedArtifact( + project.getConfigurations().create("bootArchives").getArtifacts()); project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> configureBootJarTask(project)); project.getPlugins().withType(WarPlugin.class, (warPlugin) -> configureBootWarTask(project)); + project.afterEvaluate(this::configureBootArchivesUpload); } private void configureBootWarTask(Project project) { BootWar bootWar = project.getTasks().create("bootWar", BootWar.class); bootWar.providedClasspath(providedRuntimeConfiguration(project)); + this.singlePublishedArtifact.addCandidate(new ArchivePublishArtifact(bootWar)); } private void configureBootJarTask(Project project) { @@ -57,6 +66,22 @@ public class BundlingPluginFeatures implements PluginFeatures { .getByName(SourceSet.MAIN_SOURCE_SET_NAME); return mainSourceSet.getRuntimeClasspath(); }); + this.singlePublishedArtifact.addCandidate(new ArchivePublishArtifact(bootJar)); + } + + private void configureBootArchivesUpload(Project project) { + Upload upload = project.getTasks().withType(Upload.class) + .findByName("uploadBootArchives"); + if (upload == null) { + return; + } + clearConfigurationMappings(upload); + } + + private void clearConfigurationMappings(Upload upload) { + upload.getRepositories().withType(MavenResolver.class, (resolver) -> { + resolver.getPom().getScopeMappings().getMappings().clear(); + }); } private Configuration providedRuntimeConfiguration(Project project) { diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/bundling/SinglePublishedArtifact.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/bundling/SinglePublishedArtifact.java new file mode 100644 index 0000000000..eeafb064c3 --- /dev/null +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/bundling/SinglePublishedArtifact.java @@ -0,0 +1,46 @@ +/* + * 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.bundling; + +import org.gradle.api.artifacts.PublishArtifact; +import org.gradle.api.artifacts.PublishArtifactSet; + +/** + * A wrapper for a {@PublishArtifactSet} that ensures that only a single artifact is + * published, with a war file taking precedence over a jar file. + * + * @author Andy Wilkinson + */ +class SinglePublishedArtifact { + + private final PublishArtifactSet artifacts; + + private PublishArtifact currentArtifact; + + SinglePublishedArtifact(PublishArtifactSet artifacts) { + this.artifacts = artifacts; + } + + void addCandidate(PublishArtifact candidate) { + if (this.currentArtifact == null || "war".equals(candidate.getExtension())) { + this.artifacts.clear(); + this.artifacts.add(candidate); + this.currentArtifact = candidate; + } + } + +} diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/bundling/MavenIntegrationTests.java b/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/bundling/MavenIntegrationTests.java new file mode 100644 index 0000000000..1954cc8170 --- /dev/null +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/bundling/MavenIntegrationTests.java @@ -0,0 +1,74 @@ +/* + * 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.bundling; + +import java.io.File; +import java.io.FileNotFoundException; +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 static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for uploading Boot jars and wars using Gradle's Maven plugin. + * + * @author Andy Wilkinson + */ +public class MavenIntegrationTests { + + @Rule + public final GradleBuild gradleBuild = new GradleBuild(); + + @Test + public void bootJarCanBeUploaded() throws FileNotFoundException, IOException { + BuildResult result = this.gradleBuild.build("uploadBootArchives"); + assertThat(result.task(":uploadBootArchives").getOutcome()) + .isEqualTo(TaskOutcome.SUCCESS); + assertThat(artifactWithSuffix("jar")).isFile(); + assertThat(artifactWithSuffix("pom")).is(pomWith().groupId("com.example") + .artifactId(this.gradleBuild.getProjectDir().getName()).version("1.0") + .noPackaging().noDependencies()); + } + + @Test + public void bootWarCanBeUploaded() throws IOException { + BuildResult result = this.gradleBuild.build("uploadBootArchives"); + assertThat(result.task(":uploadBootArchives").getOutcome()) + .isEqualTo(TaskOutcome.SUCCESS); + assertThat(artifactWithSuffix("war")).isFile(); + assertThat(artifactWithSuffix("pom")).is(pomWith().groupId("com.example") + .artifactId(this.gradleBuild.getProjectDir().getName()).version("1.0") + .packaging("war").noDependencies()); + } + + private File artifactWithSuffix(String suffix) { + String name = this.gradleBuild.getProjectDir().getName(); + return new File(new File(this.gradleBuild.getProjectDir(), "build/repo"), + String.format("com/example/%s/1.0/%s-1.0.%s", name, name, suffix)); + } + + private PomCondition pomWith() { + return new PomCondition(); + } + +} diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/bundling/PomCondition.java b/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/bundling/PomCondition.java new file mode 100644 index 0000000000..f05fff957e --- /dev/null +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/bundling/PomCondition.java @@ -0,0 +1,111 @@ +/* + * 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.bundling; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +import org.assertj.core.api.Condition; +import org.assertj.core.description.Description; +import org.assertj.core.description.TextDescription; + +import org.springframework.util.FileCopyUtils; + +/** + * AssertJ {@link Condition} for asserting the contents of a pom file. + * + * @author Andy Wilkinson + */ +class PomCondition extends Condition { + + private Set expectedContents; + + private Set notExpectedContents; + + PomCondition() { + this(new HashSet(), new HashSet()); + } + + private PomCondition(Set expectedContents, Set notExpectedContents) { + super(new TextDescription("Pom file containing %s and not containing %s", + expectedContents, notExpectedContents)); + this.expectedContents = expectedContents; + this.notExpectedContents = notExpectedContents; + } + + @Override + public boolean matches(File pom) { + try { + String contents = FileCopyUtils.copyToString(new FileReader(pom)); + for (String expected : this.expectedContents) { + if (!contents.contains(expected)) { + return false; + } + } + for (String notExpected : this.notExpectedContents) { + if (contents.contains(notExpected)) { + return false; + } + } + } + catch (IOException ex) { + throw new RuntimeException(ex); + } + return true; + } + + @Override + public Description description() { + return new TextDescription("Pom file containing %s and not containing %s", + this.expectedContents, this.notExpectedContents); + } + + PomCondition groupId(String groupId) { + this.expectedContents.add(String.format("%s", groupId)); + return this; + } + + PomCondition artifactId(String artifactId) { + this.expectedContents + .add(String.format("%s", artifactId)); + return this; + } + + PomCondition version(String version) { + this.expectedContents.add(String.format("%s", version)); + return this; + } + + PomCondition packaging(String packaging) { + this.expectedContents.add(String.format("%s", packaging)); + return this; + } + + PomCondition noDependencies() { + this.notExpectedContents.add(""); + return this; + } + + PomCondition noPackaging() { + this.notExpectedContents.add(""); + return this; + } + +} 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 d3bae7f497..078446016d 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 @@ -56,8 +56,7 @@ public class GradleBuild implements TestRule { @Override public Statement apply(Statement base, Description description) { - String name = description.getTestClass().getSimpleName() + ".gradle"; - URL scriptUrl = description.getTestClass().getResource(name); + URL scriptUrl = findDefaultScript(description); if (scriptUrl != null) { script(scriptUrl.getFile()); } @@ -77,6 +76,24 @@ public class GradleBuild implements TestRule { }, description); } + private URL findDefaultScript(Description description) { + URL scriptUrl = getScriptForTestMethod(description); + if (scriptUrl != null) { + return scriptUrl; + } + return getScriptForTestClass(description.getTestClass()); + } + + private URL getScriptForTestMethod(Description description) { + return description.getTestClass() + .getResource(description.getTestClass().getSimpleName() + "-" + + description.getMethodName() + ".gradle"); + } + + private URL getScriptForTestClass(Class testClass) { + return testClass.getResource(testClass.getSimpleName() + ".gradle"); + } + private void before() throws IOException { this.projectDir = this.temp.newFolder(); } @@ -123,7 +140,15 @@ public class GradleBuild implements TestRule { } } - public static String getBootVersion() { + public File getProjectDir() { + return this.projectDir; + } + + public void setProjectDir(File projectDir) { + this.projectDir = projectDir; + } + + private static String getBootVersion() { return evaluateExpression( "/*[local-name()='project']/*[local-name()='parent']/*[local-name()='version']" + "/text()"); diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/bundling/MavenIntegrationTests-bootJarCanBeUploaded.gradle b/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/bundling/MavenIntegrationTests-bootJarCanBeUploaded.gradle new file mode 100644 index 0000000000..145bc8ac02 --- /dev/null +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/bundling/MavenIntegrationTests-bootJarCanBeUploaded.gradle @@ -0,0 +1,24 @@ +buildscript { + dependencies { + classpath files(pluginClasspath.split(',')) + } +} + +apply plugin: 'java' +apply plugin: 'org.springframework.boot' +apply plugin: 'maven' + +bootJar { + mainClass = 'com.example.Application' +} + +group = 'com.example' +version = '1.0' + +uploadBootArchives { + repositories { + mavenDeployer { + repository(url: "file:$buildDir/repo") + } + } +} diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/bundling/MavenIntegrationTests-bootWarCanBeUploaded.gradle b/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/bundling/MavenIntegrationTests-bootWarCanBeUploaded.gradle new file mode 100644 index 0000000000..89c8e25f60 --- /dev/null +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/bundling/MavenIntegrationTests-bootWarCanBeUploaded.gradle @@ -0,0 +1,24 @@ +buildscript { + dependencies { + classpath files(pluginClasspath.split(',')) + } +} + +apply plugin: 'war' +apply plugin: 'org.springframework.boot' +apply plugin: 'maven' + +bootWar { + mainClass = 'com.example.Application' +} + +group = 'com.example' +version = '1.0' + +uploadBootArchives { + repositories { + mavenDeployer { + repository(url: "file:$buildDir/repo") + } + } +}