diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle new file mode 100644 index 0000000..b4e350f --- /dev/null +++ b/buildSrc/build.gradle @@ -0,0 +1,86 @@ +plugins { + id "java-gradle-plugin" + id "java" + id "groovy" +} + +sourceCompatibility = JavaVersion.VERSION_17 + +repositories { + mavenCentral() + gradlePluginPortal() + maven { url 'https://repo.spring.io/plugins-release/' } +} + +sourceSets { + main { + java { + srcDirs = [] + } + groovy { + srcDirs += ["src/main/java"] + } + } +} + +gradlePlugin { + plugins { + managementConfiguration { + id = "io.spring.convention.management-configuration" + implementationClass = "io.spring.gradle.convention.ManagementConfigurationPlugin" + } + sagan { + id = "org.springframework.security.sagan" + implementationClass = "org.springframework.gradle.sagan.SaganPlugin" + } + githubMilestone { + id = "org.springframework.github.milestone" + implementationClass = "org.springframework.gradle.github.milestones.GitHubMilestonePlugin" + } + propdeps { + id = "org.springframework.propdeps" + implementationClass = "org.springframework.gradle.propdeps.PropDepsPlugin" + } + } +} + +configurations { + implementation { + exclude module: 'groovy-all' + } +} + +dependencies { + + implementation 'com.google.code.gson:gson:2.8.8' + implementation 'net.sourceforge.saxon:saxon:9.1.0.8' + implementation localGroovy() + implementation 'io.github.gradle-nexus:publish-plugin:1.1.0' + implementation 'io.spring.gradle:dependency-management-plugin:1.0.10.RELEASE' + implementation 'io.projectreactor:reactor-core:3.4.11' + implementation 'com.apollographql.apollo:apollo-runtime:2.4.5' + implementation 'com.github.ben-manes:gradle-versions-plugin:0.38.0' + implementation 'com.github.spullara.mustache.java:compiler:0.9.10' + implementation 'io.spring.javaformat:spring-javaformat-gradle-plugin:0.0.15' + implementation 'io.spring.nohttp:nohttp-gradle:0.0.9' + implementation 'net.sourceforge.htmlunit:htmlunit:2.55.0' + implementation 'org.hidetake:gradle-ssh-plugin:2.10.1' + implementation 'org.jfrog.buildinfo:build-info-extractor-gradle:4.24.20' + implementation 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1' + + testImplementation platform('org.junit:junit-bom:5.8.1') + testImplementation "org.junit.jupiter:junit-jupiter-api" + testImplementation "org.junit.jupiter:junit-jupiter-params" + testImplementation "org.junit.jupiter:junit-jupiter-engine" + testImplementation 'org.apache.commons:commons-io:1.3.2' + testImplementation 'org.assertj:assertj-core:3.21.0' + testImplementation 'org.mockito:mockito-core:4.0.0' + testImplementation 'org.mockito:mockito-junit-jupiter:3.12.4' + testImplementation 'com.squareup.okhttp3:mockwebserver:3.14.9' + +} + +test { + onlyIf { !project.hasProperty("buildSrc.skipTests") } + useJUnitPlatform() +} diff --git a/buildSrc/src/main/groovy/io/spring/gradle/IncludeRepoTask.groovy b/buildSrc/src/main/groovy/io/spring/gradle/IncludeRepoTask.groovy new file mode 100644 index 0000000..b549ff7 --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/IncludeRepoTask.groovy @@ -0,0 +1,104 @@ +/* + * Copyright 2002-2021 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 + * + * https://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 io.spring.gradle + +import groovy.transform.CompileStatic +import groovy.transform.TypeChecked +import groovy.transform.TypeCheckingMode +import org.gradle.api.DefaultTask +import org.gradle.api.Task +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.TaskAction + +/** + * Checkout a project template from a git repository. + * + * @author Marcus Da Coregio + */ +@CompileStatic +abstract class IncludeRepoTask extends DefaultTask { + + private static final String DEFAULT_URI_PREFIX = 'https://github.com/' + + /** + * Git repository to use. Will be prefixed with {@link #DEFAULT_URI_PREFIX} if it isn't already + * @return + */ + @Input + abstract Property getRepository(); + + /** + * Git reference to use. + */ + @Input + abstract Property getRef() + + /** + * Directory where the project template should be copied. + */ + @OutputDirectory + final File outputDirectory = project.file("$project.buildDir/$name") + + @TaskAction + void checkoutAndCopy() { + outputDirectory.deleteDir() + File checkoutDir = checkout(this, getRemoteUri(), ref.get()) + moveToOutputDir(checkoutDir, outputDirectory) + } + + private static File cleanTemporaryDir(Task task, File tmpDir) { + if (tmpDir.exists()) { + task.project.delete(tmpDir) + } + return tmpDir + } + + static File checkout(Task task, String remoteUri, String ref) { + checkout(task, remoteUri, ref, task.getTemporaryDir()) + } + + @TypeChecked(TypeCheckingMode.SKIP) + static File checkout(Task task, String remoteUri, String ref, File checkoutDir) { + cleanTemporaryDir(task, checkoutDir) + task.project.exec { + commandLine = ["git", "clone", "--no-checkout", remoteUri, checkoutDir.absolutePath] + errorOutput = System.err + } + task.project.exec { + commandLine = ["git", "checkout", ref] + workingDir = checkoutDir + errorOutput = System.err + } + return checkoutDir + } + + private static void moveToOutputDir(File tmpDir, File outputDirectory) { + File baseDir = tmpDir + baseDir.renameTo(outputDirectory) + } + + private String getRemoteUri() { + String remoteUri = this.repository.get() + if (remoteUri.startsWith(DEFAULT_URI_PREFIX)) { + return remoteUri + } + return DEFAULT_URI_PREFIX + remoteUri + } +} diff --git a/buildSrc/src/main/groovy/io/spring/gradle/convention/AbstractSpringJavaPlugin.groovy b/buildSrc/src/main/groovy/io/spring/gradle/convention/AbstractSpringJavaPlugin.groovy new file mode 100644 index 0000000..629bd47 --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/AbstractSpringJavaPlugin.groovy @@ -0,0 +1,73 @@ +/* + * Copyright 2002-2016 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 + * + * https://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 io.spring.gradle.convention; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.plugins.GroovyPlugin; +import org.gradle.api.plugins.JavaPlugin +import org.gradle.api.plugins.PluginManager; +import org.gradle.plugins.ide.eclipse.EclipseWtpPlugin; +import org.gradle.plugins.ide.idea.IdeaPlugin; +import org.springframework.gradle.CopyPropertiesPlugin +import org.springframework.gradle.propdeps.PropDepsEclipsePlugin +import org.springframework.gradle.propdeps.PropDepsIdeaPlugin +import org.springframework.gradle.propdeps.PropDepsPlugin; + +/** + * @author Rob Winch + */ +public abstract class AbstractSpringJavaPlugin implements Plugin { + + @Override + public final void apply(Project project) { + PluginManager pluginManager = project.getPluginManager(); + pluginManager.apply(JavaPlugin.class); + pluginManager.apply(ManagementConfigurationPlugin.class) + if (project.file("src/main/groovy").exists() + || project.file("src/test/groovy").exists() + || project.file("src/integration-test/groovy").exists()) { + pluginManager.apply(GroovyPlugin.class); + } + pluginManager.apply("io.spring.convention.repository"); + pluginManager.apply(EclipseWtpPlugin); + pluginManager.apply(IdeaPlugin); + pluginManager.apply(PropDepsPlugin); + pluginManager.apply(PropDepsEclipsePlugin); + pluginManager.apply(PropDepsIdeaPlugin); + pluginManager.apply("io.spring.convention.tests-configuration"); + pluginManager.apply("io.spring.convention.integration-test"); + pluginManager.apply("io.spring.convention.springdependencymangement"); + pluginManager.apply("io.spring.convention.javadoc-options"); + pluginManager.apply("io.spring.convention.checkstyle"); + pluginManager.apply(CopyPropertiesPlugin); + + project.jar { + manifest.attributes["Created-By"] = + "${System.getProperty("java.version")} (${System.getProperty("java.specification.vendor")})" + manifest.attributes["Implementation-Title"] = project.name + manifest.attributes["Implementation-Version"] = project.version + manifest.attributes["Automatic-Module-Name"] = project.name.replace('-', '.') + } + project.test { + useJUnitPlatform() + } + additionalPlugins(project); + } + + protected abstract void additionalPlugins(Project project); +} diff --git a/buildSrc/src/main/groovy/io/spring/gradle/convention/ArtifactoryPlugin.groovy b/buildSrc/src/main/groovy/io/spring/gradle/convention/ArtifactoryPlugin.groovy new file mode 100644 index 0000000..6e750b5 --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/ArtifactoryPlugin.groovy @@ -0,0 +1,46 @@ +/* + * Copyright 2002-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 + * + * https://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 io.spring.gradle.convention + +import org.gradle.api.Plugin +import org.gradle.api.Project + +class ArtifactoryPlugin implements Plugin { + + @Override + void apply(Project project) { + project.plugins.apply('com.jfrog.artifactory') + String name = Utils.getProjectName(project); + boolean isSnapshot = Utils.isSnapshot(project); + boolean isMilestone = Utils.isMilestone(project); + project.artifactory { + contextUrl = 'https://repo.spring.io' + publish { + repository { + repoKey = isSnapshot ? 'libs-snapshot-local' : isMilestone ? 'libs-milestone-local' : 'libs-release-local' + if(project.hasProperty('artifactoryUsername')) { + username = artifactoryUsername + password = artifactoryPassword + } + } + defaults { + publications('mavenJava') + } + } + } + } +} diff --git a/buildSrc/src/main/groovy/io/spring/gradle/convention/CheckstylePlugin.groovy b/buildSrc/src/main/groovy/io/spring/gradle/convention/CheckstylePlugin.groovy new file mode 100644 index 0000000..0c9fdec --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/CheckstylePlugin.groovy @@ -0,0 +1,49 @@ +/* + * Copyright 2016-2021 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 + * + * https://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 io.spring.gradle.convention + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.plugins.JavaPlugin + +/** + * Adds and configures Checkstyle plugin. + * + * @author Vedran Pavic + */ +class CheckstylePlugin implements Plugin { + + final CHECKSTYLE_DIR = 'etc/checkstyle' + + @Override + void apply(Project project) { + project.plugins.withType(JavaPlugin) { + def checkstyleDir = project.rootProject.file(CHECKSTYLE_DIR) + if (checkstyleDir.exists() && checkstyleDir.directory) { + project.getPluginManager().apply('checkstyle') + project.dependencies.add('checkstyle', 'io.spring.javaformat:spring-javaformat-checkstyle:0.0.29') + project.dependencies.add('checkstyle', 'io.spring.nohttp:nohttp-checkstyle:0.0.3.RELEASE') + + project.checkstyle { + configDirectory = checkstyleDir + toolVersion = '8.21' + } + } + } + } + +} diff --git a/buildSrc/src/main/groovy/io/spring/gradle/convention/DependencyManagementExportTask.groovy b/buildSrc/src/main/groovy/io/spring/gradle/convention/DependencyManagementExportTask.groovy new file mode 100644 index 0000000..4ab559c --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/DependencyManagementExportTask.groovy @@ -0,0 +1,61 @@ +package io.spring.gradle.convention + +import org.gradle.api.Project +import org.gradle.api.artifacts.component.ModuleComponentSelector +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Internal; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Map; +import java.util.Properties; + +import org.gradle.api.DefaultTask; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.tasks.TaskAction; + +import io.spring.gradle.dependencymanagement.dsl.DependencyManagementExtension; + +public class DependencyManagementExportTask extends DefaultTask { + @Internal + def projects; + + @Input + String getProjectNames() { + return projects*.name + } + + @TaskAction + public void dependencyManagementExport() throws IOException { + def projects = this.projects ?: project.subprojects + project + def configurations = projects*.configurations*.findAll { ['testRuntime','integrationTestRuntime','grettyRunnerTomcat8','ajtools'].contains(it.name) } + def dependencyResults = configurations*.incoming*.resolutionResult*.allDependencies.flatten() + def moduleVersionVersions = dependencyResults.findAll { r -> r.requested instanceof ModuleComponentSelector }.collect { r-> r.selected.moduleVersion } + + def projectDependencies = projects.collect { p-> "${p.group}:${p.name}:${p.version}".toString() } as Set + def dependencies = moduleVersionVersions.collect { d -> + "${d.group}:${d.name}:${d.version}".toString() + }.sort() as Set + + println '' + println '' + println 'dependencyManagement {' + println '\tdependencies {' + dependencies.findAll { d-> !projectDependencies.contains(d)}.each { + println "\t\tdependency '$it'" + } + println '\t}' + println '}' + println '' + println '' + println 'TIP Use this to find duplicates:\n$ sort gradle/dependency-management.gradle| uniq -c | grep -v \'^\\s*1\'' + println '' + println '' + } + + void setOutputFile(File file) throws IOException { + this.output = new FileOutputStream(file); + } +} diff --git a/buildSrc/src/main/groovy/io/spring/gradle/convention/DeployDocsPlugin.groovy b/buildSrc/src/main/groovy/io/spring/gradle/convention/DeployDocsPlugin.groovy new file mode 100644 index 0000000..bea5c25 --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/DeployDocsPlugin.groovy @@ -0,0 +1,83 @@ +/* + * Copyright 2002-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 + * + * https://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 io.spring.gradle.convention + +import org.gradle.api.plugins.JavaPlugin +import org.gradle.api.tasks.bundling.Zip +import org.gradle.api.Plugin +import org.gradle.api.Project + +public class DeployDocsPlugin implements Plugin { + + @Override + public void apply(Project project) { + project.getPluginManager().apply('org.hidetake.ssh') + + project.ssh.settings { + knownHosts = allowAnyHosts + } + project.remotes { + docs { + role 'docs' + if (project.hasProperty('deployDocsHost')) { + host = project.findProperty('deployDocsHost') + } else { + host = 'docs.af.pivotal.io' + } + retryCount = 5 // retry 5 times (default is 0) + retryWaitSec = 10 // wait 10 seconds between retries (default is 0) + user = project.findProperty('deployDocsSshUsername') + if (project.hasProperty('deployDocsSshKeyPath')) { + identity = project.file(project.findProperty('deployDocsSshKeyPath')) + } else if (project.hasProperty('deployDocsSshKey')) { + identity = project.findProperty('deployDocsSshKey') + } + if(project.hasProperty('deployDocsSshPassphrase')) { + passphrase = project.findProperty('deployDocsSshPassphrase') + } + } + } + + project.task('deployDocs') { + dependsOn 'docsZip' + doFirst { + project.ssh.run { + session(project.remotes.docs) { + def now = System.currentTimeMillis() + def name = project.rootProject.name + def version = project.rootProject.version + def tempPath = "/tmp/${name}-${now}-docs/".replaceAll(' ', '_') + execute "mkdir -p $tempPath" + + project.tasks.docsZip.outputs.each { o -> + put from: o.files, into: tempPath + } + + execute "unzip $tempPath*.zip -d $tempPath" + + def extractPath = "/var/www/domains/spring.io/docs/htdocs/autorepo/docs/${name}/${version}/" + + execute "rm -rf $extractPath" + execute "mkdir -p $extractPath" + execute "mv $tempPath/docs/* $extractPath" + execute "chmod -R g+w $extractPath" + } + } + } + } + } +} \ No newline at end of file diff --git a/buildSrc/src/main/groovy/io/spring/gradle/convention/DocsPlugin.groovy b/buildSrc/src/main/groovy/io/spring/gradle/convention/DocsPlugin.groovy new file mode 100644 index 0000000..d0a64ab --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/DocsPlugin.groovy @@ -0,0 +1,45 @@ +package io.spring.gradle.convention + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.plugins.BasePlugin +import org.gradle.api.plugins.PluginManager +import org.gradle.api.tasks.bundling.Zip + +/** + * Aggregates asciidoc, javadoc, and deploying of the docs into a single plugin + */ +public class DocsPlugin implements Plugin { + + @Override + public void apply(Project project) { + + PluginManager pluginManager = project.getPluginManager(); + pluginManager.apply(BasePlugin); + pluginManager.apply(DeployDocsPlugin); + pluginManager.apply(JavadocApiPlugin); + + Task docsZip = project.tasks.create('docsZip', Zip) { + dependsOn 'api' + group = 'Distribution' + archiveBaseName = project.rootProject.name + archiveClassifier = 'docs' + description = "Builds -${classifier} archive containing all " + + "Docs for deployment at docs.spring.io" + + from(project.tasks.api.outputs) { + into 'api' + } + into 'docs' + duplicatesStrategy 'exclude' + } + + Task docs = project.tasks.create("docs") { + group = 'Documentation' + description 'An aggregator task to generate all the documentation' + dependsOn docsZip + } + project.tasks.assemble.dependsOn docs + } +} diff --git a/buildSrc/src/main/groovy/io/spring/gradle/convention/IncludeCheckRemotePlugin.groovy b/buildSrc/src/main/groovy/io/spring/gradle/convention/IncludeCheckRemotePlugin.groovy new file mode 100644 index 0000000..5ba6e35 --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/IncludeCheckRemotePlugin.groovy @@ -0,0 +1,65 @@ +/* + * Copyright 2002-2021 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 + * + * https://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 io.spring.gradle.convention + +import io.spring.gradle.IncludeRepoTask +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.provider.Property +import org.gradle.api.tasks.GradleBuild +import org.gradle.api.tasks.TaskProvider + +/** + * Adds a set of tasks that make easy to clone a remote repository and perform some task + * + * @author Marcus Da Coregio + */ +class IncludeCheckRemotePlugin implements Plugin { + @Override + void apply(Project project) { + IncludeCheckRemoteExtension extension = project.extensions.create('includeCheckRemote', IncludeCheckRemoteExtension) + TaskProvider includeRepoTask = project.tasks.register('includeRepo', IncludeRepoTask) { IncludeRepoTask it -> + it.repository = extension.repository + it.ref = extension.ref + } + project.tasks.register('checkRemote', GradleBuild) { + it.dependsOn 'includeRepo' + it.dir = includeRepoTask.get().outputDirectory + it.tasks = extension.getTasks() + } + } + + abstract static class IncludeCheckRemoteExtension { + + /** + * Git repository to clone + */ + String repository; + + /** + * Git ref to checkout + */ + String ref + + /** + * Task to run in the repository + */ + List tasks = ['check'] + + } + +} diff --git a/buildSrc/src/main/groovy/io/spring/gradle/convention/IntegrationTestPlugin.groovy b/buildSrc/src/main/groovy/io/spring/gradle/convention/IntegrationTestPlugin.groovy new file mode 100644 index 0000000..5afd960 --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/IntegrationTestPlugin.groovy @@ -0,0 +1,123 @@ +/* + * Copyright 2016-2018 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 + * + * https://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 io.spring.gradle.convention + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.plugins.GroovyPlugin +import org.gradle.api.plugins.JavaPlugin +import org.gradle.api.tasks.testing.Test +import org.gradle.plugins.ide.eclipse.EclipsePlugin +import org.gradle.plugins.ide.idea.IdeaPlugin +import org.springframework.gradle.propdeps.PropDepsPlugin + +/** + * + * Adds support for integration tests to java projects. + * + *
    + *
  • Adds integrationTestCompile and integrationTestRuntime configurations
  • + *
  • A new source test folder of src/integration-test/java has been added
  • + *
  • A task to run integration tests named integrationTest is added
  • + *
  • If Groovy plugin is added a new source test folder src/integration-test/groovy is added
  • + *
+ * + * @author Rob Winch + */ +public class IntegrationTestPlugin implements Plugin { + + @Override + public void apply(Project project) { + project.plugins.withType(JavaPlugin.class) { + applyJava(project) + } + } + + private applyJava(Project project) { + if(!project.file('src/integration-test/').exists()) { + // ensure we don't add if no tests to avoid adding Gretty + return + } + project.configurations { + integrationTestCompile { + extendsFrom testImplementation + } + integrationTestRuntime { + extendsFrom integrationTestCompile, testRuntime, testRuntimeOnly + } + } + + project.sourceSets { + integrationTest { + java.srcDir project.file('src/integration-test/java') + resources.srcDir project.file('src/integration-test/resources') + compileClasspath = project.sourceSets.main.output + project.sourceSets.test.output + project.configurations.integrationTestCompile + runtimeClasspath = output + compileClasspath + project.configurations.integrationTestRuntime + } + } + + Task integrationTestTask = project.tasks.create("integrationTest", Test) { + group = 'Verification' + description = 'Runs the integration tests.' + dependsOn 'jar' + testClassesDirs = project.sourceSets.integrationTest.output.classesDirs + classpath = project.sourceSets.integrationTest.runtimeClasspath + shouldRunAfter project.tasks.test + useJUnitPlatform() + } + project.tasks.check.dependsOn integrationTestTask + + project.plugins.withType(IdeaPlugin) { + project.idea { + module { + testSourceDirs += project.file('src/integration-test/java') + scopes.TEST.plus += [ project.configurations.integrationTestCompile ] + } + } + } + + project.plugins.withType(GroovyPlugin) { + project.sourceSets { + integrationTest { + groovy.srcDirs project.file('src/integration-test/groovy') + } + } + project.plugins.withType(IdeaPlugin) { + project.idea { + module { + testSourceDirs += project.file('src/integration-test/groovy') + } + } + } + } + + project.plugins.withType(PropDepsPlugin) { + project.configurations { + integrationTestCompile { + extendsFrom optional, provided + } + } + } + + project.plugins.withType(EclipsePlugin) { + project.eclipse.classpath { + plusConfigurations += [ project.configurations.integrationTestCompile ] + } + } + } +} diff --git a/buildSrc/src/main/groovy/io/spring/gradle/convention/JacocoPlugin.groovy b/buildSrc/src/main/groovy/io/spring/gradle/convention/JacocoPlugin.groovy new file mode 100644 index 0000000..900cf9f --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/JacocoPlugin.groovy @@ -0,0 +1,41 @@ +/* + * Copyright 2016-2018 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 + * + * https://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 io.spring.gradle.convention + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.plugins.JavaPlugin + +/** + * Adds a version of jacoco to use and makes check depend on jacocoTestReport. + * + * @author Rob Winch + */ +class JacocoPlugin implements Plugin { + + @Override + void apply(Project project) { + project.plugins.withType(JavaPlugin) { + project.getPluginManager().apply("jacoco") + project.tasks.check.dependsOn project.tasks.jacocoTestReport + + project.jacoco { + toolVersion = '0.8.2' + } + } + } +} diff --git a/buildSrc/src/main/groovy/io/spring/gradle/convention/JavadocApiPlugin.groovy b/buildSrc/src/main/groovy/io/spring/gradle/convention/JavadocApiPlugin.groovy new file mode 100644 index 0000000..ef864b0 --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/JavadocApiPlugin.groovy @@ -0,0 +1,116 @@ +/* + * Copyright 2002-2016 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 + * + * https://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 io.spring.gradle.convention; + +import java.io.File; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Pattern; + +import org.gradle.api.Action; +import org.gradle.api.JavaVersion +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.plugins.JavaPluginConvention; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.javadoc.Javadoc; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Rob Winch + */ +public class JavadocApiPlugin implements Plugin { + Logger logger = LoggerFactory.getLogger(getClass()); + Set excludes = Collections.singleton(Pattern.compile("test")); + + @Override + public void apply(Project project) { + logger.info("Applied"); + Project rootProject = project.getRootProject(); + + + //Task docs = project.getTasks().findByPath("docs") ?: project.getTasks().create("docs"); + Javadoc api = project.getTasks().create("api", Javadoc); + + api.setGroup("Documentation"); + api.setDescription("Generates aggregated Javadoc API documentation."); + api.doLast { + if (JavaVersion.current().isJava11Compatible()) { + project.copy({ copy -> copy + .from(api.destinationDir) + .into(api.destinationDir) + .include("element-list") + .rename("element-list", "package-list") + }); + } + } + + Set subprojects = rootProject.getSubprojects(); + for (Project subproject : subprojects) { + addProject(api, subproject); + } + + if (subprojects.isEmpty()) { + addProject(api, project); + } + + api.setMaxMemory("1024m"); + api.setDestinationDir(new File(project.getBuildDir(), "api")); + + project.getPluginManager().apply("io.spring.convention.javadoc-options"); + } + + public void setExcludes(String... excludes) { + if(excludes == null) { + this.excludes = Collections.emptySet(); + } + this.excludes = new HashSet(excludes.length); + for(String exclude : excludes) { + this.excludes.add(Pattern.compile(exclude)); + } + } + + private void addProject(final Javadoc api, final Project project) { + for(Pattern exclude : excludes) { + if(exclude.matcher(project.getName()).matches()) { + logger.info("Skipping {} because it is excluded by {}", project, exclude); + return; + } + } + logger.info("Try add sources for {}", project); + project.getPlugins().withType(SpringModulePlugin.class).all(new Action() { + @Override + public void execute(SpringModulePlugin plugin) { + logger.info("Added sources for {}", project); + + JavaPluginConvention java = project.getConvention().getPlugin(JavaPluginConvention.class); + SourceSet mainSourceSet = java.getSourceSets().getByName("main"); + + api.setSource(api.getSource().plus(mainSourceSet.getAllJava())); + project.getTasks().withType(Javadoc.class).all(new Action() { + @Override + public void execute(Javadoc projectJavadoc) { + api.setClasspath(api.getClasspath().plus(projectJavadoc.getClasspath())); + } + }); + } + }); + } +} + diff --git a/buildSrc/src/main/groovy/io/spring/gradle/convention/JavadocOptionsPlugin.groovy b/buildSrc/src/main/groovy/io/spring/gradle/convention/JavadocOptionsPlugin.groovy new file mode 100644 index 0000000..e0fef7e --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/JavadocOptionsPlugin.groovy @@ -0,0 +1,15 @@ +package io.spring.gradle.convention + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.tasks.javadoc.Javadoc + +public class JavadocOptionsPlugin implements Plugin { + + @Override + public void apply(Project project) { + project.getTasks().withType(Javadoc).all { t-> + t.options.addStringOption('Xdoclint:none', '-quiet') + } + } +} diff --git a/buildSrc/src/main/groovy/io/spring/gradle/convention/ManagementConfigurationPlugin.java b/buildSrc/src/main/groovy/io/spring/gradle/convention/ManagementConfigurationPlugin.java new file mode 100644 index 0000000..8db6fd9 --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/ManagementConfigurationPlugin.java @@ -0,0 +1,74 @@ +/* + * Copyright 2002-2021 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 + * + * https://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 io.spring.gradle.convention; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.artifacts.ConfigurationContainer; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.plugins.JavaTestFixturesPlugin; +import org.gradle.api.plugins.PluginContainer; +import org.gradle.api.publish.PublishingExtension; +import org.gradle.api.publish.maven.MavenPublication; +import org.gradle.api.publish.maven.plugins.MavenPublishPlugin; + +import org.springframework.gradle.propdeps.PropDepsPlugin; + +/** + * Creates a Management configuration that is appropriate for adding a platform to that is not exposed externally. If + * the JavaPlugin is applied, the compileClasspath, runtimeClasspath, testCompileClasspath, and testRuntimeClasspath + * will extend from it. + * @author Rob Winch + */ +public class ManagementConfigurationPlugin implements Plugin { + + public static final String MANAGEMENT_CONFIGURATION_NAME = "management"; + + @Override + public void apply(Project project) { + ConfigurationContainer configurations = project.getConfigurations(); + configurations.create(MANAGEMENT_CONFIGURATION_NAME, (management) -> { + management.setVisible(false); + management.setCanBeConsumed(false); + management.setCanBeResolved(false); + + PluginContainer plugins = project.getPlugins(); + plugins.withType(JavaPlugin.class, (javaPlugin) -> { + configurations.getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME).extendsFrom(management); + configurations.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME).extendsFrom(management); + configurations.getByName(JavaPlugin.TEST_COMPILE_CLASSPATH_CONFIGURATION_NAME).extendsFrom(management); + configurations.getByName(JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME).extendsFrom(management); + }); + plugins.withType(JavaTestFixturesPlugin.class, (javaTestFixturesPlugin) -> { + configurations.getByName("testFixturesCompileClasspath").extendsFrom(management); + configurations.getByName("testFixturesRuntimeClasspath").extendsFrom(management); + }); + plugins.withType(MavenPublishPlugin.class, (mavenPublish) -> { + PublishingExtension publishing = project.getExtensions().getByType(PublishingExtension.class); + publishing.getPublications().withType(MavenPublication.class, (mavenPublication -> { + mavenPublication.versionMapping((versions) -> + versions.allVariants(versionMapping -> versionMapping.fromResolutionResult()) + ); + })); + }); + plugins.withType(PropDepsPlugin.class, (propDepsPlugin -> { + configurations.getByName("optional").extendsFrom(management); + configurations.getByName("provided").extendsFrom(management); + })); + }); + } +} \ No newline at end of file diff --git a/buildSrc/src/main/groovy/io/spring/gradle/convention/MavenBomPlugin.groovy b/buildSrc/src/main/groovy/io/spring/gradle/convention/MavenBomPlugin.groovy new file mode 100644 index 0000000..fe94e81 --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/MavenBomPlugin.groovy @@ -0,0 +1,18 @@ +package io.spring.gradle.convention + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.plugins.JavaPlatformPlugin +import org.sonarqube.gradle.SonarQubePlugin +import org.springframework.gradle.CopyPropertiesPlugin +import org.springframework.gradle.maven.SpringMavenPlugin + +public class MavenBomPlugin implements Plugin { + static String MAVEN_BOM_TASK_NAME = "mavenBom" + + public void apply(Project project) { + project.plugins.apply(JavaPlatformPlugin) + project.plugins.apply(SpringMavenPlugin) + project.plugins.apply(CopyPropertiesPlugin) + } +} diff --git a/buildSrc/src/main/groovy/io/spring/gradle/convention/RepositoryConventionPlugin.groovy b/buildSrc/src/main/groovy/io/spring/gradle/convention/RepositoryConventionPlugin.groovy new file mode 100644 index 0000000..c242081 --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/RepositoryConventionPlugin.groovy @@ -0,0 +1,84 @@ +/* + * Copyright 2016-2018 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 + * + * https://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 io.spring.gradle.convention; + +import org.gradle.api.Plugin +import org.gradle.api.Project + +class RepositoryConventionPlugin implements Plugin { + + @Override + void apply(Project project) { + String[] forceMavenRepositories = ((String) project.findProperty("forceMavenRepositories"))?.split(',') + boolean isImplicitSnapshotRepository = forceMavenRepositories == null && Utils.isSnapshot(project) + boolean isImplicitMilestoneRepository = forceMavenRepositories == null && Utils.isMilestone(project) + + boolean isSnapshot = isImplicitSnapshotRepository || forceMavenRepositories?.contains('snapshot') + boolean isMilestone = isImplicitMilestoneRepository || forceMavenRepositories?.contains('milestone') + + project.repositories { + if (forceMavenRepositories?.contains('local')) { + mavenLocal() + } + mavenCentral() + jcenter() { + content { + includeGroup "org.gretty" + } + } + if (isSnapshot) { + maven { + name = 'artifactory-snapshot' + if (project.hasProperty('artifactoryUsername')) { + credentials { + username project.artifactoryUsername + password project.artifactoryPassword + } + } + url = 'https://repo.spring.io/snapshot/' + } + } + if (isSnapshot || isMilestone) { + maven { + name = 'artifactory-milestone' + if (project.hasProperty('artifactoryUsername')) { + credentials { + username project.artifactoryUsername + password project.artifactoryPassword + } + } + url = 'https://repo.spring.io/milestone/' + } + } + maven { + name = 'artifactory-release' + if (project.hasProperty('artifactoryUsername')) { + credentials { + username project.artifactoryUsername + password project.artifactoryPassword + } + } + url = 'https://repo.spring.io/release/' + } + maven { + name = 'shibboleth' + url = 'https://build.shibboleth.net/nexus/content/repositories/releases/' + } + } + } + +} diff --git a/buildSrc/src/main/groovy/io/spring/gradle/convention/RootProjectPlugin.groovy b/buildSrc/src/main/groovy/io/spring/gradle/convention/RootProjectPlugin.groovy new file mode 100644 index 0000000..3f7bada --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/RootProjectPlugin.groovy @@ -0,0 +1,70 @@ +/* + * Copyright 2016-2019 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 + * + * https://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 io.spring.gradle.convention + +import io.spring.nohttp.gradle.NoHttpPlugin +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.plugins.BasePlugin +import org.gradle.api.plugins.PluginManager +import org.springframework.gradle.maven.SpringNexusPublishPlugin + +class RootProjectPlugin implements Plugin { + + @Override + void apply(Project project) { + PluginManager pluginManager = project.getPluginManager() + pluginManager.apply(BasePlugin) + pluginManager.apply(SchemaPlugin) + pluginManager.apply(NoHttpPlugin) + pluginManager.apply(SpringNexusPublishPlugin) + pluginManager.apply("org.sonarqube") + + project.repositories.mavenCentral() + + project.allprojects { + configurations.all { + resolutionStrategy { + cacheChangingModulesFor 0, "seconds" + cacheDynamicVersionsFor 0, "seconds" + } + } + } + + String projectName = Utils.getProjectName(project) + project.sonarqube { + properties { + property "sonar.java.coveragePlugin", "jacoco" + property "sonar.projectName", projectName + property "sonar.jacoco.reportPath", "${project.buildDir.name}/jacoco.exec" + property "sonar.links.homepage", "https://spring.io/${projectName}" + property "sonar.links.ci", "https://jenkins.spring.io/job/${projectName}/" + property "sonar.links.issue", "https://github.com/spring-projects/${projectName}/issues" + property "sonar.links.scm", "https://github.com/spring-projects/${projectName}" + property "sonar.links.scm_dev", "https://github.com/spring-projects/${projectName}.git" + } + } + + project.tasks.create("dependencyManagementExport", DependencyManagementExportTask) + + def finalizeDeployArtifacts = project.task("finalizeDeployArtifacts") + if (Utils.isRelease(project) && project.hasProperty("ossrhUsername")) { + finalizeDeployArtifacts.dependsOn project.tasks.closeAndReleaseOssrhStagingRepository + } + } + +} diff --git a/buildSrc/src/main/groovy/io/spring/gradle/convention/SchemaDeployPlugin.groovy b/buildSrc/src/main/groovy/io/spring/gradle/convention/SchemaDeployPlugin.groovy new file mode 100644 index 0000000..4b9c038 --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/SchemaDeployPlugin.groovy @@ -0,0 +1,71 @@ +package io.spring.gradle.convention + +import org.gradle.api.plugins.JavaPlugin +import org.gradle.api.tasks.bundling.Zip +import org.gradle.api.Plugin +import org.gradle.api.Project + +public class SchemaDeployPlugin implements Plugin { + + @Override + public void apply(Project project) { + project.getPluginManager().apply('org.hidetake.ssh') + + project.ssh.settings { + knownHosts = allowAnyHosts + } + project.remotes { + docs { + role 'docs' + if (project.hasProperty('deployDocsHost')) { + host = project.findProperty('deployDocsHost') + } else { + host = 'docs.af.pivotal.io' + } + retryCount = 5 // retry 5 times (default is 0) + retryWaitSec = 10 // wait 10 seconds between retries (default is 0) + user = project.findProperty('deployDocsSshUsername') + if(project.hasProperty('deployDocsSshKeyPath')) { + identity = project.file(project.findProperty('deployDocsSshKeyPath')) + } else if (project.hasProperty('deployDocsSshKey')) { + identity = project.findProperty('deployDocsSshKey') + } + if(project.hasProperty('deployDocsSshPassphrase')) { + passphrase = project.findProperty('deployDocsSshPassphrase') + } + } + } + + project.task('deploySchema') { + dependsOn 'schemaZip' + doFirst { + project.ssh.run { + session(project.remotes.docs) { + def now = System.currentTimeMillis() + def name = project.rootProject.name + def version = project.rootProject.version + def tempPath = "/tmp/${name}-${now}-schema/".replaceAll(' ', '_') + + execute "mkdir -p $tempPath" + + project.tasks.schemaZip.outputs.each { o -> + println "Putting $o.files" + put from: o.files, into: tempPath + } + + execute "unzip $tempPath*.zip -d $tempPath" + + def extractPath = "/var/www/domains/spring.io/docs/htdocs/autorepo/schema/${name}/${version}/" + + execute "rm -rf $extractPath" + execute "mkdir -p $extractPath" + execute "rm -f $tempPath*.zip" + execute "rm -rf $extractPath*" + execute "mv $tempPath/* $extractPath" + execute "chmod -R g+w $extractPath" + } + } + } + } + } +} \ No newline at end of file diff --git a/buildSrc/src/main/groovy/io/spring/gradle/convention/SchemaPlugin.groovy b/buildSrc/src/main/groovy/io/spring/gradle/convention/SchemaPlugin.groovy new file mode 100644 index 0000000..769ae80 --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/SchemaPlugin.groovy @@ -0,0 +1,15 @@ +package io.spring.gradle.convention + +import org.gradle.api.plugins.JavaPlugin +import org.gradle.api.tasks.bundling.Zip +import org.gradle.api.Plugin +import org.gradle.api.Project + +public class SchemaPlugin implements Plugin { + + @Override + public void apply(Project project) { + project.getPluginManager().apply(SchemaZipPlugin) + project.getPluginManager().apply(SchemaDeployPlugin) + } +} \ No newline at end of file diff --git a/buildSrc/src/main/groovy/io/spring/gradle/convention/SchemaZipPlugin.groovy b/buildSrc/src/main/groovy/io/spring/gradle/convention/SchemaZipPlugin.groovy new file mode 100644 index 0000000..72de4b3 --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/SchemaZipPlugin.groovy @@ -0,0 +1,43 @@ +package io.spring.gradle.convention + +import org.gradle.api.plugins.JavaPlugin +import org.gradle.api.tasks.bundling.Zip +import org.gradle.api.Plugin +import org.gradle.api.Project + +public class SchemaZipPlugin implements Plugin { + + @Override + public void apply(Project project) { + Zip schemaZip = project.tasks.create('schemaZip', Zip) + schemaZip.group = 'Distribution' + schemaZip.archiveBaseName = project.rootProject.name + schemaZip.archiveClassifier = 'schema' + schemaZip.description = "Builds -${schemaZip.archiveClassifier} archive containing all " + + "XSDs for deployment at static.springframework.org/schema." + + project.rootProject.subprojects.each { module -> + + module.getPlugins().withType(JavaPlugin.class).all { + def Properties schemas = new Properties(); + + module.sourceSets.main.resources.find { + it.path.endsWith('META-INF/spring.schemas') + }?.withInputStream { schemas.load(it) } + + for (def key : schemas.keySet()) { + def shortName = key.replaceAll(/http.*schema.(.*).spring-.*/, '$1') + assert shortName != key + File xsdFile = module.sourceSets.main.resources.find { + it.path.endsWith(schemas.get(key)) + } + assert xsdFile != null + schemaZip.into (shortName) { + duplicatesStrategy 'exclude' + from xsdFile.path + } + } + } + } + } +} diff --git a/buildSrc/src/main/groovy/io/spring/gradle/convention/SortedProperties.groovy b/buildSrc/src/main/groovy/io/spring/gradle/convention/SortedProperties.groovy new file mode 100644 index 0000000..d2e4467 --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/SortedProperties.groovy @@ -0,0 +1,53 @@ +/* + * Copyright 2002-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 + * + * https://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 io.spring.gradle.convention; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Enumeration; +import java.util.List; +import java.util.Properties; + +/** + * A Properties which sorts they keys so that they can be written to a File with + * the keys sorted. + * + * @author Rob Winch + * + */ +class SortedProperties extends Properties { + private static final long serialVersionUID = -6199017589626540836L; + + public Enumeration keys() { + Enumeration keysEnum = super.keys(); + List keyList = new ArrayList(); + + while (keysEnum.hasMoreElements()) { + keyList.add(keysEnum.nextElement()); + } + + Collections.sort(keyList, new Comparator() { + @Override + public int compare(Object o1, Object o2) { + return o1.toString().compareTo(o2.toString()); + } + }); + + return Collections.enumeration(keyList); + } +} diff --git a/buildSrc/src/main/groovy/io/spring/gradle/convention/SpringDependencyManagementConventionPlugin.groovy b/buildSrc/src/main/groovy/io/spring/gradle/convention/SpringDependencyManagementConventionPlugin.groovy new file mode 100644 index 0000000..9030617 --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/SpringDependencyManagementConventionPlugin.groovy @@ -0,0 +1,54 @@ +/* + * Copyright 2016-2021 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 + * + * https://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 io.spring.gradle.convention + +import io.spring.gradle.dependencymanagement.DependencyManagementPlugin +import org.gradle.api.Plugin +import org.gradle.api.Project + +/** + * Adds and configures {@link DependencyManagementPlugin}. + *

+ * Additionally, if 'gradle/dependency-management.gradle' file is present it will be + * automatically applied file for configuring the dependencies. + */ +class SpringDependencyManagementConventionPlugin implements Plugin { + + static final String DEPENDENCY_MANAGEMENT_RESOURCE = "gradle/dependency-management.gradle" + + @Override + void apply(Project project) { + project.getPluginManager().apply(ManagementConfigurationPlugin) + project.getPluginManager().apply(DependencyManagementPlugin) + project.dependencyManagement { + resolutionStrategy { + cacheChangingModulesFor 0, "seconds" + } + } + File rootDir = project.rootDir + List dependencyManagementFiles = [project.rootProject.file(DEPENDENCY_MANAGEMENT_RESOURCE)] + for (File dir = project.projectDir; dir != rootDir; dir = dir.parentFile) { + dependencyManagementFiles.add(new File(dir, DEPENDENCY_MANAGEMENT_RESOURCE)) + } + dependencyManagementFiles.each { f -> + if (f.exists()) { + project.apply from: f.absolutePath + } + } + } + +} diff --git a/buildSrc/src/main/groovy/io/spring/gradle/convention/SpringModulePlugin.groovy b/buildSrc/src/main/groovy/io/spring/gradle/convention/SpringModulePlugin.groovy new file mode 100644 index 0000000..36a7013 --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/SpringModulePlugin.groovy @@ -0,0 +1,45 @@ +/* + * Copyright 2016-2019 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 + * + * https://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 io.spring.gradle.convention; + +import org.gradle.api.Project +import org.gradle.api.plugins.JavaLibraryPlugin; +import org.gradle.api.plugins.MavenPlugin; +import org.gradle.api.plugins.PluginManager +import org.springframework.gradle.maven.SpringMavenPlugin; + +/** + * @author Rob Winch + */ +class SpringModulePlugin extends AbstractSpringJavaPlugin { + + @Override + void additionalPlugins(Project project) { + PluginManager pluginManager = project.getPluginManager(); + pluginManager.apply(JavaLibraryPlugin.class) + pluginManager.apply(SpringMavenPlugin.class); + pluginManager.apply("io.spring.convention.jacoco"); + + def deployArtifacts = project.task("deployArtifacts") + deployArtifacts.group = 'Deploy tasks' + deployArtifacts.description = "Deploys the artifacts to either Artifactory or Maven Central" + if (!Utils.isRelease(project)) { + deployArtifacts.dependsOn project.tasks.artifactoryPublish + } + } + +} diff --git a/buildSrc/src/main/groovy/io/spring/gradle/convention/SpringSampleBootPlugin.groovy b/buildSrc/src/main/groovy/io/spring/gradle/convention/SpringSampleBootPlugin.groovy new file mode 100644 index 0000000..c79e6b4 --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/SpringSampleBootPlugin.groovy @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2021 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 + * + * https://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 io.spring.gradle.convention + +import org.gradle.api.Project +import org.gradle.api.plugins.PluginManager + +/** + * @author Rob Winch + */ +public class SpringSampleBootPlugin extends SpringSamplePlugin { + + @Override + public void additionalPlugins(Project project) { + super.additionalPlugins(project); + + PluginManager pluginManager = project.getPluginManager(); + + pluginManager.apply("org.springframework.boot"); + + project.repositories { + maven { url 'https://repo.spring.io/snapshot' } + maven { url 'https://repo.spring.io/milestone' } + } + } +} diff --git a/buildSrc/src/main/groovy/io/spring/gradle/convention/SpringSamplePlugin.groovy b/buildSrc/src/main/groovy/io/spring/gradle/convention/SpringSamplePlugin.groovy new file mode 100644 index 0000000..f41a0ca --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/SpringSamplePlugin.groovy @@ -0,0 +1,33 @@ +/* + * Copyright 2002-2021 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 + * + * https://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 io.spring.gradle.convention; + +import org.gradle.api.Project +import org.sonarqube.gradle.SonarQubePlugin; + +/** + * @author Rob Winch + */ +public class SpringSamplePlugin extends AbstractSpringJavaPlugin { + + @Override + public void additionalPlugins(Project project) { + project.plugins.withType(SonarQubePlugin) { + project.sonarqube.skipProject = true + } + } +} diff --git a/buildSrc/src/main/groovy/io/spring/gradle/convention/SpringSampleWarPlugin.groovy b/buildSrc/src/main/groovy/io/spring/gradle/convention/SpringSampleWarPlugin.groovy new file mode 100644 index 0000000..6128f9c --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/SpringSampleWarPlugin.groovy @@ -0,0 +1,98 @@ +/* + * Copyright 2016-2021 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 + * + * https://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 io.spring.gradle.convention + +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.plugins.PluginManager +import org.gradle.api.tasks.testing.Test + +/** + * @author Rob Winch + */ +public class SpringSampleWarPlugin extends SpringSamplePlugin { + + @Override + public void additionalPlugins(Project project) { + super.additionalPlugins(project); + + PluginManager pluginManager = project.getPluginManager(); + + pluginManager.apply("war"); + pluginManager.apply("org.gretty"); + + project.gretty { + servletContainer = 'tomcat85' + contextPath = '/' + fileLogEnabled = false + } + + Task prepareAppServerForIntegrationTests = project.tasks.create('prepareAppServerForIntegrationTests') { + group = 'Verification' + description = 'Prepares the app server for integration tests' + doFirst { + project.gretty { + httpPort = getRandomFreePort() + httpsPort = getRandomPort() + } + } + } + + project.tasks.matching { it.name == "appBeforeIntegrationTest" }.all { task -> + task.dependsOn prepareAppServerForIntegrationTests + } + + project.tasks.withType(Test).all { task -> + if("integrationTest".equals(task.name)) { + applyForIntegrationTest(project, task) + } + } + } + + def applyForIntegrationTest(Project project, Task integrationTest) { + project.gretty.integrationTestTask = integrationTest.name + + integrationTest.doFirst { + def gretty = project.gretty + String host = project.gretty.host ?: 'localhost' + boolean isHttps = gretty.httpsEnabled + Integer httpPort = integrationTest.systemProperties['gretty.httpPort'] + Integer httpsPort = integrationTest.systemProperties['gretty.httpsPort'] + int port = isHttps ? httpsPort : httpPort + String contextPath = project.gretty.contextPath + String httpBaseUrl = "http://${host}:${httpPort}${contextPath}" + String httpsBaseUrl = "https://${host}:${httpsPort}${contextPath}" + String baseUrl = isHttps ? httpsBaseUrl : httpBaseUrl + integrationTest.systemProperty 'app.port', port + integrationTest.systemProperty 'app.httpPort', httpPort + integrationTest.systemProperty 'app.httpsPort', httpsPort + integrationTest.systemProperty 'app.baseURI', baseUrl + integrationTest.systemProperty 'app.httpBaseURI', httpBaseUrl + integrationTest.systemProperty 'app.httpsBaseURI', httpsBaseUrl + + integrationTest.systemProperty 'geb.build.baseUrl', baseUrl + integrationTest.systemProperty 'geb.build.reportsDir', 'build/geb-reports' + } + } + + def getRandomPort() { + ServerSocket ss = new ServerSocket(0) + int port = ss.localPort + ss.close() + return port + } +} diff --git a/buildSrc/src/main/groovy/io/spring/gradle/convention/SpringTestPlugin.groovy b/buildSrc/src/main/groovy/io/spring/gradle/convention/SpringTestPlugin.groovy new file mode 100644 index 0000000..55807dc --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/SpringTestPlugin.groovy @@ -0,0 +1,30 @@ +/* + * Copyright 2002-2016 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 + * + * https://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 io.spring.gradle.convention; + +import org.gradle.api.Project; + +/** + * @author Rob Winch + */ +public class SpringTestPlugin extends AbstractSpringJavaPlugin { + + @Override + public void additionalPlugins(Project project) { + project.sonarqube.skipProject = true + } +} diff --git a/buildSrc/src/main/groovy/io/spring/gradle/convention/TestsConfigurationPlugin.groovy b/buildSrc/src/main/groovy/io/spring/gradle/convention/TestsConfigurationPlugin.groovy new file mode 100644 index 0000000..f8c2ddd --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/TestsConfigurationPlugin.groovy @@ -0,0 +1,55 @@ +/* + * Copyright 2002-2021 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 + * + * https://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 io.spring.gradle.convention; + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.plugins.JavaPlugin +import org.gradle.jvm.tasks.Jar + +/** + * Adds the ability to depends on the test jar within other projects using: + * + * + * testImplementation project(path: ':foo', configuration: 'tests') + * + * + * @author Rob Winch + */ +public class TestsConfigurationPlugin implements Plugin { + @Override + public void apply(Project project) { + project.plugins.withType(JavaPlugin) { + applyJavaProject(project) + } + } + + private void applyJavaProject(Project project) { + project.configurations { + tests.extendsFrom testRuntime, testRuntimeClasspath + } + + project.tasks.create('testJar', Jar) { + classifier = 'test' + from project.sourceSets.test.output + } + + project.artifacts { + tests project.testJar + } + } +} diff --git a/buildSrc/src/main/groovy/io/spring/gradle/convention/Utils.groovy b/buildSrc/src/main/groovy/io/spring/gradle/convention/Utils.groovy new file mode 100644 index 0000000..8f5a6a9 --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/Utils.groovy @@ -0,0 +1,34 @@ +package io.spring.gradle.convention; + +import org.gradle.api.Project; + +public class Utils { + + static String getProjectName(Project project) { + String projectName = project.getRootProject().getName(); + if(projectName.endsWith("-build")) { + projectName = projectName.substring(0, projectName.length() - "-build".length()); + } + return projectName; + } + + static boolean isSnapshot(Project project) { + String projectVersion = projectVersion(project) + return projectVersion.matches('^.*([.-]BUILD)?-SNAPSHOT$') + } + + static boolean isMilestone(Project project) { + String projectVersion = projectVersion(project) + return projectVersion.matches('^.*[.-]M\\d+$') || projectVersion.matches('^.*[.-]RC\\d+$') + } + + static boolean isRelease(Project project) { + return !(isSnapshot(project) || isMilestone(project)) + } + + private static String projectVersion(Project project) { + return String.valueOf(project.getVersion()); + } + + private Utils() {} +} diff --git a/buildSrc/src/main/java/org/springframework/gradle/CopyPropertiesPlugin.java b/buildSrc/src/main/java/org/springframework/gradle/CopyPropertiesPlugin.java new file mode 100644 index 0000000..78819fe --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/CopyPropertiesPlugin.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2021 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 + * + * https://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.gradle; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; + +public class CopyPropertiesPlugin implements Plugin { + @Override + public void apply(Project project) { + copyPropertyFromRootProjectTo("group", project); + copyPropertyFromRootProjectTo("version", project); + copyPropertyFromRootProjectTo("description", project); + } + + + private void copyPropertyFromRootProjectTo(String propertyName, Project project) { + Project rootProject = project.getRootProject(); + Object property = rootProject.findProperty(propertyName); + if(property != null) { + project.setProperty(propertyName, property); + } + } +} diff --git a/buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestoneApi.java b/buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestoneApi.java new file mode 100644 index 0000000..31f1274 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestoneApi.java @@ -0,0 +1,110 @@ +/* + * Copyright 2019-2020 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 + * + * https://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.gradle.github.milestones; + +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; +import okhttp3.Interceptor; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +import java.io.IOException; +import java.util.List; + +public class GitHubMilestoneApi { + private String baseUrl = "https://api.github.com"; + + private OkHttpClient client; + + private Gson gson = new Gson(); + + public GitHubMilestoneApi() { + this.client = new OkHttpClient.Builder().build(); + } + + public GitHubMilestoneApi(String gitHubToken) { + this.client = new OkHttpClient.Builder() + .addInterceptor(new AuthorizationInterceptor(gitHubToken)) + .build(); + } + + public void setBaseUrl(String baseUrl) { + this.baseUrl = baseUrl; + } + + public long findMilestoneNumberByTitle(RepositoryRef repositoryRef, String milestoneTitle) { + String url = this.baseUrl + "/repos/" + repositoryRef.getOwner() + "/" + repositoryRef.getName() + "/milestones?per_page=100"; + Request request = new Request.Builder().get().url(url) + .build(); + try { + Response response = this.client.newCall(request).execute(); + if (!response.isSuccessful()) { + throw new RuntimeException("Could not find milestone with title " + milestoneTitle + " for repository " + repositoryRef + ". Response " + response); + } + List milestones = this.gson.fromJson(response.body().charStream(), new TypeToken>(){}.getType()); + for (Milestone milestone : milestones) { + if (milestoneTitle.equals(milestone.getTitle())) { + return milestone.getNumber(); + } + } + if (milestones.size() <= 100) { + throw new RuntimeException("Could not find open milestone with title " + milestoneTitle + " for repository " + repositoryRef + " Got " + milestones); + } + throw new RuntimeException("It is possible there are too many open milestones open (only 100 are supported). Could not find open milestone with title " + milestoneTitle + " for repository " + repositoryRef + " Got " + milestones); + } catch (IOException e) { + throw new RuntimeException("Could not find open milestone with title " + milestoneTitle + " for repository " + repositoryRef, e); + } + } + + public boolean isOpenIssuesForMilestoneNumber(RepositoryRef repositoryRef, long milestoneNumber) { + String url = this.baseUrl + "/repos/" + repositoryRef.getOwner() + "/" + repositoryRef.getName() + "/issues?per_page=1&milestone=" + milestoneNumber; + Request request = new Request.Builder().get().url(url) + .build(); + try { + Response response = this.client.newCall(request).execute(); + if (!response.isSuccessful()) { + throw new RuntimeException("Could not find issues for milestone number " + milestoneNumber + " for repository " + repositoryRef + ". Response " + response); + } + List issues = this.gson.fromJson(response.body().charStream(), new TypeToken>(){}.getType()); + return !issues.isEmpty(); + } catch (IOException e) { + throw new RuntimeException("Could not find issues for milestone number " + milestoneNumber + " for repository " + repositoryRef, e); + } + } + +// public boolean isOpenIssuesForMilestoneName(String owner, String repository, String milestoneName) { +// +// } + + + private static class AuthorizationInterceptor implements Interceptor { + + private final String token; + + public AuthorizationInterceptor(String token) { + this.token = token; + } + + @Override + public okhttp3.Response intercept(Chain chain) throws IOException { + Request request = chain.request().newBuilder() + .addHeader("Authorization", "Bearer " + this.token).build(); + return chain.proceed(request); + } + } +} diff --git a/buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestoneHasNoOpenIssuesTask.java b/buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestoneHasNoOpenIssuesTask.java new file mode 100644 index 0000000..65f9061 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestoneHasNoOpenIssuesTask.java @@ -0,0 +1,75 @@ +/* + * Copyright 2019-2020 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 + * + * https://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.gradle.github.milestones; + +import org.gradle.api.Action; +import org.gradle.api.DefaultTask; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.Optional; +import org.gradle.api.tasks.TaskAction; + +public class GitHubMilestoneHasNoOpenIssuesTask extends DefaultTask { + @Input + private RepositoryRef repository = new RepositoryRef(); + + @Input + private String milestoneTitle; + + @Input @Optional + private String gitHubAccessToken; + + private GitHubMilestoneApi milestones = new GitHubMilestoneApi(); + + @TaskAction + public void checkHasNoOpenIssues() { + long milestoneNumber = this.milestones.findMilestoneNumberByTitle(this.repository, this.milestoneTitle); + boolean isOpenIssues = this.milestones.isOpenIssuesForMilestoneNumber(this.repository, milestoneNumber); + if (isOpenIssues) { + throw new IllegalStateException("The repository " + this.repository + " has open issues for milestone with the title " + this.milestoneTitle + " and number " + milestoneNumber); + } + System.out.println("The repository " + this.repository + " has no open issues for milestone with the title " + this.milestoneTitle + " and number " + milestoneNumber); + } + + public RepositoryRef getRepository() { + return repository; + } + + public void repository(Action repository) { + repository.execute(this.repository); + } + + public void setRepository(RepositoryRef repository) { + this.repository = repository; + } + + public String getMilestoneTitle() { + return milestoneTitle; + } + + public void setMilestoneTitle(String milestoneTitle) { + this.milestoneTitle = milestoneTitle; + } + + public String getGitHubAccessToken() { + return gitHubAccessToken; + } + + public void setGitHubAccessToken(String gitHubAccessToken) { + this.gitHubAccessToken = gitHubAccessToken; + this.milestones = new GitHubMilestoneApi(gitHubAccessToken); + } +} diff --git a/buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestonePlugin.java b/buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestonePlugin.java new file mode 100644 index 0000000..527b767 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestonePlugin.java @@ -0,0 +1,38 @@ +/* + * Copyright 2019-2020 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 + * + * https://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.gradle.github.milestones; + +import org.gradle.api.Action; +import org.gradle.api.Plugin; +import org.gradle.api.Project; + +public class GitHubMilestonePlugin implements Plugin { + @Override + public void apply(Project project) { + project.getTasks().register("gitHubCheckMilestoneHasNoOpenIssues", GitHubMilestoneHasNoOpenIssuesTask.class, new Action() { + @Override + public void execute(GitHubMilestoneHasNoOpenIssuesTask githubCheckMilestoneHasNoOpenIssues) { + githubCheckMilestoneHasNoOpenIssues.setGroup("Release"); + githubCheckMilestoneHasNoOpenIssues.setDescription("Checks if there are any open issues for the specified repository and milestone"); + githubCheckMilestoneHasNoOpenIssues.setMilestoneTitle((String) project.findProperty("nextVersion")); + if (project.hasProperty("githubAccessToken")) { + githubCheckMilestoneHasNoOpenIssues.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken")); + } + } + }); + } +} diff --git a/buildSrc/src/main/java/org/springframework/gradle/github/milestones/Milestone.java b/buildSrc/src/main/java/org/springframework/gradle/github/milestones/Milestone.java new file mode 100644 index 0000000..5d0ff23 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/github/milestones/Milestone.java @@ -0,0 +1,31 @@ +package org.springframework.gradle.github.milestones; + +public class Milestone { + private String title; + + private long number; + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public long getNumber() { + return number; + } + + public void setNumber(long number) { + this.number = number; + } + + @Override + public String toString() { + return "Milestone{" + + "title='" + title + '\'' + + ", number=" + number + + '}'; + } +} diff --git a/buildSrc/src/main/java/org/springframework/gradle/github/milestones/RepositoryRef.java b/buildSrc/src/main/java/org/springframework/gradle/github/milestones/RepositoryRef.java new file mode 100644 index 0000000..70eec3b --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/github/milestones/RepositoryRef.java @@ -0,0 +1,65 @@ +package org.springframework.gradle.github.milestones; +public class RepositoryRef { + private String owner; + + private String name; + + RepositoryRef() { + } + + public RepositoryRef(String owner, String name) { + this.owner = owner; + this.name = name; + } + + public String getOwner() { + return owner; + } + + public void setOwner(String owner) { + this.owner = owner; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return "RepositoryRef{" + + "owner='" + owner + '\'' + + ", name='" + name + '\'' + + '}'; + } + + public static RepositoryRefBuilder owner(String owner) { + return new RepositoryRefBuilder().owner(owner); + } + + public static final class RepositoryRefBuilder { + private String owner; + private String repository; + + private RepositoryRefBuilder() { + } + + private RepositoryRefBuilder owner(String owner) { + this.owner = owner; + return this; + } + + public RepositoryRefBuilder repository(String repository) { + this.repository = repository; + return this; + } + + public RepositoryRef build() { + return new RepositoryRef(owner, repository); + } + } +} + diff --git a/buildSrc/src/main/java/org/springframework/gradle/maven/MavenPublishingConventionsPlugin.java b/buildSrc/src/main/java/org/springframework/gradle/maven/MavenPublishingConventionsPlugin.java new file mode 100644 index 0000000..8ed94c4 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/maven/MavenPublishingConventionsPlugin.java @@ -0,0 +1,98 @@ +/* + * Copyright 2016-2021 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 + * + * https://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.gradle.maven; + +import org.gradle.api.Action; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.api.publish.PublishingExtension; +import org.gradle.api.publish.maven.MavenPom; +import org.gradle.api.publish.maven.MavenPomDeveloperSpec; +import org.gradle.api.publish.maven.MavenPomIssueManagement; +import org.gradle.api.publish.maven.MavenPomLicenseSpec; +import org.gradle.api.publish.maven.MavenPomOrganization; +import org.gradle.api.publish.maven.MavenPomScm; +import org.gradle.api.publish.maven.MavenPublication; +import org.gradle.api.publish.maven.plugins.MavenPublishPlugin; + +public class MavenPublishingConventionsPlugin implements Plugin { + @Override + public void apply(Project project) { + project.getPlugins().withType(MavenPublishPlugin.class).all(new Action() { + @Override + public void execute(MavenPublishPlugin mavenPublish) { + PublishingExtension publishing = project.getExtensions().getByType(PublishingExtension.class); + publishing.getPublications().withType(MavenPublication.class) + .all((mavenPublication) -> MavenPublishingConventionsPlugin.this.customizePom(mavenPublication.getPom(), project)); + MavenPublishingConventionsPlugin.this.customizeJavaPlugin(project); + } + }); + } + + private void customizePom(MavenPom pom, Project project) { + pom.getUrl().set("https://spring.io/projects/spring-session"); + pom.getName().set(project.provider(project::getName)); + pom.getDescription().set(project.provider(project::getDescription)); + pom.organization(this::customizeOrganization); + pom.licenses(this::customizeLicences); + pom.developers(this::customizeDevelopers); + pom.scm(this::customizeScm); + pom.issueManagement(this::customizeIssueManagement); + } + + private void customizeOrganization(MavenPomOrganization organization) { + organization.getName().set("Pivotal Software, Inc."); + organization.getUrl().set("https://spring.io"); + } + + private void customizeLicences(MavenPomLicenseSpec licences) { + licences.license((licence) -> { + licence.getName().set("Apache License, Version 2.0"); + licence.getUrl().set("https://www.apache.org/licenses/LICENSE-2.0"); + }); + } + + private void customizeDevelopers(MavenPomDeveloperSpec developers) { + developers.developer((developer) -> { + developer.getName().set("Pivotal"); + developer.getEmail().set("info@pivotal.io"); + developer.getOrganization().set("Pivotal Software, Inc."); + developer.getOrganizationUrl().set("https://www.spring.io"); + }); + } + + private void customizeScm(MavenPomScm scm) { + scm.getConnection().set("scm:git:git://github.com/spring-projects/spring-session.git"); + scm.getDeveloperConnection().set("scm:git:ssh://git@github.com/spring-projects/spring-session.git"); + scm.getUrl().set("https://github.com/spring-projects/spring-session"); + } + + private void customizeIssueManagement(MavenPomIssueManagement issueManagement) { + issueManagement.getSystem().set("GitHub"); + issueManagement.getUrl().set("https://github.com/spring-projects/spring-session/issues"); + } + + private void customizeJavaPlugin(Project project) { + project.getPlugins().withType(JavaPlugin.class).all((javaPlugin) -> { + JavaPluginExtension extension = project.getExtensions().getByType(JavaPluginExtension.class); + extension.withJavadocJar(); + extension.withSourcesJar(); + }); + } +} diff --git a/buildSrc/src/main/java/org/springframework/gradle/maven/PublishAllJavaComponentsPlugin.java b/buildSrc/src/main/java/org/springframework/gradle/maven/PublishAllJavaComponentsPlugin.java new file mode 100644 index 0000000..408d83e --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/maven/PublishAllJavaComponentsPlugin.java @@ -0,0 +1,33 @@ +package org.springframework.gradle.maven; + + +import org.gradle.api.Action; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.plugins.JavaPlatformPlugin; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.publish.PublishingExtension; +import org.gradle.api.publish.VariantVersionMappingStrategy; +import org.gradle.api.publish.VersionMappingStrategy; +import org.gradle.api.publish.maven.MavenPublication; +import org.gradle.api.publish.maven.plugins.MavenPublishPlugin; + +public class PublishAllJavaComponentsPlugin implements Plugin { + @Override + public void apply(Project project) { + project.getPlugins().withType(MavenPublishPlugin.class).all((mavenPublish) -> { + PublishingExtension publishing = project.getExtensions().getByType(PublishingExtension.class); + publishing.getPublications().create("mavenJava", MavenPublication.class, new Action() { + @Override + public void execute(MavenPublication maven) { + project.getPlugins().withType(JavaPlugin.class, (plugin) -> { + maven.from(project.getComponents().getByName("java")); + }); + project.getPlugins().withType(JavaPlatformPlugin.class, (plugin) -> { + maven.from(project.getComponents().getByName("javaPlatform")); + }); + } + }); + }); + } +} diff --git a/buildSrc/src/main/java/org/springframework/gradle/maven/PublishArtifactsPlugin.java b/buildSrc/src/main/java/org/springframework/gradle/maven/PublishArtifactsPlugin.java new file mode 100644 index 0000000..ed3985e --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/maven/PublishArtifactsPlugin.java @@ -0,0 +1,26 @@ +package org.springframework.gradle.maven; + +import io.spring.gradle.convention.Utils; +import org.gradle.api.Action; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.Task; + +public class PublishArtifactsPlugin implements Plugin { + @Override + public void apply(Project project) { + project.getTasks().register("publishArtifacts", new Action() { + @Override + public void execute(Task publishArtifacts) { + publishArtifacts.setGroup("Publishing"); + publishArtifacts.setDescription("Publish the artifacts to either Artifactory or Maven Central based on the version"); + if (Utils.isRelease(project)) { + publishArtifacts.dependsOn("publishToOssrh"); + } + else { + publishArtifacts.dependsOn("artifactoryPublish"); + } + } + }); + } +} diff --git a/buildSrc/src/main/java/org/springframework/gradle/maven/PublishLocalPlugin.java b/buildSrc/src/main/java/org/springframework/gradle/maven/PublishLocalPlugin.java new file mode 100644 index 0000000..54f9e49 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/maven/PublishLocalPlugin.java @@ -0,0 +1,29 @@ +package org.springframework.gradle.maven; + +import org.gradle.api.Action; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.artifacts.repositories.MavenArtifactRepository; +import org.gradle.api.publish.PublishingExtension; +import org.gradle.api.publish.maven.plugins.MavenPublishPlugin; + +import java.io.File; + +public class PublishLocalPlugin implements Plugin { + @Override + public void apply(Project project) { + project.getPlugins().withType(MavenPublishPlugin.class).all(new Action() { + @Override + public void execute(MavenPublishPlugin mavenPublish) { + PublishingExtension publishing = project.getExtensions().getByType(PublishingExtension.class); + publishing.getRepositories().maven(new Action() { + @Override + public void execute(MavenArtifactRepository maven) { + maven.setName("local"); + maven.setUrl(new File(project.getRootProject().getBuildDir(), "publications/repos")); + } + }); + } + }); + } +} diff --git a/buildSrc/src/main/java/org/springframework/gradle/maven/SpringMavenPlugin.java b/buildSrc/src/main/java/org/springframework/gradle/maven/SpringMavenPlugin.java new file mode 100644 index 0000000..8b06212 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/maven/SpringMavenPlugin.java @@ -0,0 +1,21 @@ +package org.springframework.gradle.maven; + +import io.spring.gradle.convention.ArtifactoryPlugin; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.plugins.PluginManager; +import org.gradle.api.publish.maven.plugins.MavenPublishPlugin; + +public class SpringMavenPlugin implements Plugin { + @Override + public void apply(Project project) { + PluginManager pluginManager = project.getPluginManager(); + pluginManager.apply(MavenPublishPlugin.class); + pluginManager.apply(SpringSigningPlugin.class); + pluginManager.apply(MavenPublishingConventionsPlugin.class); + pluginManager.apply(PublishAllJavaComponentsPlugin.class); + pluginManager.apply(PublishLocalPlugin.class); + pluginManager.apply(PublishArtifactsPlugin.class); + pluginManager.apply(ArtifactoryPlugin.class); + } +} diff --git a/buildSrc/src/main/java/org/springframework/gradle/maven/SpringNexusPublishPlugin.java b/buildSrc/src/main/java/org/springframework/gradle/maven/SpringNexusPublishPlugin.java new file mode 100644 index 0000000..3002824 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/maven/SpringNexusPublishPlugin.java @@ -0,0 +1,28 @@ +package org.springframework.gradle.maven; + +import io.github.gradlenexus.publishplugin.NexusPublishExtension; +import io.github.gradlenexus.publishplugin.NexusPublishPlugin; +import io.github.gradlenexus.publishplugin.NexusRepository; +import org.gradle.api.Action; +import org.gradle.api.Plugin; +import org.gradle.api.Project; + +import java.net.URI; +import java.time.Duration; + +public class SpringNexusPublishPlugin implements Plugin { + @Override + public void apply(Project project) { + project.getPlugins().apply(NexusPublishPlugin.class); + NexusPublishExtension nexusPublishing = project.getExtensions().findByType(NexusPublishExtension.class); + nexusPublishing.getRepositories().create("ossrh", new Action() { + @Override + public void execute(NexusRepository nexusRepository) { + nexusRepository.getNexusUrl().set(URI.create("https://s01.oss.sonatype.org/service/local/")); + nexusRepository.getSnapshotRepositoryUrl().set(URI.create("https://s01.oss.sonatype.org/content/repositories/snapshots/")); + } + }); + nexusPublishing.getConnectTimeout().set(Duration.ofMinutes(3)); + nexusPublishing.getClientTimeout().set(Duration.ofMinutes(3)); + } +} diff --git a/buildSrc/src/main/java/org/springframework/gradle/maven/SpringSigningPlugin.java b/buildSrc/src/main/java/org/springframework/gradle/maven/SpringSigningPlugin.java new file mode 100644 index 0000000..112651b --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/maven/SpringSigningPlugin.java @@ -0,0 +1,70 @@ +/* + * Copyright 2016-2019 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 + * + * https://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.gradle.maven; + +import org.gradle.api.Action; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.publish.Publication; +import org.gradle.api.publish.PublishingExtension; +import org.gradle.api.publish.plugins.PublishingPlugin; +import org.gradle.plugins.signing.SigningExtension; +import org.gradle.plugins.signing.SigningPlugin; + +import java.util.concurrent.Callable; + +public class SpringSigningPlugin implements Plugin { + @Override + public void apply(Project project) { + project.getPluginManager().apply(SigningPlugin.class); + project.getPlugins().withType(SigningPlugin.class).all(new Action() { + @Override + public void execute(SigningPlugin signingPlugin) { + boolean hasSigningKey = project.hasProperty("signing.keyId") || project.hasProperty("signingKey"); + if (hasSigningKey) { + sign(project); + } + } + }); + } + + private void sign(Project project) { + SigningExtension signing = project.getExtensions().findByType(SigningExtension.class); + signing.setRequired(new Callable() { + @Override + public Boolean call() throws Exception { + return project.getGradle().getTaskGraph().hasTask("publishArtifacts"); + } + }); + String signingKeyId = (String) project.findProperty("signingKeyId"); + String signingKey = (String) project.findProperty("signingKey"); + String signingPassword = (String) project.findProperty("signingPassword"); + if (signingKeyId != null) { + signing.useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword); + } else { + signing.useInMemoryPgpKeys(signingKey, signingPassword); + } + project.getPlugins().withType(PublishAllJavaComponentsPlugin.class).all(new Action() { + @Override + public void execute(PublishAllJavaComponentsPlugin publishingPlugin) { + PublishingExtension publishing = project.getExtensions().findByType(PublishingExtension.class); + Publication maven = publishing.getPublications().getByName("mavenJava"); + signing.sign(maven); + } + }); + } +} diff --git a/buildSrc/src/main/java/org/springframework/gradle/propdeps/PropDepsEclipsePlugin.groovy b/buildSrc/src/main/java/org/springframework/gradle/propdeps/PropDepsEclipsePlugin.groovy new file mode 100644 index 0000000..bf88ca6 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/propdeps/PropDepsEclipsePlugin.groovy @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2021 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 + * + * https://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.gradle.propdeps + + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.plugins.ide.eclipse.EclipsePlugin + +/** + * Plugin to allow optional and provided dependency configurations to work with the + * standard gradle 'eclipse' plugin + * + * @author Phillip Webb + */ +class PropDepsEclipsePlugin implements Plugin { + + public void apply(Project project) { + project.plugins.apply(PropDepsPlugin) + project.plugins.apply(EclipsePlugin) + + project.eclipse { + classpath { + plusConfigurations += [project.configurations.provided, project.configurations.optional] + } + } + } + +} diff --git a/buildSrc/src/main/java/org/springframework/gradle/propdeps/PropDepsIdeaPlugin.groovy b/buildSrc/src/main/java/org/springframework/gradle/propdeps/PropDepsIdeaPlugin.groovy new file mode 100644 index 0000000..4035971 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/propdeps/PropDepsIdeaPlugin.groovy @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2021 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 + * + * https://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.gradle.propdeps + + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.plugins.ide.idea.IdeaPlugin + +/** + * Plugin to allow optional and provided dependency configurations to work with the + * standard gradle 'idea' plugin + * + * @author Phillip Webb + * @author Brian Clozel + * @link https://youtrack.jetbrains.com/issue/IDEA-107046 + * @link https://youtrack.jetbrains.com/issue/IDEA-117668 + */ +class PropDepsIdeaPlugin implements Plugin { + + public void apply(Project project) { + project.plugins.apply(PropDepsPlugin) + project.plugins.apply(IdeaPlugin) + project.idea.module { + // IDEA internally deals with 4 scopes : COMPILE, TEST, PROVIDED, RUNTIME + // but only PROVIDED seems to be picked up + scopes.PROVIDED.plus += [project.configurations.provided] + scopes.PROVIDED.plus += [project.configurations.optional] + } + } + +} diff --git a/buildSrc/src/main/java/org/springframework/gradle/propdeps/PropDepsPlugin.groovy b/buildSrc/src/main/java/org/springframework/gradle/propdeps/PropDepsPlugin.groovy new file mode 100644 index 0000000..e0893e6 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/propdeps/PropDepsPlugin.groovy @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2021 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 + * + * https://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.gradle.propdeps + + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration +import org.gradle.api.plugins.JavaLibraryPlugin +import org.gradle.api.plugins.JavaPlugin +import org.gradle.api.tasks.javadoc.Javadoc + +/** + * Plugin to allow 'optional' and 'provided' dependency configurations + * + * As stated in the maven documentation, provided scope "is only available on the compilation and test classpath, + * and is not transitive". + * + * This plugin creates two new configurations, and each one: + *
    + *
  • is a parent of the compile configuration
  • + *
  • is not visible, not transitive
  • + *
  • all dependencies are excluded from the default configuration
  • + *
+ * + * @author Phillip Webb + * @author Brian Clozel + * @author Rob Winch + * + * @see Maven documentation + * @see Gradle configurations + * @see PropDepsEclipsePlugin + * @see PropDepsIdeaPlugin + */ +class PropDepsPlugin implements Plugin { + + public void apply(Project project) { + project.plugins.apply(JavaPlugin) + + Configuration provided = addConfiguration(project, "provided") + Configuration optional = addConfiguration(project, "optional") + + Javadoc javadoc = project.tasks.getByName(JavaPlugin.JAVADOC_TASK_NAME) + javadoc.classpath = javadoc.classpath.plus(provided).plus(optional) + } + + private Configuration addConfiguration(Project project, String name) { + Configuration configuration = project.configurations.create(name) + configuration.extendsFrom(project.configurations.implementation) + project.plugins.withType(JavaLibraryPlugin, { + configuration.extendsFrom(project.configurations.api) + }) + + project.sourceSets.all { + compileClasspath += configuration + runtimeClasspath += configuration + } + + return configuration + } + +} diff --git a/buildSrc/src/main/java/org/springframework/gradle/sagan/Release.java b/buildSrc/src/main/java/org/springframework/gradle/sagan/Release.java new file mode 100644 index 0000000..5e62c65 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/sagan/Release.java @@ -0,0 +1,123 @@ +/* + * Copyright 2019-2020 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 + * + * https://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.gradle.sagan; + +import java.util.regex.Pattern; + +/** + * Domain object for creating a new release version. + */ +public class Release { + private String version; + + private ReleaseStatus status; + + private boolean current; + + private String referenceDocUrl; + + private String apiDocUrl; + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public ReleaseStatus getStatus() { + return status; + } + + public void setStatus(ReleaseStatus status) { + this.status = status; + } + + public boolean isCurrent() { + return current; + } + + public void setCurrent(boolean current) { + this.current = current; + } + + public String getReferenceDocUrl() { + return referenceDocUrl; + } + + public void setReferenceDocUrl(String referenceDocUrl) { + this.referenceDocUrl = referenceDocUrl; + } + + public String getApiDocUrl() { + return apiDocUrl; + } + + public void setApiDocUrl(String apiDocUrl) { + this.apiDocUrl = apiDocUrl; + } + + @Override + public String toString() { + return "Release{" + + "version='" + version + '\'' + + ", status=" + status + + ", current=" + current + + ", referenceDocUrl='" + referenceDocUrl + '\'' + + ", apiDocUrl='" + apiDocUrl + '\'' + + '}'; + } + + public enum ReleaseStatus { + /** + * Unstable version with limited support + */ + SNAPSHOT, + /** + * Pre-Release version meant to be tested by the community + */ + PRERELEASE, + /** + * Release Generally Available on public artifact repositories and enjoying full support from maintainers + */ + GENERAL_AVAILABILITY; + + private static final Pattern PRERELEASE_PATTERN = Pattern.compile("[A-Za-z0-9\\.\\-]+?(M|RC)\\d+"); + + private static final String SNAPSHOT_SUFFIX = "SNAPSHOT"; + + /** + * Parse the ReleaseStatus from a String + * @param version a project version + * @return the release status for this version + */ + public static ReleaseStatus parse(String version) { + if (version == null) { + throw new IllegalArgumentException("version cannot be null"); + } + if (version.endsWith(SNAPSHOT_SUFFIX)) { + return SNAPSHOT; + } + if (PRERELEASE_PATTERN.matcher(version).matches()) { + return PRERELEASE; + } + return GENERAL_AVAILABILITY; + } + } + +} diff --git a/buildSrc/src/main/java/org/springframework/gradle/sagan/SaganApi.java b/buildSrc/src/main/java/org/springframework/gradle/sagan/SaganApi.java new file mode 100644 index 0000000..24eeb31 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/sagan/SaganApi.java @@ -0,0 +1,93 @@ +/* + * Copyright 2019-2020 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 + * + * https://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.gradle.sagan; + +import com.google.gson.Gson; +import okhttp3.*; + +import java.io.IOException; +import java.util.Base64; + +/** + * Implements necessary calls to the Sagan API See https://spring.io/restdocs/index.html + */ +public class SaganApi { + private String baseUrl = "https://spring.io/api"; + + private OkHttpClient client; + private Gson gson = new Gson(); + + public SaganApi(String gitHubToken) { + this.client = new OkHttpClient.Builder() + .addInterceptor(new BasicInterceptor("not-used", gitHubToken)) + .build(); + } + + public void setBaseUrl(String baseUrl) { + this.baseUrl = baseUrl; + } + + public void createReleaseForProject(Release release, String projectName) { + String url = this.baseUrl + "/projects/" + projectName + "/releases"; + String releaseJsonString = gson.toJson(release); + RequestBody body = RequestBody.create(MediaType.parse("application/json"), releaseJsonString); + Request request = new Request.Builder() + .url(url) + .post(body) + .build(); + try { + Response response = this.client.newCall(request).execute(); + if (!response.isSuccessful()) { + throw new RuntimeException("Could not create release " + release + ". Got response " + response); + } + } catch (IOException fail) { + throw new RuntimeException("Could not create release " + release, fail); + } + } + + public void deleteReleaseForProject(String release, String projectName) { + String url = this.baseUrl + "/projects/" + projectName + "/releases/" + release; + Request request = new Request.Builder() + .url(url) + .delete() + .build(); + try { + Response response = this.client.newCall(request).execute(); + if (!response.isSuccessful()) { + throw new RuntimeException("Could not delete release " + release + ". Got response " + response); + } + } catch (IOException fail) { + throw new RuntimeException("Could not delete release " + release, fail); + } + } + + private static class BasicInterceptor implements Interceptor { + + private final String token; + + public BasicInterceptor(String username, String token) { + this.token = Base64.getEncoder().encodeToString((username + ":" + token).getBytes()); + } + + @Override + public okhttp3.Response intercept(Chain chain) throws IOException { + Request request = chain.request().newBuilder() + .addHeader("Authorization", "Basic " + this.token).build(); + return chain.proceed(request); + } + } +} diff --git a/buildSrc/src/main/java/org/springframework/gradle/sagan/SaganCreateReleaseTask.java b/buildSrc/src/main/java/org/springframework/gradle/sagan/SaganCreateReleaseTask.java new file mode 100644 index 0000000..6592544 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/sagan/SaganCreateReleaseTask.java @@ -0,0 +1,86 @@ +/* + * Copyright 2019-2020 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 + * + * https://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.gradle.sagan; + +import org.gradle.api.DefaultTask; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.TaskAction; + +public class SaganCreateReleaseTask extends DefaultTask { + + @Input + private String gitHubAccessToken; + @Input + private String version; + @Input + private String apiDocUrl; + @Input + private String referenceDocUrl; + @Input + private String projectName; + + @TaskAction + public void saganCreateRelease() { + SaganApi sagan = new SaganApi(this.gitHubAccessToken); + Release release = new Release(); + release.setVersion(this.version); + release.setApiDocUrl(this.apiDocUrl); + release.setReferenceDocUrl(this.referenceDocUrl); + sagan.createReleaseForProject(release, this.projectName); + } + + public String getGitHubAccessToken() { + return gitHubAccessToken; + } + + public void setGitHubAccessToken(String gitHubAccessToken) { + this.gitHubAccessToken = gitHubAccessToken; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getApiDocUrl() { + return apiDocUrl; + } + + public void setApiDocUrl(String apiDocUrl) { + this.apiDocUrl = apiDocUrl; + } + + public String getReferenceDocUrl() { + return referenceDocUrl; + } + + public void setReferenceDocUrl(String referenceDocUrl) { + this.referenceDocUrl = referenceDocUrl; + } + + public String getProjectName() { + return projectName; + } + + public void setProjectName(String projectName) { + this.projectName = projectName; + } + +} diff --git a/buildSrc/src/main/java/org/springframework/gradle/sagan/SaganDeleteReleaseTask.java b/buildSrc/src/main/java/org/springframework/gradle/sagan/SaganDeleteReleaseTask.java new file mode 100644 index 0000000..49a3885 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/sagan/SaganDeleteReleaseTask.java @@ -0,0 +1,62 @@ +/* + * Copyright 2019-2020 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 + * + * https://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.gradle.sagan; + +import org.gradle.api.DefaultTask; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.TaskAction; + +public class SaganDeleteReleaseTask extends DefaultTask { + + @Input + private String gitHubAccessToken; + @Input + private String version; + @Input + private String projectName; + + @TaskAction + public void saganCreateRelease() { + SaganApi sagan = new SaganApi(this.gitHubAccessToken); + sagan.deleteReleaseForProject(this.version, this.projectName); + } + + public String getGitHubAccessToken() { + return gitHubAccessToken; + } + + public void setGitHubAccessToken(String gitHubAccessToken) { + this.gitHubAccessToken = gitHubAccessToken; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getProjectName() { + return projectName; + } + + public void setProjectName(String projectName) { + this.projectName = projectName; + } + +} diff --git a/buildSrc/src/main/java/org/springframework/gradle/sagan/SaganPlugin.java b/buildSrc/src/main/java/org/springframework/gradle/sagan/SaganPlugin.java new file mode 100644 index 0000000..388a2d1 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/sagan/SaganPlugin.java @@ -0,0 +1,47 @@ +/* + * Copyright 2019-2020 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 + * + * https://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.gradle.sagan; + +import io.spring.gradle.convention.Utils; +import org.gradle.api.*; + +public class SaganPlugin implements Plugin { + @Override + public void apply(Project project) { + project.getTasks().register("saganCreateRelease", SaganCreateReleaseTask.class, new Action() { + @Override + public void execute(SaganCreateReleaseTask saganCreateVersion) { + saganCreateVersion.setGroup("Release"); + saganCreateVersion.setDescription("Creates a new version for the specified project on spring.io"); + saganCreateVersion.setVersion((String) project.findProperty("nextVersion")); + saganCreateVersion.setProjectName(Utils.getProjectName(project)); + saganCreateVersion.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken")); + } + }); + project.getTasks().register("saganDeleteRelease", SaganDeleteReleaseTask.class, new Action() { + @Override + public void execute(SaganDeleteReleaseTask saganDeleteVersion) { + saganDeleteVersion.setGroup("Release"); + saganDeleteVersion.setDescription("Delete a version for the specified project on spring.io"); + saganDeleteVersion.setVersion((String) project.findProperty("previousVersion")); + saganDeleteVersion.setProjectName(Utils.getProjectName(project)); + saganDeleteVersion.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken")); + } + }); + } + +} diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.artifactory.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.artifactory.properties new file mode 100644 index 0000000..573d128 --- /dev/null +++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.artifactory.properties @@ -0,0 +1 @@ +implementation-class=io.spring.gradle.convention.ArtifactoryPlugin \ No newline at end of file diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.bom.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.bom.properties new file mode 100644 index 0000000..dec2e7a --- /dev/null +++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.bom.properties @@ -0,0 +1 @@ +implementation-class=io.spring.gradle.convention.MavenBomPlugin \ No newline at end of file diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.checkstyle.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.checkstyle.properties new file mode 100644 index 0000000..9259a91 --- /dev/null +++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.checkstyle.properties @@ -0,0 +1 @@ +implementation-class=io.spring.gradle.convention.CheckstylePlugin diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.docs.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.docs.properties new file mode 100644 index 0000000..fcad41a --- /dev/null +++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.docs.properties @@ -0,0 +1 @@ +implementation-class=io.spring.gradle.convention.DocsPlugin \ No newline at end of file diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.include-check-remote.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.include-check-remote.properties new file mode 100644 index 0000000..298e1bb --- /dev/null +++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.include-check-remote.properties @@ -0,0 +1 @@ +implementation-class=io.spring.gradle.convention.IncludeCheckRemotePlugin diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.integration-test.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.integration-test.properties new file mode 100644 index 0000000..0da39b3 --- /dev/null +++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.integration-test.properties @@ -0,0 +1 @@ +implementation-class=io.spring.gradle.convention.IntegrationTestPlugin \ No newline at end of file diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.jacoco.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.jacoco.properties new file mode 100644 index 0000000..70cfb7d --- /dev/null +++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.jacoco.properties @@ -0,0 +1 @@ +implementation-class=io.spring.gradle.convention.JacocoPlugin \ No newline at end of file diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.javadoc-api.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.javadoc-api.properties new file mode 100644 index 0000000..bfb9230 --- /dev/null +++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.javadoc-api.properties @@ -0,0 +1 @@ +implementation-class=io.spring.gradle.convention.JavadocApiPlugin \ No newline at end of file diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.javadoc-options.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.javadoc-options.properties new file mode 100644 index 0000000..77e7832 --- /dev/null +++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.javadoc-options.properties @@ -0,0 +1 @@ +implementation-class=io.spring.gradle.convention.JavadocOptionsPlugin \ No newline at end of file diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.repository.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.repository.properties new file mode 100644 index 0000000..b427d55 --- /dev/null +++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.repository.properties @@ -0,0 +1 @@ +implementation-class=io.spring.gradle.convention.RepositoryConventionPlugin \ No newline at end of file diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.root.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.root.properties new file mode 100644 index 0000000..9844db6 --- /dev/null +++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.root.properties @@ -0,0 +1 @@ +implementation-class=io.spring.gradle.convention.RootProjectPlugin \ No newline at end of file diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.spring-module.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.spring-module.properties new file mode 100644 index 0000000..124dd85 --- /dev/null +++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.spring-module.properties @@ -0,0 +1 @@ +implementation-class=io.spring.gradle.convention.SpringModulePlugin \ No newline at end of file diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.spring-sample-boot.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.spring-sample-boot.properties new file mode 100644 index 0000000..4ecd703 --- /dev/null +++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.spring-sample-boot.properties @@ -0,0 +1 @@ +implementation-class=io.spring.gradle.convention.SpringSampleBootPlugin diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.spring-sample-war.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.spring-sample-war.properties new file mode 100644 index 0000000..367e018 --- /dev/null +++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.spring-sample-war.properties @@ -0,0 +1 @@ +implementation-class=io.spring.gradle.convention.SpringSampleWarPlugin diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.spring-sample.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.spring-sample.properties new file mode 100644 index 0000000..77cb21d --- /dev/null +++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.spring-sample.properties @@ -0,0 +1 @@ +implementation-class=io.spring.gradle.convention.SpringSamplePlugin diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.spring-test.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.spring-test.properties new file mode 100644 index 0000000..4881e2d --- /dev/null +++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.spring-test.properties @@ -0,0 +1 @@ +implementation-class=io.spring.gradle.convention.SpringTestPlugin \ No newline at end of file diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.springdependencymangement.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.springdependencymangement.properties new file mode 100644 index 0000000..10b8580 --- /dev/null +++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.springdependencymangement.properties @@ -0,0 +1 @@ +implementation-class=io.spring.gradle.convention.SpringDependencyManagementConventionPlugin diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.tests-configuration.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.tests-configuration.properties new file mode 100644 index 0000000..afd0a71 --- /dev/null +++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.tests-configuration.properties @@ -0,0 +1 @@ +implementation-class=io.spring.gradle.convention.TestsConfigurationPlugin \ No newline at end of file