From 84fab2e2a9ab3246e86f7549d27d73f5755dc51a Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Fri, 29 Oct 2021 13:30:29 +0200 Subject: [PATCH] Add buildSrc including build conventions plugins Closes gh-1942 --- build.gradle | 5 +- buildSrc/build.gradle | 84 +++ buildSrc/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59203 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + .../io/spring/gradle/IncludeRepoTask.groovy | 104 +++ .../AbstractSpringJavaPlugin.groovy | 73 ++ .../convention/ArtifactoryPlugin.groovy | 45 ++ .../gradle/convention/CheckstylePlugin.groovy | 49 ++ .../DependencyManagementExportTask.groovy | 61 ++ .../gradle/convention/DeployDocsPlugin.groovy | 82 +++ .../gradle/convention/DocsPlugin.groovy | 45 ++ .../IncludeCheckRemotePlugin.groovy | 65 ++ .../convention/IntegrationTestPlugin.groovy | 122 ++++ .../gradle/convention/JacocoPlugin.groovy | 41 ++ .../gradle/convention/JavadocApiPlugin.groovy | 116 +++ .../convention/JavadocOptionsPlugin.groovy | 15 + .../ManagementConfigurationPlugin.java | 74 ++ .../gradle/convention/MavenBomPlugin.groovy | 18 + .../RepositoryConventionPlugin.groovy | 84 +++ .../convention/RootProjectPlugin.groovy | 70 ++ .../convention/SchemaDeployPlugin.groovy | 71 ++ .../gradle/convention/SchemaPlugin.groovy | 15 + .../gradle/convention/SchemaZipPlugin.groovy | 43 ++ .../gradle/convention/SortedProperties.groovy | 52 ++ ...ependencyManagementConventionPlugin.groovy | 54 ++ .../convention/SpringModulePlugin.groovy | 45 ++ .../convention/SpringSampleBootPlugin.groovy | 40 + .../convention/SpringSamplePlugin.groovy | 33 + .../convention/SpringSampleWarPlugin.groovy | 98 +++ .../gradle/convention/SpringTestPlugin.groovy | 30 + .../TestsConfigurationPlugin.groovy | 54 ++ .../io/spring/gradle/convention/Utils.groovy | 34 + .../gradle/CopyPropertiesPlugin.java | 38 + .../github/milestones/GitHubMilestoneApi.java | 110 +++ .../GitHubMilestoneHasNoOpenIssuesTask.java | 74 ++ .../milestones/GitHubMilestonePlugin.java | 38 + .../gradle/github/milestones/Milestone.java | 31 + .../github/milestones/RepositoryRef.java | 65 ++ .../MavenPublishingConventionsPlugin.java | 82 +++ .../maven/PublishAllJavaComponentsPlugin.java | 33 + .../gradle/maven/PublishArtifactsPlugin.java | 26 + .../gradle/maven/PublishLocalPlugin.java | 29 + .../gradle/maven/SpringMavenPlugin.java | 21 + .../maven/SpringNexusPublishPlugin.java | 28 + .../gradle/maven/SpringSigningPlugin.java | 70 ++ .../propdeps/PropDepsEclipsePlugin.groovy | 43 ++ .../gradle/propdeps/PropDepsIdeaPlugin.groovy | 46 ++ .../gradle/propdeps/PropDepsPlugin.groovy | 76 ++ .../springframework/gradle/sagan/Release.java | 123 ++++ .../gradle/sagan/SaganApi.java | 93 +++ .../gradle/sagan/SaganCreateReleaseTask.java | 86 +++ .../gradle/sagan/SaganDeleteReleaseTask.java | 62 ++ .../gradle/sagan/SaganPlugin.java | 47 ++ ...o.spring.convention.artifactory.properties | 1 + .../io.spring.convention.bom.properties | 1 + ...io.spring.convention.checkstyle.properties | 1 + .../io.spring.convention.docs.properties | 1 + ...convention.include-check-remote.properties | 1 + ...ing.convention.integration-test.properties | 1 + .../io.spring.convention.jacoco.properties | 1 + ...o.spring.convention.javadoc-api.properties | 1 + ...ring.convention.javadoc-options.properties | 1 + ...io.spring.convention.repository.properties | 1 + .../io.spring.convention.root.properties | 1 + ...spring.convention.spring-module.properties | 1 + ...g.convention.spring-sample-boot.properties | 1 + ...ng.convention.spring-sample-war.properties | 1 + ...spring.convention.spring-sample.properties | 1 + ...o.spring.convention.spring-test.properties | 1 + ...ntion.springdependencymangement.properties | 1 + ....convention.tests-configuration.properties | 1 + .../test/java/io/spring/gradle/TestKit.java | 58 ++ .../IncludeCheckRemotePluginTest.java | 111 +++ .../convention/IntegrationPluginTest.java | 52 ++ .../IntegrationTestPluginITest.java | 52 ++ .../gradle/convention/JacocoPluginITest.java | 31 + .../convention/JavadocApiPluginITest.java | 38 + .../convention/JavadocApiPluginTest.java | 56 ++ .../RepositoryConventionPluginTests.java | 158 ++++ .../gradle/convention/ShowcaseITest.java | 70 ++ .../convention/SpringMavenPluginITest.java | 61 ++ .../TestsConfigurationPluginITest.java | 31 + .../spring/gradle/convention/UtilsTest.java | 147 ++++ .../convention/sagan/SaganApiTests.java | 85 +++ .../milestones/GitHubMilestoneApiTests.java | 388 ++++++++++ .../milestones/GitHubMilestoneApiTests.java | 386 ++++++++++ .../integrationtest/withgroovy/build.gradle | 16 + .../groovy/sample/TheTest.groovy | 31 + .../integrationtest/withjava/build.gradle | 14 + .../integration-test/java/sample/TheTest.java | 16 + .../integrationtest/withpropdeps/build.gradle | 14 + .../integration-test/java/sample/TheTest.java | 11 + .../samples/jacoco/java/build.gradle | 13 + .../java/src/main/java/sample/TheClass.java | 11 + .../src/test/java/sample/TheClassTest.java | 19 + .../javadocapi/multimodule/api/build.gradle | 1 + .../api/src/main/java/sample/Api.java | 14 + .../javadocapi/multimodule/build.gradle | 5 + .../javadocapi/multimodule/impl/build.gradle | 1 + .../impl/src/main/java/sample/Impl.java | 14 + .../multimodule/sample/build.gradle | 1 + .../sample/src/main/java/sample/Sample.java | 14 + .../javadocapi/multimodule/settings.gradle | 3 + .../maven/install-with-springio/build.gradle | 12 + .../gradle/dependency-management.gradle | 5 + .../samples/maven/install/build.gradle | 14 + .../samples/maven/signing/build.gradle | 16 + .../samples/maven/signing/settings.gradle | 1 + .../samples/maven/upload/build.gradle | 20 + .../resources/samples/showcase/Jenkinsfile | 52 ++ .../resources/samples/showcase/bom/bom.gradle | 2 + .../resources/samples/showcase/build.gradle | 6 + .../showcase/etc/checkstyle/checkstyle.xml | 5 + .../samples/sgbcs-sample-war/build.gradle | 13 + .../java/sample/HelloServletTest.java | 23 + .../src/main/java/sample/HelloServlet.java | 35 + .../samples/showcase/settings.gradle | 25 + .../showcase/sgbcs-api/sgbcs-api.gradle | 10 + .../sgbcs-api/src/main/java/api/Api.java | 10 + .../sgbcs-api/src/test/java/api/ApiTest.java | 9 + .../showcase/sgbcs-core/sgbcs-core.gradle | 8 + .../src/main/java/core/CoreClass.java | 13 + .../src/main/java/core/HasOptional.java | 14 + .../main/resources/META-INF/spring.handlers | 1 + .../main/resources/META-INF/spring.schemas | 4 + .../spring-springgradlebuildsample-2.0.xsd | 468 ++++++++++++ .../spring-springgradlebuildsample-2.1.xsd | 685 ++++++++++++++++++ .../spring-springgradlebuildsample-2.2.xsd | 685 ++++++++++++++++++ .../src/test/java/core/CoreClassTest.java | 12 + .../src/test/java/core/HasOptionalTest.java | 12 + .../showcase/sgbcs-docs/sgbcs-docs.gradle | 4 + .../sgbcs-docs/src/docs/asciidoc/docinfo.html | 1 + .../src/docs/asciidoc/images/sunset.jpg | Bin 0 -> 122404 bytes .../sgbcs-docs/src/docs/asciidoc/index.adoc | 60 ++ .../src/docs/asciidoc/subdir/_b.adoc | 7 + .../src/docs/asciidoc/subdir/_c.adoc | 1 + .../src/main/java/example/StringUtils.java | 9 + .../samples/testsconfiguration/build.gradle | 4 + .../testsconfiguration/core/build.gradle | 2 + .../core/src/test/java/sample/Dependency.java | 3 + .../testsconfiguration/settings.gradle | 2 + .../testsconfiguration/web/build.gradle | 10 + .../src/test/java/sample/DependencyTest.java | 10 + buildSrc/src/test/resources/test-private.pgp | 83 +++ .../spring-session-data-redis.gradle | 3 + .../hazelcast4/hazelcast4.gradle | 4 +- .../spring-session-hazelcast.gradle | 3 + .../spring-session-jdbc.gradle | 3 + ...-session-sample-boot-findbyusername.gradle | 3 +- ...pring-session-sample-boot-hazelcast.gradle | 3 +- ...ring-session-sample-boot-hazelcast4.gradle | 3 +- .../spring-session-sample-boot-jdbc.gradle | 3 +- ...ring-session-sample-boot-redis-json.gradle | 3 +- ...ng-session-sample-boot-redis-simple.gradle | 3 +- .../spring-session-sample-boot-redis.gradle | 3 +- ...n-sample-boot-webflux-custom-cookie.gradle | 3 +- .../spring-session-sample-boot-webflux.gradle | 3 +- ...ion-sample-javaconfig-custom-cookie.gradle | 16 +- ...session-sample-javaconfig-hazelcast.gradle | 16 +- ...ring-session-sample-javaconfig-jdbc.gradle | 16 +- ...ing-session-sample-javaconfig-redis.gradle | 16 +- ...ring-session-sample-javaconfig-rest.gradle | 13 +- ...-session-sample-javaconfig-security.gradle | 16 +- ...pring-session-sample-misc-hazelcast.gradle | 16 +- .../spring-session-sample-xml-jdbc.gradle | 16 +- .../spring-session-sample-xml-redis.gradle | 16 +- 166 files changed, 7379 insertions(+), 46 deletions(-) create mode 100644 buildSrc/build.gradle create mode 100644 buildSrc/gradle/wrapper/gradle-wrapper.jar create mode 100644 buildSrc/gradle/wrapper/gradle-wrapper.properties create mode 100644 buildSrc/src/main/groovy/io/spring/gradle/IncludeRepoTask.groovy create mode 100644 buildSrc/src/main/groovy/io/spring/gradle/convention/AbstractSpringJavaPlugin.groovy create mode 100644 buildSrc/src/main/groovy/io/spring/gradle/convention/ArtifactoryPlugin.groovy create mode 100644 buildSrc/src/main/groovy/io/spring/gradle/convention/CheckstylePlugin.groovy create mode 100644 buildSrc/src/main/groovy/io/spring/gradle/convention/DependencyManagementExportTask.groovy create mode 100644 buildSrc/src/main/groovy/io/spring/gradle/convention/DeployDocsPlugin.groovy create mode 100644 buildSrc/src/main/groovy/io/spring/gradle/convention/DocsPlugin.groovy create mode 100644 buildSrc/src/main/groovy/io/spring/gradle/convention/IncludeCheckRemotePlugin.groovy create mode 100644 buildSrc/src/main/groovy/io/spring/gradle/convention/IntegrationTestPlugin.groovy create mode 100644 buildSrc/src/main/groovy/io/spring/gradle/convention/JacocoPlugin.groovy create mode 100644 buildSrc/src/main/groovy/io/spring/gradle/convention/JavadocApiPlugin.groovy create mode 100644 buildSrc/src/main/groovy/io/spring/gradle/convention/JavadocOptionsPlugin.groovy create mode 100644 buildSrc/src/main/groovy/io/spring/gradle/convention/ManagementConfigurationPlugin.java create mode 100644 buildSrc/src/main/groovy/io/spring/gradle/convention/MavenBomPlugin.groovy create mode 100644 buildSrc/src/main/groovy/io/spring/gradle/convention/RepositoryConventionPlugin.groovy create mode 100644 buildSrc/src/main/groovy/io/spring/gradle/convention/RootProjectPlugin.groovy create mode 100644 buildSrc/src/main/groovy/io/spring/gradle/convention/SchemaDeployPlugin.groovy create mode 100644 buildSrc/src/main/groovy/io/spring/gradle/convention/SchemaPlugin.groovy create mode 100644 buildSrc/src/main/groovy/io/spring/gradle/convention/SchemaZipPlugin.groovy create mode 100644 buildSrc/src/main/groovy/io/spring/gradle/convention/SortedProperties.groovy create mode 100644 buildSrc/src/main/groovy/io/spring/gradle/convention/SpringDependencyManagementConventionPlugin.groovy create mode 100644 buildSrc/src/main/groovy/io/spring/gradle/convention/SpringModulePlugin.groovy create mode 100644 buildSrc/src/main/groovy/io/spring/gradle/convention/SpringSampleBootPlugin.groovy create mode 100644 buildSrc/src/main/groovy/io/spring/gradle/convention/SpringSamplePlugin.groovy create mode 100644 buildSrc/src/main/groovy/io/spring/gradle/convention/SpringSampleWarPlugin.groovy create mode 100644 buildSrc/src/main/groovy/io/spring/gradle/convention/SpringTestPlugin.groovy create mode 100644 buildSrc/src/main/groovy/io/spring/gradle/convention/TestsConfigurationPlugin.groovy create mode 100644 buildSrc/src/main/groovy/io/spring/gradle/convention/Utils.groovy create mode 100644 buildSrc/src/main/java/org/springframework/gradle/CopyPropertiesPlugin.java create mode 100644 buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestoneApi.java create mode 100644 buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestoneHasNoOpenIssuesTask.java create mode 100644 buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestonePlugin.java create mode 100644 buildSrc/src/main/java/org/springframework/gradle/github/milestones/Milestone.java create mode 100644 buildSrc/src/main/java/org/springframework/gradle/github/milestones/RepositoryRef.java create mode 100644 buildSrc/src/main/java/org/springframework/gradle/maven/MavenPublishingConventionsPlugin.java create mode 100644 buildSrc/src/main/java/org/springframework/gradle/maven/PublishAllJavaComponentsPlugin.java create mode 100644 buildSrc/src/main/java/org/springframework/gradle/maven/PublishArtifactsPlugin.java create mode 100644 buildSrc/src/main/java/org/springframework/gradle/maven/PublishLocalPlugin.java create mode 100644 buildSrc/src/main/java/org/springframework/gradle/maven/SpringMavenPlugin.java create mode 100644 buildSrc/src/main/java/org/springframework/gradle/maven/SpringNexusPublishPlugin.java create mode 100644 buildSrc/src/main/java/org/springframework/gradle/maven/SpringSigningPlugin.java create mode 100644 buildSrc/src/main/java/org/springframework/gradle/propdeps/PropDepsEclipsePlugin.groovy create mode 100644 buildSrc/src/main/java/org/springframework/gradle/propdeps/PropDepsIdeaPlugin.groovy create mode 100644 buildSrc/src/main/java/org/springframework/gradle/propdeps/PropDepsPlugin.groovy create mode 100644 buildSrc/src/main/java/org/springframework/gradle/sagan/Release.java create mode 100644 buildSrc/src/main/java/org/springframework/gradle/sagan/SaganApi.java create mode 100644 buildSrc/src/main/java/org/springframework/gradle/sagan/SaganCreateReleaseTask.java create mode 100644 buildSrc/src/main/java/org/springframework/gradle/sagan/SaganDeleteReleaseTask.java create mode 100644 buildSrc/src/main/java/org/springframework/gradle/sagan/SaganPlugin.java create mode 100644 buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.artifactory.properties create mode 100644 buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.bom.properties create mode 100644 buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.checkstyle.properties create mode 100644 buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.docs.properties create mode 100644 buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.include-check-remote.properties create mode 100644 buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.integration-test.properties create mode 100644 buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.jacoco.properties create mode 100644 buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.javadoc-api.properties create mode 100644 buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.javadoc-options.properties create mode 100644 buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.repository.properties create mode 100644 buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.root.properties create mode 100644 buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.spring-module.properties create mode 100644 buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.spring-sample-boot.properties create mode 100644 buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.spring-sample-war.properties create mode 100644 buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.spring-sample.properties create mode 100644 buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.spring-test.properties create mode 100644 buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.springdependencymangement.properties create mode 100644 buildSrc/src/main/resources/META-INF/gradle-plugins/io.spring.convention.tests-configuration.properties create mode 100644 buildSrc/src/test/java/io/spring/gradle/TestKit.java create mode 100644 buildSrc/src/test/java/io/spring/gradle/convention/IncludeCheckRemotePluginTest.java create mode 100644 buildSrc/src/test/java/io/spring/gradle/convention/IntegrationPluginTest.java create mode 100644 buildSrc/src/test/java/io/spring/gradle/convention/IntegrationTestPluginITest.java create mode 100644 buildSrc/src/test/java/io/spring/gradle/convention/JacocoPluginITest.java create mode 100644 buildSrc/src/test/java/io/spring/gradle/convention/JavadocApiPluginITest.java create mode 100644 buildSrc/src/test/java/io/spring/gradle/convention/JavadocApiPluginTest.java create mode 100644 buildSrc/src/test/java/io/spring/gradle/convention/RepositoryConventionPluginTests.java create mode 100644 buildSrc/src/test/java/io/spring/gradle/convention/ShowcaseITest.java create mode 100644 buildSrc/src/test/java/io/spring/gradle/convention/SpringMavenPluginITest.java create mode 100644 buildSrc/src/test/java/io/spring/gradle/convention/TestsConfigurationPluginITest.java create mode 100644 buildSrc/src/test/java/io/spring/gradle/convention/UtilsTest.java create mode 100644 buildSrc/src/test/java/io/spring/gradle/convention/sagan/SaganApiTests.java create mode 100644 buildSrc/src/test/java/io/spring/gradle/github/milestones/GitHubMilestoneApiTests.java create mode 100644 buildSrc/src/test/java/org/springframework/gradle/github/milestones/GitHubMilestoneApiTests.java create mode 100644 buildSrc/src/test/resources/samples/integrationtest/withgroovy/build.gradle create mode 100644 buildSrc/src/test/resources/samples/integrationtest/withgroovy/src/integration-test/groovy/sample/TheTest.groovy create mode 100644 buildSrc/src/test/resources/samples/integrationtest/withjava/build.gradle create mode 100644 buildSrc/src/test/resources/samples/integrationtest/withjava/src/integration-test/java/sample/TheTest.java create mode 100644 buildSrc/src/test/resources/samples/integrationtest/withpropdeps/build.gradle create mode 100644 buildSrc/src/test/resources/samples/integrationtest/withpropdeps/src/integration-test/java/sample/TheTest.java create mode 100644 buildSrc/src/test/resources/samples/jacoco/java/build.gradle create mode 100644 buildSrc/src/test/resources/samples/jacoco/java/src/main/java/sample/TheClass.java create mode 100644 buildSrc/src/test/resources/samples/jacoco/java/src/test/java/sample/TheClassTest.java create mode 100644 buildSrc/src/test/resources/samples/javadocapi/multimodule/api/build.gradle create mode 100644 buildSrc/src/test/resources/samples/javadocapi/multimodule/api/src/main/java/sample/Api.java create mode 100644 buildSrc/src/test/resources/samples/javadocapi/multimodule/build.gradle create mode 100644 buildSrc/src/test/resources/samples/javadocapi/multimodule/impl/build.gradle create mode 100644 buildSrc/src/test/resources/samples/javadocapi/multimodule/impl/src/main/java/sample/Impl.java create mode 100644 buildSrc/src/test/resources/samples/javadocapi/multimodule/sample/build.gradle create mode 100644 buildSrc/src/test/resources/samples/javadocapi/multimodule/sample/src/main/java/sample/Sample.java create mode 100644 buildSrc/src/test/resources/samples/javadocapi/multimodule/settings.gradle create mode 100644 buildSrc/src/test/resources/samples/maven/install-with-springio/build.gradle create mode 100644 buildSrc/src/test/resources/samples/maven/install-with-springio/gradle/dependency-management.gradle create mode 100644 buildSrc/src/test/resources/samples/maven/install/build.gradle create mode 100644 buildSrc/src/test/resources/samples/maven/signing/build.gradle create mode 100644 buildSrc/src/test/resources/samples/maven/signing/settings.gradle create mode 100644 buildSrc/src/test/resources/samples/maven/upload/build.gradle create mode 100644 buildSrc/src/test/resources/samples/showcase/Jenkinsfile create mode 100644 buildSrc/src/test/resources/samples/showcase/bom/bom.gradle create mode 100644 buildSrc/src/test/resources/samples/showcase/build.gradle create mode 100644 buildSrc/src/test/resources/samples/showcase/etc/checkstyle/checkstyle.xml create mode 100644 buildSrc/src/test/resources/samples/showcase/samples/sgbcs-sample-war/build.gradle create mode 100644 buildSrc/src/test/resources/samples/showcase/samples/sgbcs-sample-war/src/integration-test/java/sample/HelloServletTest.java create mode 100644 buildSrc/src/test/resources/samples/showcase/samples/sgbcs-sample-war/src/main/java/sample/HelloServlet.java create mode 100644 buildSrc/src/test/resources/samples/showcase/settings.gradle create mode 100644 buildSrc/src/test/resources/samples/showcase/sgbcs-api/sgbcs-api.gradle create mode 100644 buildSrc/src/test/resources/samples/showcase/sgbcs-api/src/main/java/api/Api.java create mode 100644 buildSrc/src/test/resources/samples/showcase/sgbcs-api/src/test/java/api/ApiTest.java create mode 100644 buildSrc/src/test/resources/samples/showcase/sgbcs-core/sgbcs-core.gradle create mode 100644 buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/main/java/core/CoreClass.java create mode 100644 buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/main/java/core/HasOptional.java create mode 100644 buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/main/resources/META-INF/spring.handlers create mode 100644 buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/main/resources/META-INF/spring.schemas create mode 100644 buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/main/resources/org/springframework/springgradlebuildsample/config/spring-springgradlebuildsample-2.0.xsd create mode 100644 buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/main/resources/org/springframework/springgradlebuildsample/config/spring-springgradlebuildsample-2.1.xsd create mode 100644 buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/main/resources/org/springframework/springgradlebuildsample/config/spring-springgradlebuildsample-2.2.xsd create mode 100644 buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/test/java/core/CoreClassTest.java create mode 100644 buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/test/java/core/HasOptionalTest.java create mode 100644 buildSrc/src/test/resources/samples/showcase/sgbcs-docs/sgbcs-docs.gradle create mode 100644 buildSrc/src/test/resources/samples/showcase/sgbcs-docs/src/docs/asciidoc/docinfo.html create mode 100644 buildSrc/src/test/resources/samples/showcase/sgbcs-docs/src/docs/asciidoc/images/sunset.jpg create mode 100644 buildSrc/src/test/resources/samples/showcase/sgbcs-docs/src/docs/asciidoc/index.adoc create mode 100644 buildSrc/src/test/resources/samples/showcase/sgbcs-docs/src/docs/asciidoc/subdir/_b.adoc create mode 100644 buildSrc/src/test/resources/samples/showcase/sgbcs-docs/src/docs/asciidoc/subdir/_c.adoc create mode 100644 buildSrc/src/test/resources/samples/showcase/sgbcs-docs/src/main/java/example/StringUtils.java create mode 100644 buildSrc/src/test/resources/samples/testsconfiguration/build.gradle create mode 100644 buildSrc/src/test/resources/samples/testsconfiguration/core/build.gradle create mode 100644 buildSrc/src/test/resources/samples/testsconfiguration/core/src/test/java/sample/Dependency.java create mode 100644 buildSrc/src/test/resources/samples/testsconfiguration/settings.gradle create mode 100644 buildSrc/src/test/resources/samples/testsconfiguration/web/build.gradle create mode 100644 buildSrc/src/test/resources/samples/testsconfiguration/web/src/test/java/sample/DependencyTest.java create mode 100644 buildSrc/src/test/resources/test-private.pgp diff --git a/build.gradle b/build.gradle index 42e274d2..01222500 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,6 @@ buildscript { } dependencies { - classpath 'io.spring.gradle:spring-build-conventions:0.0.37' classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion" } } @@ -55,3 +54,7 @@ subprojects { useJUnitPlatform() } } + +nohttp { + source.exclude "buildSrc/build/**" +} diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle new file mode 100644 index 00000000..6070a5e3 --- /dev/null +++ b/buildSrc/build.gradle @@ -0,0 +1,84 @@ +plugins { + id "java-gradle-plugin" + id "java" + id "groovy" +} + +sourceCompatibility = 1.8 + +repositories { + jcenter() + gradlePluginPortal() + mavenCentral() + 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" + } + } +} + +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.spring.gradle:propdeps-plugin:0.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.37.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:3.12.4' + 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/gradle/wrapper/gradle-wrapper.jar b/buildSrc/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f GIT binary patch literal 59203 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

K7UpUK$>l@ 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 E2O(~oYXATM literal 0 HcmV?d00001 diff --git a/buildSrc/gradle/wrapper/gradle-wrapper.properties b/buildSrc/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..ffed3a25 --- /dev/null +++ b/buildSrc/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists 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 00000000..b549ff73 --- /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 00000000..629bd470 --- /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 00000000..3292ca4b --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/ArtifactoryPlugin.groovy @@ -0,0 +1,45 @@ +/* + * 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 00000000..53a32a83 --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/CheckstylePlugin.groovy @@ -0,0 +1,49 @@ +/* + * 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.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.15') + 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 00000000..4ab559cf --- /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 00000000..bf1add50 --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/DeployDocsPlugin.groovy @@ -0,0 +1,82 @@ +/* + * 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 00000000..d0a64ab8 --- /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 00000000..5ba6e350 --- /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 00000000..9858458b --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/IntegrationTestPlugin.groovy @@ -0,0 +1,122 @@ +/* + * 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 00000000..900cf9f1 --- /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 00000000..ef864b03 --- /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 00000000..e0fef7ec --- /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 00000000..8db6fd97 --- /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 00000000..fe94e81a --- /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 00000000..c2420814 --- /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 00000000..3f7bada8 --- /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 00000000..4b9c038d --- /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 00000000..769ae80d --- /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 00000000..72de4b3f --- /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 00000000..5b950d83 --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/SortedProperties.groovy @@ -0,0 +1,52 @@ +/* + * 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 00000000..90306174 --- /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 00000000..36a7013f --- /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 00000000..c79e6b42 --- /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 00000000..f41a0ca8 --- /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 00000000..6128f9c1 --- /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 00000000..55807dc0 --- /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 00000000..99aac80a --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/TestsConfigurationPlugin.groovy @@ -0,0 +1,54 @@ +/* + * 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 +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: + * + * + * testCompile 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 00000000..8f5a6a90 --- /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 00000000..78819fe5 --- /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 00000000..31f1274a --- /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 00000000..de846378 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/github/milestones/GitHubMilestoneHasNoOpenIssuesTask.java @@ -0,0 +1,74 @@ +/* + * 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 00000000..527b7676 --- /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 00000000..5d0ff234 --- /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 00000000..70eec3b1 --- /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 00000000..d82b6d94 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/maven/MavenPublishingConventionsPlugin.java @@ -0,0 +1,82 @@ +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-security"); + 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-security.git"); + scm.getDeveloperConnection().set("scm:git:ssh://git@github.com/spring-projects/spring-security.git"); + scm.getUrl().set("https://github.com/spring-projects/spring-security"); + } + + private void customizeIssueManagement(MavenPomIssueManagement issueManagement) { + issueManagement.getSystem().set("GitHub"); + issueManagement.getUrl().set("https://github.com/spring-projects/spring-security/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 00000000..408d83e7 --- /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 00000000..ed3985e2 --- /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 00000000..54f9e497 --- /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 00000000..8b062129 --- /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 00000000..3002824c --- /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 00000000..112651ba --- /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 00000000..bf88ca6c --- /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 00000000..4035971a --- /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 00000000..e0893e6a --- /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 00000000..5e62c658 --- /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 00000000..24eeb311 --- /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 00000000..6592544b --- /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 00000000..49a38852 --- /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 00000000..388a2d15 --- /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 00000000..573d1281 --- /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 00000000..dec2e7a8 --- /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 00000000..9259a914 --- /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 00000000..fcad41ab --- /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 00000000..298e1bba --- /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 00000000..0da39b33 --- /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 00000000..70cfb7d0 --- /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 00000000..bfb92306 --- /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 00000000..77e7832e --- /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 00000000..b427d552 --- /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 00000000..9844db62 --- /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 00000000..124dd85f --- /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 00000000..4ecd703e --- /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 00000000..367e0189 --- /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 00000000..77cb21d5 --- /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 00000000..4881e2d0 --- /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 00000000..10b8580c --- /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 00000000..afd0a712 --- /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 diff --git a/buildSrc/src/test/java/io/spring/gradle/TestKit.java b/buildSrc/src/test/java/io/spring/gradle/TestKit.java new file mode 100644 index 00000000..2bc0b35e --- /dev/null +++ b/buildSrc/src/test/java/io/spring/gradle/TestKit.java @@ -0,0 +1,58 @@ +/* + * 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; + +import org.apache.commons.io.FileUtils; +import org.gradle.testkit.runner.GradleRunner; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Paths; +import java.util.Enumeration; + +public class TestKit { + final File buildDir; + + public TestKit(File buildDir) { + this.buildDir = buildDir; + } + + public File getRootDir() { + return buildDir; + } + + public GradleRunner withProjectDir(File projectDir) throws IOException { + FileUtils.copyDirectory(projectDir, buildDir); + return GradleRunner.create() + .withProjectDir(buildDir) + .withPluginClasspath(); + } + + public GradleRunner withProjectResource(String projectResourceName) throws IOException, URISyntaxException { + ClassLoader classLoader = getClass().getClassLoader(); + Enumeration resources = classLoader.getResources(projectResourceName); + if(!resources.hasMoreElements()) { + throw new IOException("Cannot find resource " + projectResourceName + " with " + classLoader); + } + URL resourceUrl = resources.nextElement(); + File projectDir = Paths.get(resourceUrl.toURI()).toFile(); + return withProjectDir(projectDir); + } +} diff --git a/buildSrc/src/test/java/io/spring/gradle/convention/IncludeCheckRemotePluginTest.java b/buildSrc/src/test/java/io/spring/gradle/convention/IncludeCheckRemotePluginTest.java new file mode 100644 index 00000000..e8ad6211 --- /dev/null +++ b/buildSrc/src/test/java/io/spring/gradle/convention/IncludeCheckRemotePluginTest.java @@ -0,0 +1,111 @@ +/* + * 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.apache.commons.io.FileUtils; +import org.gradle.api.Project; +import org.gradle.api.tasks.GradleBuild; +import org.gradle.testfixtures.ProjectBuilder; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; + +class IncludeCheckRemotePluginTest { + + Project rootProject; + + @AfterEach + public void cleanup() throws Exception { + if (rootProject != null) { + FileUtils.deleteDirectory(rootProject.getProjectDir()); + } + } + + @Test + void applyWhenExtensionPropertiesNoTasksThenCreateCheckRemoteTaskWithDefaultTask() { + this.rootProject = ProjectBuilder.builder().build(); + this.rootProject.getPluginManager().apply(IncludeCheckRemotePlugin.class); + this.rootProject.getExtensions().configure(IncludeCheckRemotePlugin.IncludeCheckRemoteExtension.class, + (includeCheckRemoteExtension) -> { + includeCheckRemoteExtension.setProperty("repository", "my-project/my-repository"); + includeCheckRemoteExtension.setProperty("ref", "main"); + }); + + GradleBuild checkRemote = (GradleBuild) this.rootProject.getTasks().named("checkRemote").get(); + assertThat(checkRemote.getTasks()).containsExactly("check"); + } + + @Test + void applyWhenExtensionPropertiesTasksThenCreateCheckRemoteWithProvidedTasks() { + this.rootProject = ProjectBuilder.builder().build(); + this.rootProject.getPluginManager().apply(IncludeCheckRemotePlugin.class); + this.rootProject.getExtensions().configure(IncludeCheckRemotePlugin.IncludeCheckRemoteExtension.class, + (includeCheckRemoteExtension) -> { + includeCheckRemoteExtension.setProperty("repository", "my-project/my-repository"); + includeCheckRemoteExtension.setProperty("ref", "main"); + includeCheckRemoteExtension.setProperty("tasks", Arrays.asList("clean", "build", "test")); + }); + + GradleBuild checkRemote = (GradleBuild) this.rootProject.getTasks().named("checkRemote").get(); + assertThat(checkRemote.getTasks()).containsExactly("clean", "build", "test"); + } + + @Test + void applyWhenExtensionPropertiesThenRegisterIncludeRepoTaskWithExtensionProperties() { + this.rootProject = ProjectBuilder.builder().build(); + this.rootProject.getPluginManager().apply(IncludeCheckRemotePlugin.class); + this.rootProject.getExtensions().configure(IncludeCheckRemotePlugin.IncludeCheckRemoteExtension.class, + (includeCheckRemoteExtension) -> { + includeCheckRemoteExtension.setProperty("repository", "my-project/my-repository"); + includeCheckRemoteExtension.setProperty("ref", "main"); + }); + + IncludeRepoTask includeRepo = (IncludeRepoTask) this.rootProject.getTasks().named("includeRepo").get(); + assertThat(includeRepo).isNotNull(); + assertThat(includeRepo.getRepository().get()).isEqualTo("my-project/my-repository"); + assertThat(includeRepo.getRef().get()).isEqualTo("main"); + } + + @Test + void applyWhenRegisterTasksThenCheckRemoteDirSameAsIncludeRepoOutputDir() { + this.rootProject = ProjectBuilder.builder().build(); + this.rootProject.getPluginManager().apply(IncludeCheckRemotePlugin.class); + this.rootProject.getExtensions().configure(IncludeCheckRemotePlugin.IncludeCheckRemoteExtension.class, + (includeCheckRemoteExtension) -> { + includeCheckRemoteExtension.setProperty("repository", "my-project/my-repository"); + includeCheckRemoteExtension.setProperty("ref", "main"); + }); + IncludeRepoTask includeRepo = (IncludeRepoTask) this.rootProject.getTasks().named("includeRepo").get(); + GradleBuild checkRemote = (GradleBuild) this.rootProject.getTasks().named("checkRemote").get(); + assertThat(checkRemote.getDir()).isEqualTo(includeRepo.getOutputDirectory()); + } + + @Test + void applyWhenNoExtensionPropertiesThenRegisterTasks() { + this.rootProject = ProjectBuilder.builder().build(); + this.rootProject.getPluginManager().apply(IncludeCheckRemotePlugin.class); + IncludeRepoTask includeRepo = (IncludeRepoTask) this.rootProject.getTasks().named("includeRepo").get(); + GradleBuild checkRemote = (GradleBuild) this.rootProject.getTasks().named("checkRemote").get(); + assertThat(includeRepo).isNotNull(); + assertThat(checkRemote).isNotNull(); + } + +} diff --git a/buildSrc/src/test/java/io/spring/gradle/convention/IntegrationPluginTest.java b/buildSrc/src/test/java/io/spring/gradle/convention/IntegrationPluginTest.java new file mode 100644 index 00000000..cfb7d3ff --- /dev/null +++ b/buildSrc/src/test/java/io/spring/gradle/convention/IntegrationPluginTest.java @@ -0,0 +1,52 @@ +/* + * 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.apache.commons.io.FileUtils; +import org.gradle.api.Project; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.testfixtures.ProjectBuilder; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import java.io.File; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Rob Winch + */ +public class IntegrationPluginTest { + Project rootProject; + + @AfterEach + public void cleanup() throws Exception { + if (rootProject != null) { + FileUtils.deleteDirectory(rootProject.getProjectDir()); + } + } + + @Test + public void applyWhenNoSourceThenIntegrationTestTaskNull() { + rootProject = ProjectBuilder.builder().build(); + rootProject.getPlugins().apply(JavaPlugin.class); + rootProject.getPlugins().apply(IntegrationTestPlugin.class); + + assertThat(rootProject.getTasks().findByPath("integrationTest")).isNull(); + } + +} diff --git a/buildSrc/src/test/java/io/spring/gradle/convention/IntegrationTestPluginITest.java b/buildSrc/src/test/java/io/spring/gradle/convention/IntegrationTestPluginITest.java new file mode 100644 index 00000000..97537343 --- /dev/null +++ b/buildSrc/src/test/java/io/spring/gradle/convention/IntegrationTestPluginITest.java @@ -0,0 +1,52 @@ +package io.spring.gradle.convention; + +import io.spring.gradle.TestKit; +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.TaskOutcome; +import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.io.TempDir; + +import java.io.File; +import java.nio.file.Path; + +import static org.assertj.core.api.Assertions.assertThat; + +public class IntegrationTestPluginITest { + private io.spring.gradle.TestKit testKit; + + @BeforeEach + void setup(@TempDir Path tempDir) { + this.testKit = new TestKit(tempDir.toFile()); + } + + @Test + public void checkWithJavaPlugin() throws Exception { + BuildResult result = this.testKit.withProjectResource("samples/integrationtest/withjava/") + .withArguments("check") + .build(); + assertThat(result.task(":check").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + assertThat(new File(testKit.getRootDir(), "build/test-results/integrationTest/")).exists(); + assertThat(new File(testKit.getRootDir(), "build/reports/tests/integrationTest/")).exists(); + } + + @Test + public void checkWithPropdeps() throws Exception { + BuildResult result = this.testKit.withProjectResource("samples/integrationtest/withpropdeps/") + .withArguments("check") + .build(); + assertThat(result.task(":check").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + assertThat(new File(testKit.getRootDir(), "build/test-results/integrationTest/")).exists(); + assertThat(new File(testKit.getRootDir(), "build/reports/tests/integrationTest/")).exists(); + } + + @Test + public void checkWithGroovy() throws Exception { + BuildResult result = this.testKit.withProjectResource("samples/integrationtest/withgroovy/") + .withArguments("check") + .build(); + assertThat(result.task(":check").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + assertThat(new File(testKit.getRootDir(), "build/test-results/integrationTest/")).exists(); + assertThat(new File(testKit.getRootDir(), "build/reports/tests/integrationTest/")).exists(); + } +} diff --git a/buildSrc/src/test/java/io/spring/gradle/convention/JacocoPluginITest.java b/buildSrc/src/test/java/io/spring/gradle/convention/JacocoPluginITest.java new file mode 100644 index 00000000..8b1c0972 --- /dev/null +++ b/buildSrc/src/test/java/io/spring/gradle/convention/JacocoPluginITest.java @@ -0,0 +1,31 @@ +package io.spring.gradle.convention; + +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.TaskOutcome; +import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.io.TempDir; + +import java.io.File; +import java.nio.file.Path; + +import static org.assertj.core.api.Assertions.assertThat; + +public class JacocoPluginITest{ + private io.spring.gradle.TestKit testKit; + + @BeforeEach + void setup(@TempDir Path tempDir) { + this.testKit = new io.spring.gradle.TestKit(tempDir.toFile()); + } + + @Test + public void checkWithJavaPlugin() throws Exception { + BuildResult result = this.testKit.withProjectResource("samples/jacoco/java/") + .withArguments("check") + .build(); + assertThat(result.task(":check").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + assertThat(new File(testKit.getRootDir(), "build/jacoco")).exists(); + assertThat(new File(testKit.getRootDir(), "build/reports/jacoco/test/html/")).exists(); + } +} diff --git a/buildSrc/src/test/java/io/spring/gradle/convention/JavadocApiPluginITest.java b/buildSrc/src/test/java/io/spring/gradle/convention/JavadocApiPluginITest.java new file mode 100644 index 00000000..681d5c49 --- /dev/null +++ b/buildSrc/src/test/java/io/spring/gradle/convention/JavadocApiPluginITest.java @@ -0,0 +1,38 @@ +package io.spring.gradle.convention; + +import io.spring.gradle.TestKit; +import org.apache.commons.io.FileUtils; +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.TaskOutcome; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.File; +import java.nio.file.Path; + +import static org.assertj.core.api.Assertions.assertThat; + +public class JavadocApiPluginITest { + private TestKit testKit; + + @BeforeEach + void setup(@TempDir Path tempDir) { + this.testKit = new TestKit(tempDir.toFile()); + } + + @Test + public void multiModuleApi() throws Exception { + BuildResult result = this.testKit.withProjectResource("samples/javadocapi/multimodule/") + .withArguments("api") + .build(); + assertThat(result.task(":api").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + File allClasses = new File(testKit.getRootDir(), "build/api/allclasses-noframe.html"); + File index = new File(testKit.getRootDir(), "build/api/allclasses.html"); + File listing = allClasses.exists() ? allClasses : index; + String listingText = FileUtils.readFileToString(listing); + assertThat(listingText).contains("sample/Api.html"); + assertThat(listingText).contains("sample/Impl.html"); + assertThat(listingText).doesNotContain("sample/Sample.html"); + } +} diff --git a/buildSrc/src/test/java/io/spring/gradle/convention/JavadocApiPluginTest.java b/buildSrc/src/test/java/io/spring/gradle/convention/JavadocApiPluginTest.java new file mode 100644 index 00000000..26d8b4e2 --- /dev/null +++ b/buildSrc/src/test/java/io/spring/gradle/convention/JavadocApiPluginTest.java @@ -0,0 +1,56 @@ +/* + * 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 org.apache.commons.io.FileUtils; +import static org.assertj.core.api.Assertions.assertThat; +import org.gradle.api.Project; +import org.gradle.api.tasks.javadoc.Javadoc; +import org.gradle.testfixtures.ProjectBuilder; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +/** + * @author Rob Winch + */ +public class JavadocApiPluginTest { + Project rootProject; + + @AfterEach + public void cleanup() throws Exception { + if (rootProject != null) { + FileUtils.deleteDirectory(rootProject.getProjectDir()); + } + } + + @Test + public void applyWhenNotOverrideThenPropertiesDefaulted() { + rootProject = ProjectBuilder.builder().build(); + rootProject.getPlugins().apply(JavadocApiPlugin.class); + + Javadoc apiTask = (Javadoc) rootProject.getTasks().getByPath("api"); + + assertThat(apiTask).isNotNull(); + assertThat(apiTask.getGroup()).isEqualTo("Documentation"); + assertThat(apiTask.getDescription()).isEqualTo("Generates aggregated Javadoc API documentation."); + assertThat(apiTask.getMaxMemory()).isEqualTo("1024m"); + assertThat(apiTask.getDestinationDir()).isEqualTo(new File(rootProject.getBuildDir(), "api")); + } + +} diff --git a/buildSrc/src/test/java/io/spring/gradle/convention/RepositoryConventionPluginTests.java b/buildSrc/src/test/java/io/spring/gradle/convention/RepositoryConventionPluginTests.java new file mode 100644 index 00000000..2bad49c8 --- /dev/null +++ b/buildSrc/src/test/java/io/spring/gradle/convention/RepositoryConventionPluginTests.java @@ -0,0 +1,158 @@ +/* + * 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.Project; +import org.gradle.api.artifacts.dsl.RepositoryHandler; +import org.gradle.api.artifacts.repositories.ArtifactRepository; +import org.gradle.api.artifacts.repositories.MavenArtifactRepository; +import org.gradle.api.plugins.ExtraPropertiesExtension; +import org.gradle.testfixtures.ProjectBuilder; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link RepositoryConventionPlugin}. + */ +public class RepositoryConventionPluginTests { + + private Project project = ProjectBuilder.builder().build(); + + @BeforeEach + public void setUp() { + this.project.getProperties().clear(); + } + + @Test + public void applyWhenIsReleaseThenShouldIncludeReleaseRepo() { + this.project.setVersion("1.0.0.RELEASE"); + this.project.getPluginManager().apply(RepositoryConventionPlugin.class); + + RepositoryHandler repositories = this.project.getRepositories(); + assertReleaseRepository(repositories); + } + + @Test + public void applyWhenIsMilestoneThenShouldIncludeMilestoneRepo() { + this.project.setVersion("1.0.0.M1"); + this.project.getPluginManager().apply(RepositoryConventionPlugin.class); + + RepositoryHandler repositories = this.project.getRepositories(); + assertMilestoneRepository(repositories); // milestone + } + + @Test + public void applyWhenIsSnapshotThenShouldIncludeSnapshotRepo() { + this.project.setVersion("1.0.0.BUILD-SNAPSHOT"); + this.project.getPluginManager().apply(RepositoryConventionPlugin.class); + + RepositoryHandler repositories = this.project.getRepositories(); + assertSnapshotRepository(repositories); + } + + @Test + public void applyWhenIsSnapshotWithForceReleaseThenShouldOnlyIncludeReleaseRepo() { + this.project.getExtensions().getByType(ExtraPropertiesExtension.class) + .set("forceMavenRepositories", "release"); + this.project.setVersion("1.0.0.RELEASE"); + this.project.getPluginManager().apply(RepositoryConventionPlugin.class); + + RepositoryHandler repositories = this.project.getRepositories(); + assertReleaseRepository(repositories); + } + + @Test + public void applyWhenIsReleaseWithForceMilestoneThenShouldIncludeMilestoneRepo() { + this.project.getExtensions().getByType(ExtraPropertiesExtension.class) + .set("forceMavenRepositories", "milestone"); + this.project.setVersion("1.0.0.RELEASE"); + this.project.getPluginManager().apply(RepositoryConventionPlugin.class); + + RepositoryHandler repositories = this.project.getRepositories(); + assertMilestoneRepository(repositories); + } + + @Test + public void applyWhenIsReleaseWithForceSnapshotThenShouldIncludeSnapshotRepo() { + this.project.getExtensions().getByType(ExtraPropertiesExtension.class) + .set("forceMavenRepositories", "snapshot"); + this.project.setVersion("1.0.0.RELEASE"); + this.project.getPluginManager().apply(RepositoryConventionPlugin.class); + + RepositoryHandler repositories = this.project.getRepositories(); + assertSnapshotRepository(repositories); + } + + @Test + public void applyWhenIsReleaseWithForceLocalThenShouldIncludeReleaseAndLocalRepos() { + this.project.getExtensions().getByType(ExtraPropertiesExtension.class) + .set("forceMavenRepositories", "local"); + this.project.setVersion("1.0.0.RELEASE"); + this.project.getPluginManager().apply(RepositoryConventionPlugin.class); + + RepositoryHandler repositories = this.project.getRepositories(); + assertThat(repositories).hasSize(5); + assertThat((repositories.get(0)).getName()).isEqualTo("MavenLocal"); + } + + @Test + public void applyWhenIsReleaseWithForceMilestoneAndLocalThenShouldIncludeMilestoneAndLocalRepos() { + this.project.getExtensions().getByType(ExtraPropertiesExtension.class) + .set("forceMavenRepositories", "milestone,local"); + this.project.setVersion("1.0.0.RELEASE"); + this.project.getPluginManager().apply(RepositoryConventionPlugin.class); + + RepositoryHandler repositories = this.project.getRepositories(); + assertThat(repositories).hasSize(6); + assertThat((repositories.get(0)).getName()).isEqualTo("MavenLocal"); + } + + private void assertSnapshotRepository(RepositoryHandler repositories) { + assertThat(repositories).extracting(ArtifactRepository::getName).hasSize(6); + assertThat(((MavenArtifactRepository) repositories.get(0)).getUrl().toString()) + .isEqualTo("https://repo.maven.apache.org/maven2/"); + assertThat(((MavenArtifactRepository) repositories.get(1)).getUrl().toString()) + .isEqualTo("https://jcenter.bintray.com/"); + assertThat(((MavenArtifactRepository) repositories.get(2)).getUrl().toString()) + .isEqualTo("https://repo.spring.io/snapshot/"); + assertThat(((MavenArtifactRepository) repositories.get(3)).getUrl().toString()) + .isEqualTo("https://repo.spring.io/milestone/"); + } + + private void assertMilestoneRepository(RepositoryHandler repositories) { + assertThat(repositories).extracting(ArtifactRepository::getName).hasSize(5); + assertThat(((MavenArtifactRepository) repositories.get(0)).getUrl().toString()) + .isEqualTo("https://repo.maven.apache.org/maven2/"); + assertThat(((MavenArtifactRepository) repositories.get(1)).getUrl().toString()) + .isEqualTo("https://jcenter.bintray.com/"); + assertThat(((MavenArtifactRepository) repositories.get(2)).getUrl().toString()) + .isEqualTo("https://repo.spring.io/milestone/"); + } + + private void assertReleaseRepository(RepositoryHandler repositories) { + assertThat(repositories).extracting(ArtifactRepository::getName).hasSize(4); + assertThat(((MavenArtifactRepository) repositories.get(0)).getUrl().toString()) + .isEqualTo("https://repo.maven.apache.org/maven2/"); + assertThat(((MavenArtifactRepository) repositories.get(1)).getUrl().toString()) + .isEqualTo("https://jcenter.bintray.com/"); + assertThat(((MavenArtifactRepository) repositories.get(2)).getUrl().toString()) + .isEqualTo("https://repo.spring.io/release/"); + } + +} diff --git a/buildSrc/src/test/java/io/spring/gradle/convention/ShowcaseITest.java b/buildSrc/src/test/java/io/spring/gradle/convention/ShowcaseITest.java new file mode 100644 index 00000000..469b190e --- /dev/null +++ b/buildSrc/src/test/java/io/spring/gradle/convention/ShowcaseITest.java @@ -0,0 +1,70 @@ +package io.spring.gradle.convention; + +import io.spring.gradle.TestKit; +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.TaskOutcome; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ShowcaseITest { + private TestKit testKit; + + @BeforeEach + void setup(@TempDir Path tempDir) { + this.testKit = new TestKit(tempDir.toFile()); + } + + @Test + public void build() throws Exception { + BuildResult result = this.testKit.withProjectResource("samples/showcase/") + .withArguments("build", "--stacktrace") + .forwardOutput() + .build(); + assertThat(result.getOutput()).contains("BUILD SUCCESSFUL"); + } + + @Test + @Disabled + public void install() throws Exception { + BuildResult result = this.testKit + .withProjectResource("samples/showcase/") + .withArguments("install", "--stacktrace") + .build(); + + assertThat(result.getOutput()).contains("SUCCESS"); + + File pom = new File(testKit.getRootDir(), "sgbcs-core/build/poms/pom-default.xml"); + assertThat(pom).exists(); + + String pomText = new String(Files.readAllBytes(pom.toPath())); + String pomTextNoSpace = pomText.replaceAll("\\s", ""); + + assertThat(pomText).doesNotContain(""); + + assertThat(pomTextNoSpace).contains("\n org.springframework\n spring-test\n test\n 4.3.6.RELEASE\n ".replaceAll("\\s", "")); + assertThat(pomTextNoSpace).contains("\n \n rwinch\n Rob Winch\n rwinch@pivotal.io\n \n \n jgrandja\n Joe Grandja\n jgrandja@pivotal.io\n \n ".replaceAll("\\s", "")); + assertThat(pomTextNoSpace).contains("\n scm:git:git://github.com/spring-projects/spring-security\n scm:git:git://github.com/spring-projects/spring-security\n https://github.com/spring-projects/spring-security\n ".replaceAll("\\s", "")); + assertThat(pomTextNoSpace).contains("sgbcs-core"); + assertThat(pomTextNoSpace).contains("https://spring.io/spring-security"); + assertThat(pomTextNoSpace).contains("\n spring.io\n https://spring.io/\n ".replaceAll("\\s", "")); + assertThat(pomTextNoSpace).contains(" \n \n The Apache Software License, Version 2.0\n https://www.apache.org/licenses/LICENSE-2.0.txt\n repo\n \n ".replaceAll("\\s", "")); + assertThat(pomTextNoSpace).contains("\n scm:git:git://github.com/spring-projects/spring-security\n scm:git:git://github.com/spring-projects/spring-security\n https://github.com/spring-projects/spring-security\n ".replaceAll("\\s", "")); + + File bom = new File(testKit.getRootDir(), "bom/build/poms/pom-default.xml"); + assertThat(bom).exists(); + assertThat(bom).hasContent("sgbcs-core"); + + BuildResult secondBuild = this.testKit.withProjectResource("samples/showcase/").withArguments("mavenBom", "--stacktrace").build(); + // mavenBom is not up to date since install is never up to date + assertThat(result.task(":bom:mavenBom").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + } + +} diff --git a/buildSrc/src/test/java/io/spring/gradle/convention/SpringMavenPluginITest.java b/buildSrc/src/test/java/io/spring/gradle/convention/SpringMavenPluginITest.java new file mode 100644 index 00000000..3f0855dd --- /dev/null +++ b/buildSrc/src/test/java/io/spring/gradle/convention/SpringMavenPluginITest.java @@ -0,0 +1,61 @@ +package io.spring.gradle.convention; + +import io.spring.gradle.TestKit; +import org.apache.commons.io.IOUtils; +import org.gradle.testkit.runner.BuildResult; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.LinkedHashMap; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SpringMavenPluginITest { + + private TestKit testKit; + + @BeforeEach + void setup(@TempDir Path tempDir) { + this.testKit = new TestKit(tempDir.toFile()); + } + + @Disabled + @Test + public void install() throws Exception { + BuildResult result = this.testKit.withProjectResource("samples/maven/install") + .withArguments("install") + .build(); + assertThat(result.getOutput()).contains("SUCCESS"); + File pom = new File(testKit.getRootDir(), "build/poms/pom-default.xml"); + assertThat(pom).exists(); + String pomText = new String(Files.readAllBytes(pom.toPath())); + assertThat(pomText.replaceAll("\\s", "")).contains("\n aopalliance\n aopalliance\n 1.0\n compile\n true\n ".replaceAll("\\s", "")); + } + + @Disabled + @Test + public void signArchivesWhenInMemory() throws Exception { + LinkedHashMap map = new LinkedHashMap(2); + map.put("ORG_GRADLE_PROJECT_signingKey", getSigningKey()); + map.put("ORG_GRADLE_PROJECT_signingPassword", "password"); + BuildResult result = this.testKit.withProjectResource("samples/maven/signing") + .withArguments("signArchives") + .withEnvironment(map) + .forwardOutput() + .build(); + assertThat(result.getOutput()).contains("SUCCESS"); + final File jar = new File(testKit.getRootDir(), "build/libs/signing-1.0.0.RELEASE.jar"); + assertThat(jar).exists(); + File signature = new File(jar.getAbsolutePath() + ".asc"); + assertThat(signature).exists(); + } + + public String getSigningKey() throws Exception { + return IOUtils.toString(getClass().getResource("/test-private.pgp")); + } +} diff --git a/buildSrc/src/test/java/io/spring/gradle/convention/TestsConfigurationPluginITest.java b/buildSrc/src/test/java/io/spring/gradle/convention/TestsConfigurationPluginITest.java new file mode 100644 index 00000000..4fd2944b --- /dev/null +++ b/buildSrc/src/test/java/io/spring/gradle/convention/TestsConfigurationPluginITest.java @@ -0,0 +1,31 @@ +package io.spring.gradle.convention; + +import io.spring.gradle.TestKit; +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.TaskOutcome; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Path; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TestsConfigurationPluginITest { + + private TestKit testKit; + + @BeforeEach + void setup(@TempDir Path tempDir) { + this.testKit = new TestKit(tempDir.toFile()); + } + + @Test + public void canFindDepencency() throws Exception { + BuildResult result = this.testKit.withProjectResource("samples/testsconfiguration") + .withArguments("check") + .build(); + assertThat(result.task(":web:check").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + } + +} diff --git a/buildSrc/src/test/java/io/spring/gradle/convention/UtilsTest.java b/buildSrc/src/test/java/io/spring/gradle/convention/UtilsTest.java new file mode 100644 index 00000000..0bed12b4 --- /dev/null +++ b/buildSrc/src/test/java/io/spring/gradle/convention/UtilsTest.java @@ -0,0 +1,147 @@ +package io.spring.gradle.convention; + + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import org.gradle.api.Project; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class UtilsTest { + @Mock + Project project; + @Mock + Project rootProject; + + @Test + public void getProjectName() { + when(project.getRootProject()).thenReturn(rootProject); + when(rootProject.getName()).thenReturn("spring-security"); + + assertThat(Utils.getProjectName(project)).isEqualTo("spring-security"); + } + + @Test + public void getProjectNameWhenEndsWithBuildThenStrippedOut() { + when(project.getRootProject()).thenReturn(rootProject); + when(rootProject.getName()).thenReturn("spring-security-build"); + + assertThat(Utils.getProjectName(project)).isEqualTo("spring-security"); + } + + @Test + public void isSnapshotValidWithDot() { + when(project.getVersion()).thenReturn("1.0.0.BUILD-SNAPSHOT"); + + assertThat(Utils.isSnapshot(project)).isTrue(); + } + + @Test + public void isSnapshotValidWithNoBuild() { + when(project.getVersion()).thenReturn("1.0.0-SNAPSHOT"); + + assertThat(Utils.isSnapshot(project)).isTrue(); + } + + @Test + public void isSnapshotValidWithDash() { + when(project.getVersion()).thenReturn("Theme-BUILD-SNAPSHOT"); + + assertThat(Utils.isSnapshot(project)).isTrue(); + } + + @Test + public void isSnapshotInvalid() { + when(project.getVersion()).thenReturn("1.0.0.SNAPSHOT"); + + assertThat(Utils.isSnapshot(project)).isFalse(); + } + + @Test + public void isMilestoneValidWithDot() { + when(project.getVersion()).thenReturn("1.0.0.M1"); + + assertThat(Utils.isMilestone(project)).isTrue(); + } + + @Test + public void isMilestoneValidWithDash() { + when(project.getVersion()).thenReturn("Theme-M1"); + + assertThat(Utils.isMilestone(project)).isTrue(); + } + + @Test + public void isMilestoneValidWithNumberDash() { + when(project.getVersion()).thenReturn("1.0.0-M1"); + + assertThat(Utils.isMilestone(project)).isTrue(); + } + + @Test + public void isMilestoneInvalid() { + when(project.getVersion()).thenReturn("1.0.0.M"); + + assertThat(Utils.isMilestone(project)).isFalse(); + } + + @Test + public void isReleaseCandidateValidWithDot() { + when(project.getVersion()).thenReturn("1.0.0.RC1"); + + assertThat(Utils.isMilestone(project)).isTrue(); + } + + @Test + public void isReleaseCandidateValidWithNumberDash() { + when(project.getVersion()).thenReturn("1.0.0-RC1"); + + assertThat(Utils.isMilestone(project)).isTrue(); + } + + @Test + public void isReleaseCandidateValidWithDash() { + when(project.getVersion()).thenReturn("Theme-RC1"); + + assertThat(Utils.isMilestone(project)).isTrue(); + } + + @Test + public void isReleaseCandidateInvalid() { + when(project.getVersion()).thenReturn("1.0.0.RC"); + + assertThat(Utils.isMilestone(project)).isFalse(); + } + + @Test + public void isReleaseValidWithDot() { + when(project.getVersion()).thenReturn("1.0.0.RELEASE"); + + assertThat(Utils.isRelease(project)).isTrue(); + } + + @Test + public void isReleaseValidWithNoRelease() { + when(project.getVersion()).thenReturn("1.0.0"); + + assertThat(Utils.isRelease(project)).isTrue(); + } + + @Test + public void isReleaseValidWithDash() { + when(project.getVersion()).thenReturn("Theme-RELEASE"); + + assertThat(Utils.isRelease(project)).isTrue(); + } + + @Test + public void isServiceReleaseValid() { + when(project.getVersion()).thenReturn("Theme-SR1"); + + assertThat(Utils.isRelease(project)).isTrue(); + } +} diff --git a/buildSrc/src/test/java/io/spring/gradle/convention/sagan/SaganApiTests.java b/buildSrc/src/test/java/io/spring/gradle/convention/sagan/SaganApiTests.java new file mode 100644 index 00000000..f4e8c11c --- /dev/null +++ b/buildSrc/src/test/java/io/spring/gradle/convention/sagan/SaganApiTests.java @@ -0,0 +1,85 @@ +/* + * 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 io.spring.gradle.convention.sagan; + +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.gradle.sagan.Release; +import org.springframework.gradle.sagan.SaganApi; + +import java.nio.charset.Charset; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; + + +public class SaganApiTests { + private MockWebServer server; + + private SaganApi sagan; + + private String baseUrl; + + @BeforeEach + public void setup() throws Exception { + this.server = new MockWebServer(); + this.server.start(); + this.sagan = new SaganApi("mock-oauth-token"); + this.baseUrl = this.server.url("/api").toString(); + this.sagan.setBaseUrl(this.baseUrl); + } + + @AfterEach + public void cleanup() throws Exception { + this.server.shutdown(); + } + + @Test + public void createWhenValidThenNoException() throws Exception { + this.server.enqueue(new MockResponse()); + Release release = new Release(); + release.setVersion("5.6.0-SNAPSHOT"); + release.setApiDocUrl("https://docs.spring.io/spring-security/site/docs/{version}/api/"); + release.setReferenceDocUrl("https://docs.spring.io/spring-security/site/docs/{version}/reference/html5/"); + this.sagan.createReleaseForProject(release, "spring-security"); + RecordedRequest request = this.server.takeRequest(1, TimeUnit.SECONDS); + assertThat(request.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/projects/spring-security/releases"); + assertThat(request.getMethod()).isEqualToIgnoringCase("post"); + assertThat(request.getHeaders().get("Authorization")).isEqualTo("Basic bm90LXVzZWQ6bW9jay1vYXV0aC10b2tlbg=="); + assertThat(request.getBody().readString(Charset.defaultCharset())).isEqualToIgnoringWhitespace("{\n" + + " \"version\":\"5.6.0-SNAPSHOT\",\n" + + " \"current\":false,\n" + + " \"referenceDocUrl\":\"https://docs.spring.io/spring-security/site/docs/{version}/reference/html5/\",\n" + + " \"apiDocUrl\":\"https://docs.spring.io/spring-security/site/docs/{version}/api/\"\n" + + "}"); + } + + @Test + public void deleteWhenValidThenNoException() throws Exception { + this.server.enqueue(new MockResponse()); + this.sagan.deleteReleaseForProject("5.6.0-SNAPSHOT", "spring-security"); + RecordedRequest request = this.server.takeRequest(1, TimeUnit.SECONDS); + assertThat(request.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/projects/spring-security/releases/5.6.0-SNAPSHOT"); + assertThat(request.getMethod()).isEqualToIgnoringCase("delete"); + assertThat(request.getHeaders().get("Authorization")).isEqualTo("Basic bm90LXVzZWQ6bW9jay1vYXV0aC10b2tlbg=="); + assertThat(request.getBody().readString(Charset.defaultCharset())).isEmpty(); + } +} diff --git a/buildSrc/src/test/java/io/spring/gradle/github/milestones/GitHubMilestoneApiTests.java b/buildSrc/src/test/java/io/spring/gradle/github/milestones/GitHubMilestoneApiTests.java new file mode 100644 index 00000000..183cf09d --- /dev/null +++ b/buildSrc/src/test/java/io/spring/gradle/github/milestones/GitHubMilestoneApiTests.java @@ -0,0 +1,388 @@ +package io.spring.gradle.github.milestones; + +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.gradle.github.milestones.GitHubMilestoneApi; +import org.springframework.gradle.github.milestones.RepositoryRef; + +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + + +public class GitHubMilestoneApiTests { + private GitHubMilestoneApi github; + + private RepositoryRef repositoryRef = RepositoryRef.owner("spring-projects").repository("spring-security").build(); + + private MockWebServer server; + + private String baseUrl; + + @BeforeEach + public void setup() throws Exception { + this.server = new MockWebServer(); + this.server.start(); + this.github = new GitHubMilestoneApi("mock-oauth-token"); + this.baseUrl = this.server.url("/api").toString(); + this.github.setBaseUrl(this.baseUrl); + } + + @AfterEach + public void cleanup() throws Exception { + this.server.shutdown(); + } + + @Test + public void findMilestoneNumberByTitleWhenFoundThenSuccess() throws Exception { + String responseJson = "[\n" + + " {\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" + + " \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" + + " \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" + + " \"id\":6611880,\n" + + " \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" + + " \"number\":207,\n" + + " \"title\":\"5.6.x\",\n" + + " \"description\":\"\",\n" + + " \"creator\":{\n" + + " \"login\":\"jgrandja\",\n" + + " \"id\":10884212,\n" + + " \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" + + " \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" + + " \"gravatar_id\":\"\",\n" + + " \"url\":\"https://api.github.com/users/jgrandja\",\n" + + " \"html_url\":\"https://github.com/jgrandja\",\n" + + " \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" + + " \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" + + " \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" + + " \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" + + " \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" + + " \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" + + " \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" + + " \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" + + " \"type\":\"User\",\n" + + " \"site_admin\":false\n" + + " },\n" + + " \"open_issues\":1,\n" + + " \"closed_issues\":0,\n" + + " \"state\":\"open\",\n" + + " \"created_at\":\"2021-03-31T11:29:17Z\",\n" + + " \"updated_at\":\"2021-03-31T11:30:47Z\",\n" + + " \"due_on\":null,\n" + + " \"closed_at\":null\n" + + " },\n" + + " {\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" + + " \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" + + " \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" + + " \"id\":5884208,\n" + + " \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" + + " \"number\":191,\n" + + " \"title\":\"5.5.0-RC1\",\n" + + " \"description\":\"\",\n" + + " \"creator\":{\n" + + " \"login\":\"jzheaux\",\n" + + " \"id\":3627351,\n" + + " \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" + + " \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" + + " \"gravatar_id\":\"\",\n" + + " \"url\":\"https://api.github.com/users/jzheaux\",\n" + + " \"html_url\":\"https://github.com/jzheaux\",\n" + + " \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" + + " \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" + + " \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" + + " \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" + + " \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" + + " \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" + + " \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" + + " \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" + + " \"type\":\"User\",\n" + + " \"site_admin\":false\n" + + " },\n" + + " \"open_issues\":21,\n" + + " \"closed_issues\":23,\n" + + " \"state\":\"open\",\n" + + " \"created_at\":\"2020-09-16T13:28:03Z\",\n" + + " \"updated_at\":\"2021-04-06T23:47:10Z\",\n" + + " \"due_on\":\"2021-04-12T07:00:00Z\",\n" + + " \"closed_at\":null\n" + + " }\n" + + "]"; + this.server.enqueue(new MockResponse().setBody(responseJson)); + + long milestoneNumberByTitle = this.github.findMilestoneNumberByTitle(this.repositoryRef, "5.5.0-RC1"); + + RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS); + assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get"); + assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/milestones?per_page=100"); + + assertThat(milestoneNumberByTitle).isEqualTo(191); + } + + @Test + public void findMilestoneNumberByTitleWhenNotFoundThenException() throws Exception { + String responseJson = "[\n" + + " {\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" + + " \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" + + " \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" + + " \"id\":6611880,\n" + + " \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" + + " \"number\":207,\n" + + " \"title\":\"5.6.x\",\n" + + " \"description\":\"\",\n" + + " \"creator\":{\n" + + " \"login\":\"jgrandja\",\n" + + " \"id\":10884212,\n" + + " \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" + + " \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" + + " \"gravatar_id\":\"\",\n" + + " \"url\":\"https://api.github.com/users/jgrandja\",\n" + + " \"html_url\":\"https://github.com/jgrandja\",\n" + + " \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" + + " \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" + + " \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" + + " \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" + + " \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" + + " \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" + + " \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" + + " \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" + + " \"type\":\"User\",\n" + + " \"site_admin\":false\n" + + " },\n" + + " \"open_issues\":1,\n" + + " \"closed_issues\":0,\n" + + " \"state\":\"open\",\n" + + " \"created_at\":\"2021-03-31T11:29:17Z\",\n" + + " \"updated_at\":\"2021-03-31T11:30:47Z\",\n" + + " \"due_on\":null,\n" + + " \"closed_at\":null\n" + + " },\n" + + " {\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" + + " \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" + + " \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" + + " \"id\":5884208,\n" + + " \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" + + " \"number\":191,\n" + + " \"title\":\"5.5.0-RC1\",\n" + + " \"description\":\"\",\n" + + " \"creator\":{\n" + + " \"login\":\"jzheaux\",\n" + + " \"id\":3627351,\n" + + " \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" + + " \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" + + " \"gravatar_id\":\"\",\n" + + " \"url\":\"https://api.github.com/users/jzheaux\",\n" + + " \"html_url\":\"https://github.com/jzheaux\",\n" + + " \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" + + " \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" + + " \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" + + " \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" + + " \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" + + " \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" + + " \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" + + " \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" + + " \"type\":\"User\",\n" + + " \"site_admin\":false\n" + + " },\n" + + " \"open_issues\":21,\n" + + " \"closed_issues\":23,\n" + + " \"state\":\"open\",\n" + + " \"created_at\":\"2020-09-16T13:28:03Z\",\n" + + " \"updated_at\":\"2021-04-06T23:47:10Z\",\n" + + " \"due_on\":\"2021-04-12T07:00:00Z\",\n" + + " \"closed_at\":null\n" + + " }\n" + + "]"; + this.server.enqueue(new MockResponse().setBody(responseJson)); + + assertThatExceptionOfType(RuntimeException.class) + .isThrownBy(() -> this.github.findMilestoneNumberByTitle(this.repositoryRef, "missing")); + } + + @Test + public void isOpenIssuesForMilestoneNumberWhenAllClosedThenFalse() throws Exception { + String responseJson = "[]"; + long milestoneNumber = 202; + this.server.enqueue(new MockResponse().setBody(responseJson)); + + assertThat(this.github.isOpenIssuesForMilestoneNumber(this.repositoryRef, milestoneNumber)).isFalse(); + + RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS); + assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get"); + assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/issues?per_page=1&milestone=" + milestoneNumber); + } + + @Test + public void isOpenIssuesForMilestoneNumberWhenOpenIssuesThenTrue() throws Exception { + String responseJson = "[\n" + + " {\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/issues/9562\",\n" + + " \"repository_url\":\"https://api.github.com/repos/spring-projects/spring-security\",\n" + + " \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/issues/9562/labels{/name}\",\n" + + " \"comments_url\":\"https://api.github.com/repos/spring-projects/spring-security/issues/9562/comments\",\n" + + " \"events_url\":\"https://api.github.com/repos/spring-projects/spring-security/issues/9562/events\",\n" + + " \"html_url\":\"https://github.com/spring-projects/spring-security/pull/9562\",\n" + + " \"id\":851886504,\n" + + " \"node_id\":\"MDExOlB1bGxSZXF1ZXN0NjEwMjMzMDcw\",\n" + + " \"number\":9562,\n" + + " \"title\":\"Add package-list\",\n" + + " \"user\":{\n" + + " \"login\":\"jzheaux\",\n" + + " \"id\":3627351,\n" + + " \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" + + " \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" + + " \"gravatar_id\":\"\",\n" + + " \"url\":\"https://api.github.com/users/jzheaux\",\n" + + " \"html_url\":\"https://github.com/jzheaux\",\n" + + " \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" + + " \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" + + " \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" + + " \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" + + " \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" + + " \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" + + " \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" + + " \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" + + " \"type\":\"User\",\n" + + " \"site_admin\":false\n" + + " },\n" + + " \"labels\":[\n" + + " {\n" + + " \"id\":322225043,\n" + + " \"node_id\":\"MDU6TGFiZWwzMjIyMjUwNDM=\",\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/labels/in:%20build\",\n" + + " \"name\":\"in: build\",\n" + + " \"color\":\"e8f9de\",\n" + + " \"default\":false,\n" + + " \"description\":\"An issue in the build\"\n" + + " },\n" + + " {\n" + + " \"id\":322225079,\n" + + " \"node_id\":\"MDU6TGFiZWwzMjIyMjUwNzk=\",\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/labels/type:%20bug\",\n" + + " \"name\":\"type: bug\",\n" + + " \"color\":\"e3d9fc\",\n" + + " \"default\":false,\n" + + " \"description\":\"A general bug\"\n" + + " }\n" + + " ],\n" + + " \"state\":\"open\",\n" + + " \"locked\":false,\n" + + " \"assignee\":{\n" + + " \"login\":\"rwinch\",\n" + + " \"id\":362503,\n" + + " \"node_id\":\"MDQ6VXNlcjM2MjUwMw==\",\n" + + " \"avatar_url\":\"https://avatars.githubusercontent.com/u/362503?v=4\",\n" + + " \"gravatar_id\":\"\",\n" + + " \"url\":\"https://api.github.com/users/rwinch\",\n" + + " \"html_url\":\"https://github.com/rwinch\",\n" + + " \"followers_url\":\"https://api.github.com/users/rwinch/followers\",\n" + + " \"following_url\":\"https://api.github.com/users/rwinch/following{/other_user}\",\n" + + " \"gists_url\":\"https://api.github.com/users/rwinch/gists{/gist_id}\",\n" + + " \"starred_url\":\"https://api.github.com/users/rwinch/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\":\"https://api.github.com/users/rwinch/subscriptions\",\n" + + " \"organizations_url\":\"https://api.github.com/users/rwinch/orgs\",\n" + + " \"repos_url\":\"https://api.github.com/users/rwinch/repos\",\n" + + " \"events_url\":\"https://api.github.com/users/rwinch/events{/privacy}\",\n" + + " \"received_events_url\":\"https://api.github.com/users/rwinch/received_events\",\n" + + " \"type\":\"User\",\n" + + " \"site_admin\":false\n" + + " },\n" + + " \"assignees\":[\n" + + " {\n" + + " \"login\":\"rwinch\",\n" + + " \"id\":362503,\n" + + " \"node_id\":\"MDQ6VXNlcjM2MjUwMw==\",\n" + + " \"avatar_url\":\"https://avatars.githubusercontent.com/u/362503?v=4\",\n" + + " \"gravatar_id\":\"\",\n" + + " \"url\":\"https://api.github.com/users/rwinch\",\n" + + " \"html_url\":\"https://github.com/rwinch\",\n" + + " \"followers_url\":\"https://api.github.com/users/rwinch/followers\",\n" + + " \"following_url\":\"https://api.github.com/users/rwinch/following{/other_user}\",\n" + + " \"gists_url\":\"https://api.github.com/users/rwinch/gists{/gist_id}\",\n" + + " \"starred_url\":\"https://api.github.com/users/rwinch/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\":\"https://api.github.com/users/rwinch/subscriptions\",\n" + + " \"organizations_url\":\"https://api.github.com/users/rwinch/orgs\",\n" + + " \"repos_url\":\"https://api.github.com/users/rwinch/repos\",\n" + + " \"events_url\":\"https://api.github.com/users/rwinch/events{/privacy}\",\n" + + " \"received_events_url\":\"https://api.github.com/users/rwinch/received_events\",\n" + + " \"type\":\"User\",\n" + + " \"site_admin\":false\n" + + " }\n" + + " ],\n" + + " \"milestone\":{\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" + + " \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" + + " \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" + + " \"id\":5884208,\n" + + " \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" + + " \"number\":191,\n" + + " \"title\":\"5.5.0-RC1\",\n" + + " \"description\":\"\",\n" + + " \"creator\":{\n" + + " \"login\":\"jzheaux\",\n" + + " \"id\":3627351,\n" + + " \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" + + " \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" + + " \"gravatar_id\":\"\",\n" + + " \"url\":\"https://api.github.com/users/jzheaux\",\n" + + " \"html_url\":\"https://github.com/jzheaux\",\n" + + " \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" + + " \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" + + " \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" + + " \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" + + " \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" + + " \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" + + " \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" + + " \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" + + " \"type\":\"User\",\n" + + " \"site_admin\":false\n" + + " },\n" + + " \"open_issues\":21,\n" + + " \"closed_issues\":23,\n" + + " \"state\":\"open\",\n" + + " \"created_at\":\"2020-09-16T13:28:03Z\",\n" + + " \"updated_at\":\"2021-04-06T23:47:10Z\",\n" + + " \"due_on\":\"2021-04-12T07:00:00Z\",\n" + + " \"closed_at\":null\n" + + " },\n" + + " \"comments\":0,\n" + + " \"created_at\":\"2021-04-06T23:47:10Z\",\n" + + " \"updated_at\":\"2021-04-07T17:00:00Z\",\n" + + " \"closed_at\":null,\n" + + " \"author_association\":\"MEMBER\",\n" + + " \"active_lock_reason\":null,\n" + + " \"pull_request\":{\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/pulls/9562\",\n" + + " \"html_url\":\"https://github.com/spring-projects/spring-security/pull/9562\",\n" + + " \"diff_url\":\"https://github.com/spring-projects/spring-security/pull/9562.diff\",\n" + + " \"patch_url\":\"https://github.com/spring-projects/spring-security/pull/9562.patch\"\n" + + " },\n" + + " \"body\":\"Closes gh-9528\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\",\n" + + " \"performed_via_github_app\":null\n" + + " }\n" + + "]"; + long milestoneNumber = 191; + this.server.enqueue(new MockResponse().setBody(responseJson)); + + assertThat(this.github.isOpenIssuesForMilestoneNumber(this.repositoryRef, milestoneNumber)).isTrue(); + + RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS); + assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get"); + assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/issues?per_page=1&milestone=" + milestoneNumber); + } + +} diff --git a/buildSrc/src/test/java/org/springframework/gradle/github/milestones/GitHubMilestoneApiTests.java b/buildSrc/src/test/java/org/springframework/gradle/github/milestones/GitHubMilestoneApiTests.java new file mode 100644 index 00000000..b4072c07 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/gradle/github/milestones/GitHubMilestoneApiTests.java @@ -0,0 +1,386 @@ +package org.springframework.gradle.github.milestones; + +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + + +public class GitHubMilestoneApiTests { + private GitHubMilestoneApi github; + + private RepositoryRef repositoryRef = RepositoryRef.owner("spring-projects").repository("spring-security").build(); + + private MockWebServer server; + + private String baseUrl; + + @BeforeEach + public void setup() throws Exception { + this.server = new MockWebServer(); + this.server.start(); + this.github = new GitHubMilestoneApi("mock-oauth-token"); + this.baseUrl = this.server.url("/api").toString(); + this.github.setBaseUrl(this.baseUrl); + } + + @AfterEach + public void cleanup() throws Exception { + this.server.shutdown(); + } + + @Test + public void findMilestoneNumberByTitleWhenFoundThenSuccess() throws Exception { + String responseJson = "[\n" + + " {\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" + + " \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" + + " \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" + + " \"id\":6611880,\n" + + " \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" + + " \"number\":207,\n" + + " \"title\":\"5.6.x\",\n" + + " \"description\":\"\",\n" + + " \"creator\":{\n" + + " \"login\":\"jgrandja\",\n" + + " \"id\":10884212,\n" + + " \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" + + " \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" + + " \"gravatar_id\":\"\",\n" + + " \"url\":\"https://api.github.com/users/jgrandja\",\n" + + " \"html_url\":\"https://github.com/jgrandja\",\n" + + " \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" + + " \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" + + " \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" + + " \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" + + " \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" + + " \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" + + " \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" + + " \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" + + " \"type\":\"User\",\n" + + " \"site_admin\":false\n" + + " },\n" + + " \"open_issues\":1,\n" + + " \"closed_issues\":0,\n" + + " \"state\":\"open\",\n" + + " \"created_at\":\"2021-03-31T11:29:17Z\",\n" + + " \"updated_at\":\"2021-03-31T11:30:47Z\",\n" + + " \"due_on\":null,\n" + + " \"closed_at\":null\n" + + " },\n" + + " {\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" + + " \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" + + " \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" + + " \"id\":5884208,\n" + + " \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" + + " \"number\":191,\n" + + " \"title\":\"5.5.0-RC1\",\n" + + " \"description\":\"\",\n" + + " \"creator\":{\n" + + " \"login\":\"jzheaux\",\n" + + " \"id\":3627351,\n" + + " \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" + + " \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" + + " \"gravatar_id\":\"\",\n" + + " \"url\":\"https://api.github.com/users/jzheaux\",\n" + + " \"html_url\":\"https://github.com/jzheaux\",\n" + + " \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" + + " \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" + + " \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" + + " \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" + + " \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" + + " \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" + + " \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" + + " \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" + + " \"type\":\"User\",\n" + + " \"site_admin\":false\n" + + " },\n" + + " \"open_issues\":21,\n" + + " \"closed_issues\":23,\n" + + " \"state\":\"open\",\n" + + " \"created_at\":\"2020-09-16T13:28:03Z\",\n" + + " \"updated_at\":\"2021-04-06T23:47:10Z\",\n" + + " \"due_on\":\"2021-04-12T07:00:00Z\",\n" + + " \"closed_at\":null\n" + + " }\n" + + "]"; + this.server.enqueue(new MockResponse().setBody(responseJson)); + + long milestoneNumberByTitle = this.github.findMilestoneNumberByTitle(this.repositoryRef, "5.5.0-RC1"); + + RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS); + assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get"); + assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/milestones?per_page=100"); + + assertThat(milestoneNumberByTitle).isEqualTo(191); + } + + @Test + public void findMilestoneNumberByTitleWhenNotFoundThenException() throws Exception { + String responseJson = "[\n" + + " {\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" + + " \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" + + " \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" + + " \"id\":6611880,\n" + + " \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" + + " \"number\":207,\n" + + " \"title\":\"5.6.x\",\n" + + " \"description\":\"\",\n" + + " \"creator\":{\n" + + " \"login\":\"jgrandja\",\n" + + " \"id\":10884212,\n" + + " \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" + + " \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" + + " \"gravatar_id\":\"\",\n" + + " \"url\":\"https://api.github.com/users/jgrandja\",\n" + + " \"html_url\":\"https://github.com/jgrandja\",\n" + + " \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" + + " \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" + + " \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" + + " \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" + + " \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" + + " \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" + + " \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" + + " \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" + + " \"type\":\"User\",\n" + + " \"site_admin\":false\n" + + " },\n" + + " \"open_issues\":1,\n" + + " \"closed_issues\":0,\n" + + " \"state\":\"open\",\n" + + " \"created_at\":\"2021-03-31T11:29:17Z\",\n" + + " \"updated_at\":\"2021-03-31T11:30:47Z\",\n" + + " \"due_on\":null,\n" + + " \"closed_at\":null\n" + + " },\n" + + " {\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" + + " \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" + + " \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" + + " \"id\":5884208,\n" + + " \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" + + " \"number\":191,\n" + + " \"title\":\"5.5.0-RC1\",\n" + + " \"description\":\"\",\n" + + " \"creator\":{\n" + + " \"login\":\"jzheaux\",\n" + + " \"id\":3627351,\n" + + " \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" + + " \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" + + " \"gravatar_id\":\"\",\n" + + " \"url\":\"https://api.github.com/users/jzheaux\",\n" + + " \"html_url\":\"https://github.com/jzheaux\",\n" + + " \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" + + " \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" + + " \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" + + " \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" + + " \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" + + " \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" + + " \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" + + " \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" + + " \"type\":\"User\",\n" + + " \"site_admin\":false\n" + + " },\n" + + " \"open_issues\":21,\n" + + " \"closed_issues\":23,\n" + + " \"state\":\"open\",\n" + + " \"created_at\":\"2020-09-16T13:28:03Z\",\n" + + " \"updated_at\":\"2021-04-06T23:47:10Z\",\n" + + " \"due_on\":\"2021-04-12T07:00:00Z\",\n" + + " \"closed_at\":null\n" + + " }\n" + + "]"; + this.server.enqueue(new MockResponse().setBody(responseJson)); + + assertThatExceptionOfType(RuntimeException.class) + .isThrownBy(() -> this.github.findMilestoneNumberByTitle(this.repositoryRef, "missing")); + } + + @Test + public void isOpenIssuesForMilestoneNumberWhenAllClosedThenFalse() throws Exception { + String responseJson = "[]"; + long milestoneNumber = 202; + this.server.enqueue(new MockResponse().setBody(responseJson)); + + assertThat(this.github.isOpenIssuesForMilestoneNumber(this.repositoryRef, milestoneNumber)).isFalse(); + + RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS); + assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get"); + assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/issues?per_page=1&milestone=" + milestoneNumber); + } + + @Test + public void isOpenIssuesForMilestoneNumberWhenOpenIssuesThenTrue() throws Exception { + String responseJson = "[\n" + + " {\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/issues/9562\",\n" + + " \"repository_url\":\"https://api.github.com/repos/spring-projects/spring-security\",\n" + + " \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/issues/9562/labels{/name}\",\n" + + " \"comments_url\":\"https://api.github.com/repos/spring-projects/spring-security/issues/9562/comments\",\n" + + " \"events_url\":\"https://api.github.com/repos/spring-projects/spring-security/issues/9562/events\",\n" + + " \"html_url\":\"https://github.com/spring-projects/spring-security/pull/9562\",\n" + + " \"id\":851886504,\n" + + " \"node_id\":\"MDExOlB1bGxSZXF1ZXN0NjEwMjMzMDcw\",\n" + + " \"number\":9562,\n" + + " \"title\":\"Add package-list\",\n" + + " \"user\":{\n" + + " \"login\":\"jzheaux\",\n" + + " \"id\":3627351,\n" + + " \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" + + " \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" + + " \"gravatar_id\":\"\",\n" + + " \"url\":\"https://api.github.com/users/jzheaux\",\n" + + " \"html_url\":\"https://github.com/jzheaux\",\n" + + " \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" + + " \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" + + " \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" + + " \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" + + " \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" + + " \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" + + " \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" + + " \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" + + " \"type\":\"User\",\n" + + " \"site_admin\":false\n" + + " },\n" + + " \"labels\":[\n" + + " {\n" + + " \"id\":322225043,\n" + + " \"node_id\":\"MDU6TGFiZWwzMjIyMjUwNDM=\",\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/labels/in:%20build\",\n" + + " \"name\":\"in: build\",\n" + + " \"color\":\"e8f9de\",\n" + + " \"default\":false,\n" + + " \"description\":\"An issue in the build\"\n" + + " },\n" + + " {\n" + + " \"id\":322225079,\n" + + " \"node_id\":\"MDU6TGFiZWwzMjIyMjUwNzk=\",\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/labels/type:%20bug\",\n" + + " \"name\":\"type: bug\",\n" + + " \"color\":\"e3d9fc\",\n" + + " \"default\":false,\n" + + " \"description\":\"A general bug\"\n" + + " }\n" + + " ],\n" + + " \"state\":\"open\",\n" + + " \"locked\":false,\n" + + " \"assignee\":{\n" + + " \"login\":\"rwinch\",\n" + + " \"id\":362503,\n" + + " \"node_id\":\"MDQ6VXNlcjM2MjUwMw==\",\n" + + " \"avatar_url\":\"https://avatars.githubusercontent.com/u/362503?v=4\",\n" + + " \"gravatar_id\":\"\",\n" + + " \"url\":\"https://api.github.com/users/rwinch\",\n" + + " \"html_url\":\"https://github.com/rwinch\",\n" + + " \"followers_url\":\"https://api.github.com/users/rwinch/followers\",\n" + + " \"following_url\":\"https://api.github.com/users/rwinch/following{/other_user}\",\n" + + " \"gists_url\":\"https://api.github.com/users/rwinch/gists{/gist_id}\",\n" + + " \"starred_url\":\"https://api.github.com/users/rwinch/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\":\"https://api.github.com/users/rwinch/subscriptions\",\n" + + " \"organizations_url\":\"https://api.github.com/users/rwinch/orgs\",\n" + + " \"repos_url\":\"https://api.github.com/users/rwinch/repos\",\n" + + " \"events_url\":\"https://api.github.com/users/rwinch/events{/privacy}\",\n" + + " \"received_events_url\":\"https://api.github.com/users/rwinch/received_events\",\n" + + " \"type\":\"User\",\n" + + " \"site_admin\":false\n" + + " },\n" + + " \"assignees\":[\n" + + " {\n" + + " \"login\":\"rwinch\",\n" + + " \"id\":362503,\n" + + " \"node_id\":\"MDQ6VXNlcjM2MjUwMw==\",\n" + + " \"avatar_url\":\"https://avatars.githubusercontent.com/u/362503?v=4\",\n" + + " \"gravatar_id\":\"\",\n" + + " \"url\":\"https://api.github.com/users/rwinch\",\n" + + " \"html_url\":\"https://github.com/rwinch\",\n" + + " \"followers_url\":\"https://api.github.com/users/rwinch/followers\",\n" + + " \"following_url\":\"https://api.github.com/users/rwinch/following{/other_user}\",\n" + + " \"gists_url\":\"https://api.github.com/users/rwinch/gists{/gist_id}\",\n" + + " \"starred_url\":\"https://api.github.com/users/rwinch/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\":\"https://api.github.com/users/rwinch/subscriptions\",\n" + + " \"organizations_url\":\"https://api.github.com/users/rwinch/orgs\",\n" + + " \"repos_url\":\"https://api.github.com/users/rwinch/repos\",\n" + + " \"events_url\":\"https://api.github.com/users/rwinch/events{/privacy}\",\n" + + " \"received_events_url\":\"https://api.github.com/users/rwinch/received_events\",\n" + + " \"type\":\"User\",\n" + + " \"site_admin\":false\n" + + " }\n" + + " ],\n" + + " \"milestone\":{\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" + + " \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" + + " \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" + + " \"id\":5884208,\n" + + " \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" + + " \"number\":191,\n" + + " \"title\":\"5.5.0-RC1\",\n" + + " \"description\":\"\",\n" + + " \"creator\":{\n" + + " \"login\":\"jzheaux\",\n" + + " \"id\":3627351,\n" + + " \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" + + " \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" + + " \"gravatar_id\":\"\",\n" + + " \"url\":\"https://api.github.com/users/jzheaux\",\n" + + " \"html_url\":\"https://github.com/jzheaux\",\n" + + " \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" + + " \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" + + " \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" + + " \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" + + " \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" + + " \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" + + " \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" + + " \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" + + " \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" + + " \"type\":\"User\",\n" + + " \"site_admin\":false\n" + + " },\n" + + " \"open_issues\":21,\n" + + " \"closed_issues\":23,\n" + + " \"state\":\"open\",\n" + + " \"created_at\":\"2020-09-16T13:28:03Z\",\n" + + " \"updated_at\":\"2021-04-06T23:47:10Z\",\n" + + " \"due_on\":\"2021-04-12T07:00:00Z\",\n" + + " \"closed_at\":null\n" + + " },\n" + + " \"comments\":0,\n" + + " \"created_at\":\"2021-04-06T23:47:10Z\",\n" + + " \"updated_at\":\"2021-04-07T17:00:00Z\",\n" + + " \"closed_at\":null,\n" + + " \"author_association\":\"MEMBER\",\n" + + " \"active_lock_reason\":null,\n" + + " \"pull_request\":{\n" + + " \"url\":\"https://api.github.com/repos/spring-projects/spring-security/pulls/9562\",\n" + + " \"html_url\":\"https://github.com/spring-projects/spring-security/pull/9562\",\n" + + " \"diff_url\":\"https://github.com/spring-projects/spring-security/pull/9562.diff\",\n" + + " \"patch_url\":\"https://github.com/spring-projects/spring-security/pull/9562.patch\"\n" + + " },\n" + + " \"body\":\"Closes gh-9528\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\",\n" + + " \"performed_via_github_app\":null\n" + + " }\n" + + "]"; + long milestoneNumber = 191; + this.server.enqueue(new MockResponse().setBody(responseJson)); + + assertThat(this.github.isOpenIssuesForMilestoneNumber(this.repositoryRef, milestoneNumber)).isTrue(); + + RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS); + assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get"); + assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/issues?per_page=1&milestone=" + milestoneNumber); + } + +} diff --git a/buildSrc/src/test/resources/samples/integrationtest/withgroovy/build.gradle b/buildSrc/src/test/resources/samples/integrationtest/withgroovy/build.gradle new file mode 100644 index 00000000..d5dd6923 --- /dev/null +++ b/buildSrc/src/test/resources/samples/integrationtest/withgroovy/build.gradle @@ -0,0 +1,16 @@ +plugins { + id 'io.spring.convention.integration-test' +} + +apply plugin: 'java' +apply plugin: 'groovy' + +repositories { + mavenCentral() +} + +dependencies { + testCompile 'junit:junit:4.12' + testCompile 'org.spockframework:spock-core:1.0-groovy-2.4' + integrationTestCompile 'org.springframework:spring-core:4.3.7.RELEASE' +} \ No newline at end of file diff --git a/buildSrc/src/test/resources/samples/integrationtest/withgroovy/src/integration-test/groovy/sample/TheTest.groovy b/buildSrc/src/test/resources/samples/integrationtest/withgroovy/src/integration-test/groovy/sample/TheTest.groovy new file mode 100644 index 00000000..d3a9898b --- /dev/null +++ b/buildSrc/src/test/resources/samples/integrationtest/withgroovy/src/integration-test/groovy/sample/TheTest.groovy @@ -0,0 +1,31 @@ +/* + * 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 sample; + +import org.springframework.core.Ordered; +import spock.lang.Specification; + +class TheTest extends Specification { + def "has Ordered"() { + expect: 'Loads Ordered fine' + Ordered ordered = new Ordered() { + @Override + int getOrder() { + return 0 + } + } + } +} \ No newline at end of file diff --git a/buildSrc/src/test/resources/samples/integrationtest/withjava/build.gradle b/buildSrc/src/test/resources/samples/integrationtest/withjava/build.gradle new file mode 100644 index 00000000..69fe9fa8 --- /dev/null +++ b/buildSrc/src/test/resources/samples/integrationtest/withjava/build.gradle @@ -0,0 +1,14 @@ +plugins { + id 'io.spring.convention.integration-test' +} + +apply plugin: 'java' + +repositories { + mavenCentral() +} + +dependencies { + testCompile 'junit:junit:4.12' + integrationTestCompile 'org.springframework:spring-core:4.3.7.RELEASE' +} \ No newline at end of file diff --git a/buildSrc/src/test/resources/samples/integrationtest/withjava/src/integration-test/java/sample/TheTest.java b/buildSrc/src/test/resources/samples/integrationtest/withjava/src/integration-test/java/sample/TheTest.java new file mode 100644 index 00000000..dc82588c --- /dev/null +++ b/buildSrc/src/test/resources/samples/integrationtest/withjava/src/integration-test/java/sample/TheTest.java @@ -0,0 +1,16 @@ +package sample; + +import org.junit.Test; +import org.springframework.core.Ordered; + +public class TheTest { + @Test + public void compilesAndRuns() { + Ordered ordered = new Ordered() { + @Override + public int getOrder() { + return 0; + } + }; + } +} \ No newline at end of file diff --git a/buildSrc/src/test/resources/samples/integrationtest/withpropdeps/build.gradle b/buildSrc/src/test/resources/samples/integrationtest/withpropdeps/build.gradle new file mode 100644 index 00000000..732278d0 --- /dev/null +++ b/buildSrc/src/test/resources/samples/integrationtest/withpropdeps/build.gradle @@ -0,0 +1,14 @@ +plugins { + id 'io.spring.convention.integration-test' +} + +apply plugin: 'java' + +repositories { + mavenCentral() +} + +dependencies { + optional 'javax.servlet:javax.servlet-api:3.1.0' + testCompile 'junit:junit:4.12' +} \ No newline at end of file diff --git a/buildSrc/src/test/resources/samples/integrationtest/withpropdeps/src/integration-test/java/sample/TheTest.java b/buildSrc/src/test/resources/samples/integrationtest/withpropdeps/src/integration-test/java/sample/TheTest.java new file mode 100644 index 00000000..de492ca0 --- /dev/null +++ b/buildSrc/src/test/resources/samples/integrationtest/withpropdeps/src/integration-test/java/sample/TheTest.java @@ -0,0 +1,11 @@ +package sample; + +import org.junit.Test; +import javax.servlet.http.HttpServletRequest; + +public class TheTest { + @Test + public void compilesAndRuns() { + HttpServletRequest request = null; + } +} \ No newline at end of file diff --git a/buildSrc/src/test/resources/samples/jacoco/java/build.gradle b/buildSrc/src/test/resources/samples/jacoco/java/build.gradle new file mode 100644 index 00000000..f70cc9a7 --- /dev/null +++ b/buildSrc/src/test/resources/samples/jacoco/java/build.gradle @@ -0,0 +1,13 @@ +plugins { + id 'io.spring.convention.jacoco' +} + +apply plugin: 'java' + +repositories { + mavenCentral() +} + +dependencies { + testCompile 'junit:junit:4.12' +} \ No newline at end of file diff --git a/buildSrc/src/test/resources/samples/jacoco/java/src/main/java/sample/TheClass.java b/buildSrc/src/test/resources/samples/jacoco/java/src/main/java/sample/TheClass.java new file mode 100644 index 00000000..cb7daa50 --- /dev/null +++ b/buildSrc/src/test/resources/samples/jacoco/java/src/main/java/sample/TheClass.java @@ -0,0 +1,11 @@ +package sample; + +public class TheClass { + public boolean doStuff(boolean b) { + if(b) { + return true; + } else { + return false; + } + } +} \ No newline at end of file diff --git a/buildSrc/src/test/resources/samples/jacoco/java/src/test/java/sample/TheClassTest.java b/buildSrc/src/test/resources/samples/jacoco/java/src/test/java/sample/TheClassTest.java new file mode 100644 index 00000000..5e1c64bb --- /dev/null +++ b/buildSrc/src/test/resources/samples/jacoco/java/src/test/java/sample/TheClassTest.java @@ -0,0 +1,19 @@ +package sample; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class TheClassTest { + TheClass theClass = new TheClass(); + + @Test + public void doStuffWhenTrueThenTrue() { + assertTrue(theClass.doStuff(true)); + } + + @Test + public void doStuffWhenTrueThenFalse() { + assertFalse(theClass.doStuff(false)); + } +} \ No newline at end of file diff --git a/buildSrc/src/test/resources/samples/javadocapi/multimodule/api/build.gradle b/buildSrc/src/test/resources/samples/javadocapi/multimodule/api/build.gradle new file mode 100644 index 00000000..f9887e07 --- /dev/null +++ b/buildSrc/src/test/resources/samples/javadocapi/multimodule/api/build.gradle @@ -0,0 +1 @@ +apply plugin: 'io.spring.convention.spring-module' \ No newline at end of file diff --git a/buildSrc/src/test/resources/samples/javadocapi/multimodule/api/src/main/java/sample/Api.java b/buildSrc/src/test/resources/samples/javadocapi/multimodule/api/src/main/java/sample/Api.java new file mode 100644 index 00000000..7177b85b --- /dev/null +++ b/buildSrc/src/test/resources/samples/javadocapi/multimodule/api/src/main/java/sample/Api.java @@ -0,0 +1,14 @@ +package sample; + +/** + * Testing this + * @author Rob Winch + * + */ +public class Api { + + /** + * This does stuff + */ + public void doStuff() {} +} diff --git a/buildSrc/src/test/resources/samples/javadocapi/multimodule/build.gradle b/buildSrc/src/test/resources/samples/javadocapi/multimodule/build.gradle new file mode 100644 index 00000000..86f3f669 --- /dev/null +++ b/buildSrc/src/test/resources/samples/javadocapi/multimodule/build.gradle @@ -0,0 +1,5 @@ +plugins { + id 'io.spring.convention.javadoc-api' + id 'io.spring.convention.spring-module' apply false + id 'io.spring.convention.spring-sample' apply false +} diff --git a/buildSrc/src/test/resources/samples/javadocapi/multimodule/impl/build.gradle b/buildSrc/src/test/resources/samples/javadocapi/multimodule/impl/build.gradle new file mode 100644 index 00000000..f9887e07 --- /dev/null +++ b/buildSrc/src/test/resources/samples/javadocapi/multimodule/impl/build.gradle @@ -0,0 +1 @@ +apply plugin: 'io.spring.convention.spring-module' \ No newline at end of file diff --git a/buildSrc/src/test/resources/samples/javadocapi/multimodule/impl/src/main/java/sample/Impl.java b/buildSrc/src/test/resources/samples/javadocapi/multimodule/impl/src/main/java/sample/Impl.java new file mode 100644 index 00000000..bf906a54 --- /dev/null +++ b/buildSrc/src/test/resources/samples/javadocapi/multimodule/impl/src/main/java/sample/Impl.java @@ -0,0 +1,14 @@ +package sample; + +/** + * Testing this + * @author Rob Winch + * + */ +public class Impl { + + /** + * This does stuff + */ + public void otherThings() {} +} diff --git a/buildSrc/src/test/resources/samples/javadocapi/multimodule/sample/build.gradle b/buildSrc/src/test/resources/samples/javadocapi/multimodule/sample/build.gradle new file mode 100644 index 00000000..333d4915 --- /dev/null +++ b/buildSrc/src/test/resources/samples/javadocapi/multimodule/sample/build.gradle @@ -0,0 +1 @@ +apply plugin: 'io.spring.convention.spring-sample' diff --git a/buildSrc/src/test/resources/samples/javadocapi/multimodule/sample/src/main/java/sample/Sample.java b/buildSrc/src/test/resources/samples/javadocapi/multimodule/sample/src/main/java/sample/Sample.java new file mode 100644 index 00000000..51b7f3f9 --- /dev/null +++ b/buildSrc/src/test/resources/samples/javadocapi/multimodule/sample/src/main/java/sample/Sample.java @@ -0,0 +1,14 @@ +package sample; + +/** + * Testing this + * @author Rob Winch + * + */ +public class Sample { + + /** + * This does stuff + */ + public void doSample() {} +} diff --git a/buildSrc/src/test/resources/samples/javadocapi/multimodule/settings.gradle b/buildSrc/src/test/resources/samples/javadocapi/multimodule/settings.gradle new file mode 100644 index 00000000..eb644147 --- /dev/null +++ b/buildSrc/src/test/resources/samples/javadocapi/multimodule/settings.gradle @@ -0,0 +1,3 @@ +include ':api' +include ':impl' +include ':sample' \ No newline at end of file diff --git a/buildSrc/src/test/resources/samples/maven/install-with-springio/build.gradle b/buildSrc/src/test/resources/samples/maven/install-with-springio/build.gradle new file mode 100644 index 00000000..b4784e44 --- /dev/null +++ b/buildSrc/src/test/resources/samples/maven/install-with-springio/build.gradle @@ -0,0 +1,12 @@ +plugins { + id 'io.spring.convention.spring-module' +} + +repositories { + mavenCentral() +} + +dependencies { + testCompile 'junit:junit:4.12' + compile 'org.springframework:spring-core' +} \ No newline at end of file diff --git a/buildSrc/src/test/resources/samples/maven/install-with-springio/gradle/dependency-management.gradle b/buildSrc/src/test/resources/samples/maven/install-with-springio/gradle/dependency-management.gradle new file mode 100644 index 00000000..3169bca2 --- /dev/null +++ b/buildSrc/src/test/resources/samples/maven/install-with-springio/gradle/dependency-management.gradle @@ -0,0 +1,5 @@ +dependencyManagement { + dependencies { + dependency 'org.springframework:spring-core:3.0.0.RELEASE' + } +} \ No newline at end of file diff --git a/buildSrc/src/test/resources/samples/maven/install/build.gradle b/buildSrc/src/test/resources/samples/maven/install/build.gradle new file mode 100644 index 00000000..4847d7f2 --- /dev/null +++ b/buildSrc/src/test/resources/samples/maven/install/build.gradle @@ -0,0 +1,14 @@ +plugins { + id 'io.spring.convention.root' +} + +apply plugin: 'io.spring.convention.maven' + +repositories { + mavenCentral() +} + +dependencies { + testCompile 'junit:junit:4.12' + optional 'aopalliance:aopalliance:1.0' +} \ No newline at end of file diff --git a/buildSrc/src/test/resources/samples/maven/signing/build.gradle b/buildSrc/src/test/resources/samples/maven/signing/build.gradle new file mode 100644 index 00000000..7460119f --- /dev/null +++ b/buildSrc/src/test/resources/samples/maven/signing/build.gradle @@ -0,0 +1,16 @@ +plugins { + id 'io.spring.convention.root' +} + +version = "1.0.0.RELEASE" + +apply plugin: 'io.spring.convention.maven' + +repositories { + mavenCentral() +} + +dependencies { + testCompile 'junit:junit:4.12' + optional 'aopalliance:aopalliance:1.0' +} \ No newline at end of file diff --git a/buildSrc/src/test/resources/samples/maven/signing/settings.gradle b/buildSrc/src/test/resources/samples/maven/signing/settings.gradle new file mode 100644 index 00000000..cf2aed0b --- /dev/null +++ b/buildSrc/src/test/resources/samples/maven/signing/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'signing' \ No newline at end of file diff --git a/buildSrc/src/test/resources/samples/maven/upload/build.gradle b/buildSrc/src/test/resources/samples/maven/upload/build.gradle new file mode 100644 index 00000000..fb36fc6f --- /dev/null +++ b/buildSrc/src/test/resources/samples/maven/upload/build.gradle @@ -0,0 +1,20 @@ +plugins { + id 'io.spring.convention.root' +} + +repositories { + mavenCentral() +} + +dependencies { + testCompile 'junit:junit:4.12' + optional 'aopalliance:aopalliance:1.0' +} + +uploadArchives { + repositories { + mavenDeployer { + repository(url: "file:$buildDir/repo") + } + } +} \ No newline at end of file diff --git a/buildSrc/src/test/resources/samples/showcase/Jenkinsfile b/buildSrc/src/test/resources/samples/showcase/Jenkinsfile new file mode 100644 index 00000000..ad9bfeaf --- /dev/null +++ b/buildSrc/src/test/resources/samples/showcase/Jenkinsfile @@ -0,0 +1,52 @@ +parallel check: { + stage('Check') { + node { + checkout scm + sh "./gradlew check --refresh-dependencies --no-daemon" + } + } +}, +sonar: { + stage('Sonar') { + node { + checkout scm + withCredentials([string(credentialsId: 'spring-sonar.login', variable: 'SONAR_LOGIN')]) { + sh "./gradlew sonarqube -Dsonar.host.url=$SPRING_SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN --refresh-dependencies --no-daemon" + } + } + } +}, +ossrh: { + stage('OSSRH Deploy') { + node { + checkout scm + withCredentials([file(credentialsId: 'spring-signing-secring.gpg', variable: 'SIGNING_KEYRING_FILE')]) { + withCredentials([string(credentialsId: 'spring-gpg-passphrase', variable: 'SIGNING_PASSWORD')]) { + withCredentials([usernamePassword(credentialsId: 'oss-token', passwordVariable: 'OSSRH_PASSWORD', usernameVariable: 'OSSRH_USERNAME')]) { + sh "./gradlew uploadArchives -Psigning.secretKeyRingFile=$SIGNING_KEYRING_FILE -Psigning.keyId=$SPRING_SIGNING_KEYID -Psigning.password=$SIGNING_PASSWORD -PossrhUsername=$OSSRH_USERNAME -PossrhPassword=$OSSRH_PASSWORD --refresh-dependencies --no-daemon" + } + } + } + } + } +}, +docs: { + stage('Deploy Docs') { + node { + checkout scm + withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) { + sh "./gradlew deployDocs -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME --refresh-dependencies --no-daemon --stacktrace" + } + } + } +}, +schema: { + stage('Deploy Schema') { + node { + checkout scm + withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) { + sh "./gradlew deploySchema -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME --refresh-dependencies --no-daemon --stacktrace" + } + } + } +} \ No newline at end of file diff --git a/buildSrc/src/test/resources/samples/showcase/bom/bom.gradle b/buildSrc/src/test/resources/samples/showcase/bom/bom.gradle new file mode 100644 index 00000000..19aa720c --- /dev/null +++ b/buildSrc/src/test/resources/samples/showcase/bom/bom.gradle @@ -0,0 +1,2 @@ +apply plugin: 'io.spring.convention.bom' + diff --git a/buildSrc/src/test/resources/samples/showcase/build.gradle b/buildSrc/src/test/resources/samples/showcase/build.gradle new file mode 100644 index 00000000..163e7b74 --- /dev/null +++ b/buildSrc/src/test/resources/samples/showcase/build.gradle @@ -0,0 +1,6 @@ +plugins { + id 'io.spring.convention.root' +} + +group = "org.springframework.build.test" +version = "1.0.0.BUILD-SNAPSHOT" diff --git a/buildSrc/src/test/resources/samples/showcase/etc/checkstyle/checkstyle.xml b/buildSrc/src/test/resources/samples/showcase/etc/checkstyle/checkstyle.xml new file mode 100644 index 00000000..fbe98f23 --- /dev/null +++ b/buildSrc/src/test/resources/samples/showcase/etc/checkstyle/checkstyle.xml @@ -0,0 +1,5 @@ + + + + diff --git a/buildSrc/src/test/resources/samples/showcase/samples/sgbcs-sample-war/build.gradle b/buildSrc/src/test/resources/samples/showcase/samples/sgbcs-sample-war/build.gradle new file mode 100644 index 00000000..750a5735 --- /dev/null +++ b/buildSrc/src/test/resources/samples/showcase/samples/sgbcs-sample-war/build.gradle @@ -0,0 +1,13 @@ +plugins { + id "org.gretty" version "3.0.7" + id "io.spring.convention.spring-sample-war" +} + +dependencies { + provided 'javax.servlet:javax.servlet-api' + testImplementation 'commons-io:commons-io:2.11.0' + testImplementation 'org.assertj:assertj-core:3.21.0' + testImplementation platform('org.junit:junit-bom:5.8.1') + testImplementation 'org.junit.jupiter:junit-jupiter-api' + testImplementation 'org.junit.jupiter:junit-jupiter-engine' +} diff --git a/buildSrc/src/test/resources/samples/showcase/samples/sgbcs-sample-war/src/integration-test/java/sample/HelloServletTest.java b/buildSrc/src/test/resources/samples/showcase/samples/sgbcs-sample-war/src/integration-test/java/sample/HelloServletTest.java new file mode 100644 index 00000000..ded30d0c --- /dev/null +++ b/buildSrc/src/test/resources/samples/showcase/samples/sgbcs-sample-war/src/integration-test/java/sample/HelloServletTest.java @@ -0,0 +1,23 @@ +package sample; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.InputStream; +import java.net.URL; +import java.nio.charset.Charset; + +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.Test; + +public class HelloServletTest { + + @Test + public void hello() throws Exception { + String url = System.getProperty("app.baseURI"); + try (InputStream get = new URL(url).openConnection().getInputStream()) { + String hello = IOUtils.toString(get, Charset.defaultCharset()); + assertThat(hello).isEqualTo("Hello"); + } + + } +} diff --git a/buildSrc/src/test/resources/samples/showcase/samples/sgbcs-sample-war/src/main/java/sample/HelloServlet.java b/buildSrc/src/test/resources/samples/showcase/samples/sgbcs-sample-war/src/main/java/sample/HelloServlet.java new file mode 100644 index 00000000..8d74375d --- /dev/null +++ b/buildSrc/src/test/resources/samples/showcase/samples/sgbcs-sample-war/src/main/java/sample/HelloServlet.java @@ -0,0 +1,35 @@ +/* + * 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 sample; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@WebServlet("/") +public class HelloServlet extends HttpServlet { + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + resp.getWriter().write("Hello"); + } + + private static final long serialVersionUID = -166535360229360350L; +} diff --git a/buildSrc/src/test/resources/samples/showcase/settings.gradle b/buildSrc/src/test/resources/samples/showcase/settings.gradle new file mode 100644 index 00000000..5b63a3c3 --- /dev/null +++ b/buildSrc/src/test/resources/samples/showcase/settings.gradle @@ -0,0 +1,25 @@ +import java.util.regex.Matcher + +rootProject.name = 'spring-gradle-build-conventions-sample' + + +FileTree projects = fileTree(rootDir) { + include '**/*.gradle' + exclude '**/gradle', 'settings.gradle', 'buildSrc', '/build.gradle', '.*' +} + +String rootDirPath = rootDir.absolutePath + File.separator +projects.each { File buildFile -> + String buildFilePath = buildFile.parentFile.absolutePath + + String projectPath = buildFilePath.replace(rootDirPath, '').replaceAll(Matcher.quoteReplacement(File.separator), ':') + + include projectPath + + def project = findProject(":${projectPath}") + if(!'build.gradle'.equals(buildFile.name)) { + project.name = buildFile.name.replace('.gradle','') + project.buildFileName = buildFile.name + } + project.projectDir = buildFile.parentFile +} diff --git a/buildSrc/src/test/resources/samples/showcase/sgbcs-api/sgbcs-api.gradle b/buildSrc/src/test/resources/samples/showcase/sgbcs-api/sgbcs-api.gradle new file mode 100644 index 00000000..21103a8c --- /dev/null +++ b/buildSrc/src/test/resources/samples/showcase/sgbcs-api/sgbcs-api.gradle @@ -0,0 +1,10 @@ +apply plugin: 'io.spring.convention.spring-module' + +dependencies { + api platform('org.springframework.boot:spring-boot-dependencies:2.5.2') + implementation 'org.springframework:spring-web' + implementation 'org.springframework:spring-core' + testImplementation "org.junit.jupiter:junit-jupiter-api" + testImplementation "org.junit.jupiter:junit-jupiter-engine" +} + diff --git a/buildSrc/src/test/resources/samples/showcase/sgbcs-api/src/main/java/api/Api.java b/buildSrc/src/test/resources/samples/showcase/sgbcs-api/src/main/java/api/Api.java new file mode 100644 index 00000000..6ed5b9c6 --- /dev/null +++ b/buildSrc/src/test/resources/samples/showcase/sgbcs-api/src/main/java/api/Api.java @@ -0,0 +1,10 @@ +package api; + +/** + * + * @author Rob Winch + * + */ +public class Api { + +} diff --git a/buildSrc/src/test/resources/samples/showcase/sgbcs-api/src/test/java/api/ApiTest.java b/buildSrc/src/test/resources/samples/showcase/sgbcs-api/src/test/java/api/ApiTest.java new file mode 100644 index 00000000..b6c952f9 --- /dev/null +++ b/buildSrc/src/test/resources/samples/showcase/sgbcs-api/src/test/java/api/ApiTest.java @@ -0,0 +1,9 @@ +package api; + +import org.junit.jupiter.api.Test; + +public class ApiTest { + + @Test + public void api() {} +} diff --git a/buildSrc/src/test/resources/samples/showcase/sgbcs-core/sgbcs-core.gradle b/buildSrc/src/test/resources/samples/showcase/sgbcs-core/sgbcs-core.gradle new file mode 100644 index 00000000..d40d2e28 --- /dev/null +++ b/buildSrc/src/test/resources/samples/showcase/sgbcs-core/sgbcs-core.gradle @@ -0,0 +1,8 @@ +apply plugin: 'io.spring.convention.spring-module' + +dependencies { + api platform('org.springframework.boot:spring-boot-dependencies:2.5.2') + optional 'ch.qos.logback:logback-classic' + testImplementation "org.junit.jupiter:junit-jupiter-api" + testImplementation "org.junit.jupiter:junit-jupiter-engine" +} diff --git a/buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/main/java/core/CoreClass.java b/buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/main/java/core/CoreClass.java new file mode 100644 index 00000000..4a0ecdb5 --- /dev/null +++ b/buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/main/java/core/CoreClass.java @@ -0,0 +1,13 @@ +package core; + +/** + * + * @author Rob Winch + * + */ +public class CoreClass { + + public void run() { + + } +} diff --git a/buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/main/java/core/HasOptional.java b/buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/main/java/core/HasOptional.java new file mode 100644 index 00000000..8d1ace3c --- /dev/null +++ b/buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/main/java/core/HasOptional.java @@ -0,0 +1,14 @@ +package core; + + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class HasOptional { + + public static void doStuffWithOptionalDependency() { + Logger logger = LoggerFactory.getLogger(HasOptional.class); + logger.debug("This is optional"); + } +} diff --git a/buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/main/resources/META-INF/spring.handlers b/buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/main/resources/META-INF/spring.handlers new file mode 100644 index 00000000..6838fba4 --- /dev/null +++ b/buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/main/resources/META-INF/spring.handlers @@ -0,0 +1 @@ +http\://www.springframework.org/schema/springgradlebuildsample=org.springframework.ldap.config.LdapNamespaceHandler \ No newline at end of file diff --git a/buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/main/resources/META-INF/spring.schemas b/buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/main/resources/META-INF/spring.schemas new file mode 100644 index 00000000..84285717 --- /dev/null +++ b/buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/main/resources/META-INF/spring.schemas @@ -0,0 +1,4 @@ +http\://www.springframework.org/schema/springgradlebuildsample/spring-springgradlebuildsample.xsd=org/springframework/springgradlebuildsample/config/spring-springgradlebuildsample-2.2.xsd +http\://www.springframework.org/schema/springgradlebuildsample/spring-springgradlebuildsample-2.0.xsd=org/springframework/springgradlebuildsample/config/spring-springgradlebuildsample-2.0.xsd +http\://www.springframework.org/schema/springgradlebuildsample/spring-springgradlebuildsample-2.1.xsd=org/springframework/springgradlebuildsample/config/spring-springgradlebuildsample-2.1.xsd +http\://www.springframework.org/schema/springgradlebuildsample/spring-springgradlebuildsample-2.2.xsd=org/springframework/springgradlebuildsample/config/spring-springgradlebuildsample-2.2.xsd \ No newline at end of file diff --git a/buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/main/resources/org/springframework/springgradlebuildsample/config/spring-springgradlebuildsample-2.0.xsd b/buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/main/resources/org/springframework/springgradlebuildsample/config/spring-springgradlebuildsample-2.0.xsd new file mode 100644 index 00000000..327f3a59 --- /dev/null +++ b/buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/main/resources/org/springframework/springgradlebuildsample/config/spring-springgradlebuildsample-2.0.xsd @@ -0,0 +1,468 @@ + + + + + + + + + + A bean identifier, used for referring to the bean elsewhere in the context. + "contextSource". + + + + + + + Defines whether read-only operations will be performed using an anonymous (unauthenticated) context. + + + + + + + Id of the AuthenticationSource instance to use. If not specified, a SimpleAuthenticationSource will + be used. + + + + + + + Id of the DirContextAuthenticationStrategy instance to use. If not specified, a SimpleDirContextAuthenticationStrategy + will be used. + + + + + + + The base DN. If configured, all LDAP operations on contexts retrieved from this ContextSource will + be relative to this DN. Default is an empty distinguished name (i.e. all operations will be + relative to the directory root). + + + + + + + The password to use for authentication. + + + + + + + Specify whether native Java LDAP connection pooling should be used. Default is false. + + + + + + + Defines the strategy to handle referrals, as described on https://docs.oracle.com/javase/jndi/tutorial/ldap/referral/jndi.html. + Default is null. + + + + + + + + + + + + + + URL of the LDAP server to use. If fail-over functionality is desired, more than one URL can + be specified, separated using comma (,). + + + + + + + The username (principal) to use for authentication. This will normally be the distinguished name + of an admin user. + + + + + + + Reference to a Map of custom environment properties that should supplied with the environment + sent to the DirContext on construction. + + + + + + + + + + The maximum number of active connections of each type (read-only|read-write) + that can be allocated from the pool at the same time, or non-positive for no limit. + Default is 8. + + + + + + + The overall maximum number of active connections (for all types) that can be allocated from + this pool at the same time, or non-positive for no limit. Default is -1 (no limit). + + + + + + + The maximum number of active connections of each type (read-only|read-write) that can remain idle in the pool, + without extra ones being released, or non-positive for no limit. Default is 8. + + + + + + + The minimum number of active connections of each type (read-only|read-write) that can remain + idle in the pool, without extra ones being created, or zero to create none. Default is 0. + + + + + + + The maximum number of milliseconds that the pool will wait (when there are no available connections) + for a connection to be returned before throwing an exception, or non-positive to wait indefinitely. + Default is -1. + + + + + + + Specifies the behaviour when the pool is exhausted. + + + + + + + + Throw a NoSuchElementException when the pool is exhausted + + + + + + + Wait until a new object is available. If max-wait is positive a NoSuchElementException + is thrown if no new object is available after the maxWait time expires. + + + + + + + Create and return a new object (essentially making maxActive meaningless). + + + + + + + + + + The indication of whether objects will be validated before being borrowed from the pool. + If the object fails to validate, it will be dropped from the pool, and an attempt to borrow another will be made. + Default is false. + + + + + + + The indication of whether objects will be validated before being returned to the pool. + Default is false. + + + + + + + The indication of whether objects will be validated by the idle object evictor (if any). + If an object fails to validate, it will be dropped from the pool. + Default is false. + + + + + + + The number of milliseconds to sleep between runs of the idle object evictor thread. When non-positive, + no idle object evictor thread will be run. Default is -1. + + + + + + + The number of objects to examine during each run of the idle object evictor thread (if any). + Default is 3. + + + + + + + The minimum amount of time an object may sit idle in the pool before it is eligible + for eviction by the idle object evictor (if any). Default is 1000 * 60 * 30. + + + + + + + The base dn to use for validation searches. Default is LdapUtils.emptyPath(). + + + + + + + The filter to use for validation queries. Default is (objectclass=*). + + + + + + + Id of the SearchControls instance to use for searches. Default is searchScope=OBJECT_SCOPE; + countLimit: 1; timeLimit: 500; returningAttributes: [objectclass]. + + + + + + + Id of the SearchControls instance to use for searches. Default is searchScope=OBJECT_SCOPE; + countLimit: 1; timeLimit: 500; returningAttributes: [objectclass]. + + + + + + + + + Creates a ContextSource instance to be used to get LdapContexts for communicating with an LDAP server. + + + + + + + + Defines the settings to use for the Spring LDAP connection pooling support. + + + + + + + + + + + + + + + + A bean identifier, used for referring to the bean elsewhere in the context. + Default is "ldapTemplate". + + + + + + + Id of the ContextSource instance to use. Default is "contextSource". + + + + + + + The default count limit for searches. Default is 0 (no limit). + + + + + + + The default time limit for searches. Default is 0 (no limit). + + + + + + + The default search scope for searches. Default is SUBTREE. + + + + + + + + + + + + + + Specifies whether NameNotFoundException should be ignored in searches. Setting this + attribute to true will cause errors caused by invalid search base to be silently swallowed. + Default is false. + + + + + + + Specifies whether PartialResultException should be ignored in searches. Some LDAP servers + have problems with referrals; these should normally be followed automatically, but if this + doesn't work it will manifest itself with a PartialResultException. Setting this attribute + to true presents a work-around to this problem. Default is false. + + + + + + + Id of the ObjectDirectoryMapper instance to use. Default is a default-configured DefaultObjectDirectoryMapper. + + + + + + + + + Creates an LdapTemplate instance. + + + + + + + + + + + + Id of this instance. Default is "transactionManager". + + + + + + + Id of the ContextSource instance to use. "contextSource". + + + + + + + Id of the DataSource instance to use. + + + + + + + Id of the Hibernate SessionFactory instance to use. + + + + + + + + + Creates an ContextSourceTransactionManager. If data-source-ref or session-factory-ref is specified, + a DataSourceAndContextSourceTransactionManager/HibernateAndContextSourceTransactionManager will be + created. + + + + + + + + The default (simplistic) TempEntryRenamingStrategy. Please note that this + strategy will not work for more advanced scenarios. See reference documentation + for details. + + + + + + + The default suffix that will be added to modified entries. + Default is "_temp". + + + + + + + + + TempEntryRenamingStrategy that moves the entry to a different subtree than + the original entry. + + + + + + + The subtree base where changed entries should be moved. + + + + + + + + + + + + + + + + + + The reference to an LdapTemplate. Will default to 'ldapTemplate'. + + + + + + + + \ No newline at end of file diff --git a/buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/main/resources/org/springframework/springgradlebuildsample/config/spring-springgradlebuildsample-2.1.xsd b/buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/main/resources/org/springframework/springgradlebuildsample/config/spring-springgradlebuildsample-2.1.xsd new file mode 100644 index 00000000..3b5e7198 --- /dev/null +++ b/buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/main/resources/org/springframework/springgradlebuildsample/config/spring-springgradlebuildsample-2.1.xsd @@ -0,0 +1,685 @@ + + + + + + + + + + A bean identifier, used for referring to the bean elsewhere in the context. + "contextSource". + + + + + + + Defines whether read-only operations will be performed using an anonymous (unauthenticated) context. + + + + + + + Id of the AuthenticationSource instance to use. If not specified, a SimpleAuthenticationSource will + be used. + + + + + + + Id of the DirContextAuthenticationStrategy instance to use. If not specified, a SimpleDirContextAuthenticationStrategy + will be used. + + + + + + + The base DN. If configured, all LDAP operations on contexts retrieved from this ContextSource will + be relative to this DN. Default is an empty distinguished name (i.e. all operations will be + relative to the directory root). + + + + + + + The password to use for authentication. + + + + + + + Specify whether native Java LDAP connection pooling should be used. Default is false. + + + + + + + Defines the strategy to handle referrals, as described on https://docs.oracle.com/javase/jndi/tutorial/ldap/referral/jndi.html. + Default is null. + + + + + + + + + + + + + + URL of the LDAP server to use. If fail-over functionality is desired, more than one URL can + be specified, separated using comma (,). + + + + + + + The username (principal) to use for authentication. This will normally be the distinguished name + of an admin user. + + + + + + + Reference to a Map of custom environment properties that should supplied with the environment + sent to the DirContext on construction. + + + + + + + + + + The maximum number of active connections of each type (read-only|read-write) + that can be allocated from the pool at the same time, or non-positive for no limit. + Default is 8. + + + + + + + The overall maximum number of active connections (for all types) that can be allocated from + this pool at the same time, or non-positive for no limit. Default is -1 (no limit). + + + + + + + The maximum number of active connections of each type (read-only|read-write) that can remain idle in the pool, + without extra ones being released, or non-positive for no limit. Default is 8. + + + + + + + The minimum number of active connections of each type (read-only|read-write) that can remain + idle in the pool, without extra ones being created, or zero to create none. Default is 0. + + + + + + + The maximum number of milliseconds that the pool will wait (when there are no available connections) + for a connection to be returned before throwing an exception, or non-positive to wait indefinitely. + Default is -1. + + + + + + + Specifies the behaviour when the pool is exhausted. + + + + + + + + Throw a NoSuchElementException when the pool is exhausted + + + + + + + Wait until a new object is available. If max-wait is positive a NoSuchElementException + is thrown if no new object is available after the maxWait time expires. + + + + + + + Create and return a new object (essentially making maxActive meaningless). + + + + + + + + + + The indication of whether objects will be validated before being borrowed from the pool. + If the object fails to validate, it will be dropped from the pool, and an attempt to borrow another will be made. + Default is false. + + + + + + + The indication of whether objects will be validated before being returned to the pool. + Default is false. + + + + + + + The indication of whether objects will be validated by the idle object evictor (if any). + If an object fails to validate, it will be dropped from the pool. + Default is false. + + + + + + + The number of milliseconds to sleep between runs of the idle object evictor thread. When non-positive, + no idle object evictor thread will be run. Default is -1. + + + + + + + The number of objects to examine during each run of the idle object evictor thread (if any). + Default is 3. + + + + + + + The minimum amount of time an object may sit idle in the pool before it is eligible + for eviction by the idle object evictor (if any). Default is 1000 * 60 * 30. + + + + + + + The base dn to use for validation searches. Default is LdapUtils.emptyPath(). + + + + + + + The filter to use for validation queries. Default is (objectclass=*). + + + + + + + Id of the SearchControls instance to use for searches. Default is searchScope=OBJECT_SCOPE; + countLimit: 1; timeLimit: 500; returningAttributes: [objectclass]. + + + + + + + Id of the SearchControls instance to use for searches. Default is searchScope=OBJECT_SCOPE; + countLimit: 1; timeLimit: 500; returningAttributes: [objectclass]. + + + + + + + + + + The overall maximum number of active connections (for all types) that can be allocated from + this pool at the same time, or non-positive for no limit. Default is -1 (no limit). + + + + + + + The limit on the number of object instances allocated by the pool (checked out or idle), + per key. When the limit is reached, the sub-pool is said to be exhausted. A negative value + indicates no limit. Default is 8. + + + + + + + The maximum number of active connections per type (read-only|read-write) that can remain idle in the pool, + without extra ones being released, or non-positive for no limit. Default is 8. + + + + + + + The minimum number of active connections per type (read-only|read-write) that can remain + idle in the pool, without extra ones being created, or zero to create none. Default is 0. + + + + + + + The maximum number of milliseconds that the pool will wait (when there are no available connections) + for a connection to be returned before throwing an exception, or non-positive to wait indefinitely. + Default is -1. + + + + + + + Sets to wait until a new object is available. If max-wait is positive a NoSuchElementException + is thrown if no new object is available after the maxWait time expires.. + + + + + + + Sets whether objects created for the pool will be validated before borrowing. If the object + fails to validate, then borrowing will fail. Default is false. + + + + + + + The indication of whether objects will be validated before being borrowed from the pool. + If the object fails to validate, it will be dropped from the pool, and an attempt to borrow another will be made. + Default is false. + + + + + + + The indication of whether objects will be validated before being returned to the pool. + Default is false. + + + + + + + The indication of whether objects will be validated by the idle object evictor (if any). + If an object fails to validate, it will be dropped from the pool. + Default is false. + + + + + + + The number of milliseconds to sleep between runs of the idle object evictor thread. When non-positive, + no idle object evictor thread will be run. Default is -1. + + + + + + + The number of objects to examine during each run of the idle object evictor thread (if any). + Default is 3. + + + + + + + The minimum amount of time an object may sit idle in the pool before it is eligible + for eviction by the idle object evictor (if any). Default is 1000 * 60 * 30. + + + + + + + The minimum amount of time an object may sit idle in the pool before it is eligible for + eviction by the idle object evictor, with the extra condition that at least minimum number + of object instances per key remain in the pool. This settings is overridden by min-evictable-time-millis if + it is set to a positive value. Default is -1. + + + + + + + The name of the eviction policy implementation that is used by this pool. The Pool will + attempt to load the class using the thread context class loader. If that fails, the Pool + will attempt to load the class using the class loader that loaded this class. Default is + org.apache.commons.pool2.impl.DefaultEvictionPolicy. + + + + + + + Sets whether or not the pool serves threads waiting to borrow connections fairly. + True means that waiting threads are served as if waiting in a FIFO queue. Default is false. + + + + + + + Sets whether JMX will be enabled with the platform MBean server for the pool. Default + is true. + + + + + + + The value of the JMX name base that will be used as part of the name assigned + to JMX enabled pools. Default is null. + + + + + + + The value of the JMX name prefix that will be used as part of the name assigned + to JMX enabled pools. Default value is pool. + + + + + + + Sets whether the pool has LIFO (last in, first out) behaviour with + respect to idle objects - always returning the most recently used object + from the pool, or as a FIFO (first in, first out) queue, where the pool + always returns the oldest object in the idle object pool. Default is true. + + + + + + + The base dn to use for validation searches. Default is LdapUtils.emptyPath(). + + + + + + + The filter to use for validation queries. Default is (objectclass=*). + + + + + + + Id of the SearchControls instance to use for searches. Default is searchScope=OBJECT_SCOPE; + countLimit: 1; timeLimit: 500; returningAttributes: [objectclass]. + + + + + + + Id of the SearchControls instance to use for searches. Default is searchScope=OBJECT_SCOPE; + countLimit: 1; timeLimit: 500; returningAttributes: [objectclass]. + + + + + + + + + Creates a ContextSource instance to be used to get LdapContexts for communicating with an LDAP server. + + + + + + + + + Defines the settings to use for the Spring LDAP connection pooling support. + + + + + + + + + + + + Defines the settings to use for the Spring LDAP connection pooling support based on commons-pool2 library. + + + + + + + + + + + + + + + + + A bean identifier, used for referring to the bean elsewhere in the context. + Default is "ldapTemplate". + + + + + + + Id of the ContextSource instance to use. Default is "contextSource". + + + + + + + The default count limit for searches. Default is 0 (no limit). + + + + + + + The default time limit for searches. Default is 0 (no limit). + + + + + + + The default search scope for searches. Default is SUBTREE. + + + + + + + + + + + + + + Specifies whether NameNotFoundException should be ignored in searches. Setting this + attribute to true will cause errors caused by invalid search base to be silently swallowed. + Default is false. + + + + + + + Specifies whether PartialResultException should be ignored in searches. Some LDAP servers + have problems with referrals; these should normally be followed automatically, but if this + doesn't work it will manifest itself with a PartialResultException. Setting this attribute + to true presents a work-around to this problem. Default is false. + + + + + + + Id of the ObjectDirectoryMapper instance to use. Default is a default-configured DefaultObjectDirectoryMapper. + + + + + + + + + Creates an LdapTemplate instance. + + + + + + + + + + + + Id of this instance. Default is "transactionManager". + + + + + + + Id of the ContextSource instance to use. "contextSource". + + + + + + + Id of the DataSource instance to use. + + + + + + + Id of the Hibernate SessionFactory instance to use. + + + + + + + + + Creates an ContextSourceTransactionManager. If data-source-ref or session-factory-ref is specified, + a DataSourceAndContextSourceTransactionManager/HibernateAndContextSourceTransactionManager will be + created. + + + + + + + + The default (simplistic) TempEntryRenamingStrategy. Please note that this + strategy will not work for more advanced scenarios. See reference documentation + for details. + + + + + + + The default suffix that will be added to modified entries. + Default is "_temp". + + + + + + + + + TempEntryRenamingStrategy that moves the entry to a different subtree than + the original entry. + + + + + + + The subtree base where changed entries should be moved. + + + + + + + + + + + + + + + + + + The reference to an LdapTemplate. Will default to 'ldapTemplate'. + + + + + + + + diff --git a/buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/main/resources/org/springframework/springgradlebuildsample/config/spring-springgradlebuildsample-2.2.xsd b/buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/main/resources/org/springframework/springgradlebuildsample/config/spring-springgradlebuildsample-2.2.xsd new file mode 100644 index 00000000..e76d7846 --- /dev/null +++ b/buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/main/resources/org/springframework/springgradlebuildsample/config/spring-springgradlebuildsample-2.2.xsd @@ -0,0 +1,685 @@ + + + + + + + + + + A bean identifier, used for referring to the bean elsewhere in the context. + "contextSource". + + + + + + + Defines whether read-only operations will be performed using an anonymous (unauthenticated) context. + + + + + + + Id of the AuthenticationSource instance to use. If not specified, a SimpleAuthenticationSource will + be used. + + + + + + + Id of the DirContextAuthenticationStrategy instance to use. If not specified, a SimpleDirContextAuthenticationStrategy + will be used. + + + + + + + The base DN. If configured, all LDAP operations on contexts retrieved from this ContextSource will + be relative to this DN. Default is an empty distinguished name (i.e. all operations will be + relative to the directory root). + + + + + + + The password to use for authentication. + + + + + + + Specify whether native Java LDAP connection pooling should be used. Default is false. + + + + + + + Defines the strategy to handle referrals, as described on https://docs.oracle.com/javase/jndi/tutorial/ldap/referral/jndi.html. + Default is null. + + + + + + + + + + + + + + URL of the LDAP server to use. If fail-over functionality is desired, more than one URL can + be specified, separated using comma (,). + + + + + + + The username (principal) to use for authentication. This will normally be the distinguished name + of an admin user. + + + + + + + Reference to a Map of custom environment properties that should supplied with the environment + sent to the DirContext on construction. + + + + + + + + + + The maximum number of active connections of each type (read-only|read-write) + that can be allocated from the pool at the same time, or non-positive for no limit. + Default is 8. + + + + + + + The overall maximum number of active connections (for all types) that can be allocated from + this pool at the same time, or non-positive for no limit. Default is -1 (no limit). + + + + + + + The maximum number of active connections of each type (read-only|read-write) that can remain idle in the pool, + without extra ones being released, or non-positive for no limit. Default is 8. + + + + + + + The minimum number of active connections of each type (read-only|read-write) that can remain + idle in the pool, without extra ones being created, or zero to create none. Default is 0. + + + + + + + The maximum number of milliseconds that the pool will wait (when there are no available connections) + for a connection to be returned before throwing an exception, or non-positive to wait indefinitely. + Default is -1. + + + + + + + Specifies the behaviour when the pool is exhausted. + + + + + + + + Throw a NoSuchElementException when the pool is exhausted + + + + + + + Wait until a new object is available. If max-wait is positive a NoSuchElementException + is thrown if no new object is available after the maxWait time expires. + + + + + + + Create and return a new object (essentially making maxActive meaningless). + + + + + + + + + + The indication of whether objects will be validated before being borrowed from the pool. + If the object fails to validate, it will be dropped from the pool, and an attempt to borrow another will be made. + Default is false. + + + + + + + The indication of whether objects will be validated before being returned to the pool. + Default is false. + + + + + + + The indication of whether objects will be validated by the idle object evictor (if any). + If an object fails to validate, it will be dropped from the pool. + Default is false. + + + + + + + The number of milliseconds to sleep between runs of the idle object evictor thread. When non-positive, + no idle object evictor thread will be run. Default is -1. + + + + + + + The number of objects to examine during each run of the idle object evictor thread (if any). + Default is 3. + + + + + + + The minimum amount of time an object may sit idle in the pool before it is eligible + for eviction by the idle object evictor (if any). Default is 1000 * 60 * 30. + + + + + + + The base dn to use for validation searches. Default is LdapUtils.emptyPath(). + + + + + + + The filter to use for validation queries. Default is (objectclass=*). + + + + + + + Id of the SearchControls instance to use for searches. Default is searchScope=OBJECT_SCOPE; + countLimit: 1; timeLimit: 500; returningAttributes: [objectclass]. + + + + + + + Id of the SearchControls instance to use for searches. Default is searchScope=OBJECT_SCOPE; + countLimit: 1; timeLimit: 500; returningAttributes: [objectclass]. + + + + + + + + + + The overall maximum number of active connections (for all types) that can be allocated from + this pool at the same time, or non-positive for no limit. Default is -1 (no limit). + + + + + + + The limit on the number of object instances allocated by the pool (checked out or idle), + per key. When the limit is reached, the sub-pool is said to be exhausted. A negative value + indicates no limit. Default is 8. + + + + + + + The maximum number of active connections per type (read-only|read-write) that can remain idle in the pool, + without extra ones being released, or non-positive for no limit. Default is 8. + + + + + + + The minimum number of active connections per type (read-only|read-write) that can remain + idle in the pool, without extra ones being created, or zero to create none. Default is 0. + + + + + + + The maximum number of milliseconds that the pool will wait (when there are no available connections) + for a connection to be returned before throwing an exception, or non-positive to wait indefinitely. + Default is -1. + + + + + + + Sets to wait until a new object is available. If max-wait is positive a NoSuchElementException + is thrown if no new object is available after the maxWait time expires. Default is true. + + + + + + + Sets whether objects created for the pool will be validated before borrowing. If the object + fails to validate, then borrowing will fail. Default is false. + + + + + + + The indication of whether objects will be validated before being borrowed from the pool. + If the object fails to validate, it will be dropped from the pool, and an attempt to borrow another will be made. + Default is false. + + + + + + + The indication of whether objects will be validated before being returned to the pool. + Default is false. + + + + + + + The indication of whether objects will be validated by the idle object evictor (if any). + If an object fails to validate, it will be dropped from the pool. + Default is false. + + + + + + + The number of milliseconds to sleep between runs of the idle object evictor thread. When non-positive, + no idle object evictor thread will be run. Default is -1. + + + + + + + The number of objects to examine during each run of the idle object evictor thread (if any). + Default is 3. + + + + + + + The minimum amount of time an object may sit idle in the pool before it is eligible + for eviction by the idle object evictor (if any). Default is 1000 * 60 * 30. + + + + + + + The minimum amount of time an object may sit idle in the pool before it is eligible for + eviction by the idle object evictor, with the extra condition that at least minimum number + of object instances per key remain in the pool. This settings is overridden by min-evictable-time-millis if + it is set to a positive value. Default is -1. + + + + + + + The name of the eviction policy implementation that is used by this pool. The Pool will + attempt to load the class using the thread context class loader. If that fails, the Pool + will attempt to load the class using the class loader that loaded this class. Default is + org.apache.commons.pool2.impl.DefaultEvictionPolicy. + + + + + + + Sets whether or not the pool serves threads waiting to borrow connections fairly. + True means that waiting threads are served as if waiting in a FIFO queue. Default is false. + + + + + + + Sets whether JMX will be enabled with the platform MBean server for the pool. Default + is true. + + + + + + + The value of the JMX name base that will be used as part of the name assigned + to JMX enabled pools. Default is null. + + + + + + + The value of the JMX name prefix that will be used as part of the name assigned + to JMX enabled pools. Default value is pool. + + + + + + + Sets whether the pool has LIFO (last in, first out) behaviour with + respect to idle objects - always returning the most recently used object + from the pool, or as a FIFO (first in, first out) queue, where the pool + always returns the oldest object in the idle object pool. Default is true. + + + + + + + The base dn to use for validation searches. Default is LdapUtils.emptyPath(). + + + + + + + The filter to use for validation queries. Default is (objectclass=*). + + + + + + + Id of the SearchControls instance to use for searches. Default is searchScope=OBJECT_SCOPE; + countLimit: 1; timeLimit: 500; returningAttributes: [objectclass]. + + + + + + + Id of the SearchControls instance to use for searches. Default is searchScope=OBJECT_SCOPE; + countLimit: 1; timeLimit: 500; returningAttributes: [objectclass]. + + + + + + + + + Creates a ContextSource instance to be used to get LdapContexts for communicating with an LDAP server. + + + + + + + + + Defines the settings to use for the Spring LDAP connection pooling support. + + + + + + + + + + + + Defines the settings to use for the Spring LDAP connection pooling support based on commons-pool2 library. + + + + + + + + + + + + + + + + + A bean identifier, used for referring to the bean elsewhere in the context. + Default is "ldapTemplate". + + + + + + + Id of the ContextSource instance to use. Default is "contextSource". + + + + + + + The default count limit for searches. Default is 0 (no limit). + + + + + + + The default time limit for searches. Default is 0 (no limit). + + + + + + + The default search scope for searches. Default is SUBTREE. + + + + + + + + + + + + + + Specifies whether NameNotFoundException should be ignored in searches. Setting this + attribute to true will cause errors caused by invalid search base to be silently swallowed. + Default is false. + + + + + + + Specifies whether PartialResultException should be ignored in searches. Some LDAP servers + have problems with referrals; these should normally be followed automatically, but if this + doesn't work it will manifest itself with a PartialResultException. Setting this attribute + to true presents a work-around to this problem. Default is false. + + + + + + + Id of the ObjectDirectoryMapper instance to use. Default is a default-configured DefaultObjectDirectoryMapper. + + + + + + + + + Creates an LdapTemplate instance. + + + + + + + + + + + + Id of this instance. Default is "transactionManager". + + + + + + + Id of the ContextSource instance to use. "contextSource". + + + + + + + Id of the DataSource instance to use. + + + + + + + Id of the Hibernate SessionFactory instance to use. + + + + + + + + + Creates an ContextSourceTransactionManager. If data-source-ref or session-factory-ref is specified, + a DataSourceAndContextSourceTransactionManager/HibernateAndContextSourceTransactionManager will be + created. + + + + + + + + The default (simplistic) TempEntryRenamingStrategy. Please note that this + strategy will not work for more advanced scenarios. See reference documentation + for details. + + + + + + + The default suffix that will be added to modified entries. + Default is "_temp". + + + + + + + + + TempEntryRenamingStrategy that moves the entry to a different subtree than + the original entry. + + + + + + + The subtree base where changed entries should be moved. + + + + + + + + + + + + + + + + + + The reference to an LdapTemplate. Will default to 'ldapTemplate'. + + + + + + + + diff --git a/buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/test/java/core/CoreClassTest.java b/buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/test/java/core/CoreClassTest.java new file mode 100644 index 00000000..629721cc --- /dev/null +++ b/buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/test/java/core/CoreClassTest.java @@ -0,0 +1,12 @@ +package core; + +import org.junit.jupiter.api.Test; + +public class CoreClassTest { + + @Test + public void test() { + new CoreClass().run(); + } + +} diff --git a/buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/test/java/core/HasOptionalTest.java b/buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/test/java/core/HasOptionalTest.java new file mode 100644 index 00000000..ce8dcc17 --- /dev/null +++ b/buildSrc/src/test/resources/samples/showcase/sgbcs-core/src/test/java/core/HasOptionalTest.java @@ -0,0 +1,12 @@ +package core; + +import org.junit.jupiter.api.Test; + +public class HasOptionalTest { + + @Test + public void test() { + HasOptional.doStuffWithOptionalDependency(); + } + +} diff --git a/buildSrc/src/test/resources/samples/showcase/sgbcs-docs/sgbcs-docs.gradle b/buildSrc/src/test/resources/samples/showcase/sgbcs-docs/sgbcs-docs.gradle new file mode 100644 index 00000000..88d66263 --- /dev/null +++ b/buildSrc/src/test/resources/samples/showcase/sgbcs-docs/sgbcs-docs.gradle @@ -0,0 +1,4 @@ +apply plugin: 'java' +apply plugin: 'io.spring.convention.docs' + +version = "1.0.0.BUILD-SNAPSHOT" diff --git a/buildSrc/src/test/resources/samples/showcase/sgbcs-docs/src/docs/asciidoc/docinfo.html b/buildSrc/src/test/resources/samples/showcase/sgbcs-docs/src/docs/asciidoc/docinfo.html new file mode 100644 index 00000000..f3998865 --- /dev/null +++ b/buildSrc/src/test/resources/samples/showcase/sgbcs-docs/src/docs/asciidoc/docinfo.html @@ -0,0 +1 @@ + diff --git a/buildSrc/src/test/resources/samples/showcase/sgbcs-docs/src/docs/asciidoc/images/sunset.jpg b/buildSrc/src/test/resources/samples/showcase/sgbcs-docs/src/docs/asciidoc/images/sunset.jpg new file mode 100644 index 0000000000000000000000000000000000000000..48c9129b301398f736bf7714dc8c425904b2ef49 GIT binary patch literal 122404 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⪙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 literal 0 HcmV?d00001 diff --git a/buildSrc/src/test/resources/samples/showcase/sgbcs-docs/src/docs/asciidoc/index.adoc b/buildSrc/src/test/resources/samples/showcase/sgbcs-docs/src/docs/asciidoc/index.adoc new file mode 100644 index 00000000..ea408775 --- /dev/null +++ b/buildSrc/src/test/resources/samples/showcase/sgbcs-docs/src/docs/asciidoc/index.adoc @@ -0,0 +1,60 @@ += Example Manual +Doc Writer +2014-09-09 +:example-caption!: +ifndef::imagesdir[:imagesdir: images] +ifndef::sourcedir[:sourcedir: ../java] + +This is a user manual for an example project. + +== Introduction + +This project does something. +We just haven't decided what that is yet. + +== Source Code + +[source,java] +.Java code from project +---- +include::{sourcedir}/example/StringUtils.java[tags=contains,indent=0] +---- + +This page was built by the following command: + + $ ./gradlew asciidoctor + +== Images + +[.thumb] +image::sunset.jpg[scaledwidth=75%] + +== Attributes + +.Built-in +asciidoctor-version:: {asciidoctor-version} +safe-mode-name:: {safe-mode-name} +docdir:: {docdir} +docfile:: {docfile} +imagesdir:: {imagesdir} +revnumber:: {revnumber} + +.Custom +sourcedir:: {sourcedir} +endpoint-url:: {endpoint-url} + +== Includes + +.include::subdir/_b.adoc[] +==== +include::subdir/_b.adoc[] +==== + +WARNING: Includes can be tricky! + +== build.gradle + +[source,groovy] +---- +include::{build-gradle}[] +---- diff --git a/buildSrc/src/test/resources/samples/showcase/sgbcs-docs/src/docs/asciidoc/subdir/_b.adoc b/buildSrc/src/test/resources/samples/showcase/sgbcs-docs/src/docs/asciidoc/subdir/_b.adoc new file mode 100644 index 00000000..422eb5ee --- /dev/null +++ b/buildSrc/src/test/resources/samples/showcase/sgbcs-docs/src/docs/asciidoc/subdir/_b.adoc @@ -0,0 +1,7 @@ +content from _src/docs/asciidoc/subdir/_b.adoc_. + +.include::_c.adoc[] +[example] +-- +include::_c.adoc[] +-- diff --git a/buildSrc/src/test/resources/samples/showcase/sgbcs-docs/src/docs/asciidoc/subdir/_c.adoc b/buildSrc/src/test/resources/samples/showcase/sgbcs-docs/src/docs/asciidoc/subdir/_c.adoc new file mode 100644 index 00000000..3aca1736 --- /dev/null +++ b/buildSrc/src/test/resources/samples/showcase/sgbcs-docs/src/docs/asciidoc/subdir/_c.adoc @@ -0,0 +1 @@ +content from _src/docs/asciidoc/subdir/c.adoc_. diff --git a/buildSrc/src/test/resources/samples/showcase/sgbcs-docs/src/main/java/example/StringUtils.java b/buildSrc/src/test/resources/samples/showcase/sgbcs-docs/src/main/java/example/StringUtils.java new file mode 100644 index 00000000..3884f862 --- /dev/null +++ b/buildSrc/src/test/resources/samples/showcase/sgbcs-docs/src/main/java/example/StringUtils.java @@ -0,0 +1,9 @@ +package example; + +public class StringUtils { + // tag::contains[] + public boolean contains(String haystack, String needle) { + return haystack.contains(needle); + } + // end::contains[] +} diff --git a/buildSrc/src/test/resources/samples/testsconfiguration/build.gradle b/buildSrc/src/test/resources/samples/testsconfiguration/build.gradle new file mode 100644 index 00000000..364b3bd0 --- /dev/null +++ b/buildSrc/src/test/resources/samples/testsconfiguration/build.gradle @@ -0,0 +1,4 @@ +plugins { + id 'io.spring.convention.tests-configuration' + id 'java' +} diff --git a/buildSrc/src/test/resources/samples/testsconfiguration/core/build.gradle b/buildSrc/src/test/resources/samples/testsconfiguration/core/build.gradle new file mode 100644 index 00000000..3cad4d41 --- /dev/null +++ b/buildSrc/src/test/resources/samples/testsconfiguration/core/build.gradle @@ -0,0 +1,2 @@ +apply plugin: 'io.spring.convention.tests-configuration' +apply plugin: 'java' // second to test ordering diff --git a/buildSrc/src/test/resources/samples/testsconfiguration/core/src/test/java/sample/Dependency.java b/buildSrc/src/test/resources/samples/testsconfiguration/core/src/test/java/sample/Dependency.java new file mode 100644 index 00000000..e0dcbbe4 --- /dev/null +++ b/buildSrc/src/test/resources/samples/testsconfiguration/core/src/test/java/sample/Dependency.java @@ -0,0 +1,3 @@ +package sample; + +public class Dependency {} \ No newline at end of file diff --git a/buildSrc/src/test/resources/samples/testsconfiguration/settings.gradle b/buildSrc/src/test/resources/samples/testsconfiguration/settings.gradle new file mode 100644 index 00000000..eadf90b2 --- /dev/null +++ b/buildSrc/src/test/resources/samples/testsconfiguration/settings.gradle @@ -0,0 +1,2 @@ +include ':core' +include ':web' \ No newline at end of file diff --git a/buildSrc/src/test/resources/samples/testsconfiguration/web/build.gradle b/buildSrc/src/test/resources/samples/testsconfiguration/web/build.gradle new file mode 100644 index 00000000..51609102 --- /dev/null +++ b/buildSrc/src/test/resources/samples/testsconfiguration/web/build.gradle @@ -0,0 +1,10 @@ +apply plugin: 'java' + +repositories { + mavenCentral() +} + +dependencies { + testImplementation project(path: ':core', configuration: 'tests') + testImplementation 'junit:junit:4.12' +} \ No newline at end of file diff --git a/buildSrc/src/test/resources/samples/testsconfiguration/web/src/test/java/sample/DependencyTest.java b/buildSrc/src/test/resources/samples/testsconfiguration/web/src/test/java/sample/DependencyTest.java new file mode 100644 index 00000000..8c378643 --- /dev/null +++ b/buildSrc/src/test/resources/samples/testsconfiguration/web/src/test/java/sample/DependencyTest.java @@ -0,0 +1,10 @@ +package sample; + +import org.junit.*; + +public class DependencyTest { + @Test + public void findsDependencyOnClasspath() { + new Dependency(); + } +} \ No newline at end of file diff --git a/buildSrc/src/test/resources/test-private.pgp b/buildSrc/src/test/resources/test-private.pgp new file mode 100644 index 00000000..b56a134d --- /dev/null +++ b/buildSrc/src/test/resources/test-private.pgp @@ -0,0 +1,83 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +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 +=1LV7 +-----END PGP PRIVATE KEY BLOCK----- \ No newline at end of file diff --git a/spring-session-data-redis/spring-session-data-redis.gradle b/spring-session-data-redis/spring-session-data-redis.gradle index 83ea1273..12249499 100644 --- a/spring-session-data-redis/spring-session-data-redis.gradle +++ b/spring-session-data-redis/spring-session-data-redis.gradle @@ -11,6 +11,9 @@ dependencies { optional "io.projectreactor:reactor-core" optional "org.springframework:spring-web" + testCompile "org.assertj:assertj-core" + testCompile "org.mockito:mockito-core" + testCompile "org.springframework:spring-test" testCompile "io.projectreactor:reactor-test" testCompile "javax.servlet:javax.servlet-api" testCompile "org.springframework:spring-web" diff --git a/spring-session-hazelcast/hazelcast4/hazelcast4.gradle b/spring-session-hazelcast/hazelcast4/hazelcast4.gradle index 913e84f3..ea4b549f 100644 --- a/spring-session-hazelcast/hazelcast4/hazelcast4.gradle +++ b/spring-session-hazelcast/hazelcast4/hazelcast4.gradle @@ -11,7 +11,6 @@ plugins { id 'java-library' id 'io.spring.convention.repository' id 'io.spring.convention.springdependencymangement' - id 'io.spring.convention.dependency-set' id 'io.spring.convention.checkstyle' id 'io.spring.convention.tests-configuration' id 'io.spring.convention.integration-test' @@ -36,6 +35,9 @@ dependencies { compile "javax.annotation:javax.annotation-api" testCompile "javax.servlet:javax.servlet-api" + testCompile "org.assertj:assertj-core" + testCompile "org.mockito:mockito-core" + testCompile "org.springframework:spring-test" testCompile "org.springframework:spring-web" testCompile "org.junit.jupiter:junit-jupiter-api" testCompile "org.springframework.security:spring-security-core" diff --git a/spring-session-hazelcast/spring-session-hazelcast.gradle b/spring-session-hazelcast/spring-session-hazelcast.gradle index bf018d68..133814dd 100644 --- a/spring-session-hazelcast/spring-session-hazelcast.gradle +++ b/spring-session-hazelcast/spring-session-hazelcast.gradle @@ -14,6 +14,9 @@ dependencies { compileOnly(project(":hazelcast4")) testCompile "javax.servlet:javax.servlet-api" + testCompile "org.assertj:assertj-core" + testCompile "org.mockito:mockito-core" + testCompile "org.springframework:spring-test" testCompile "org.springframework:spring-web" testCompile "org.springframework.security:spring-security-core" testCompile "org.junit.jupiter:junit-jupiter-api" diff --git a/spring-session-jdbc/spring-session-jdbc.gradle b/spring-session-jdbc/spring-session-jdbc.gradle index 7cdfd8a0..935edd0e 100644 --- a/spring-session-jdbc/spring-session-jdbc.gradle +++ b/spring-session-jdbc/spring-session-jdbc.gradle @@ -8,6 +8,9 @@ dependencies { compile "org.springframework:spring-jdbc" testCompile "javax.servlet:javax.servlet-api" + testCompile "org.assertj:assertj-core" + testCompile "org.mockito:mockito-core" + testCompile "org.springframework:spring-test" testCompile "org.springframework:spring-web" testCompile "org.springframework.security:spring-security-core" testCompile "org.junit.jupiter:junit-jupiter-api" diff --git a/spring-session-samples/spring-session-sample-boot-findbyusername/spring-session-sample-boot-findbyusername.gradle b/spring-session-samples/spring-session-sample-boot-findbyusername/spring-session-sample-boot-findbyusername.gradle index cd150adc..e6cbe74a 100644 --- a/spring-session-samples/spring-session-sample-boot-findbyusername/spring-session-sample-boot-findbyusername.gradle +++ b/spring-session-samples/spring-session-sample-boot-findbyusername/spring-session-sample-boot-findbyusername.gradle @@ -19,6 +19,7 @@ dependencies { testCompile "org.junit.jupiter:junit-jupiter-api" testRuntime "org.junit.jupiter:junit-jupiter-engine" - integrationTestCompile seleniumDependencies + integrationTestCompile "org.seleniumhq.selenium:htmlunit-driver" + integrationTestCompile "org.seleniumhq.selenium:selenium-support" integrationTestCompile "org.testcontainers:testcontainers" } diff --git a/spring-session-samples/spring-session-sample-boot-hazelcast/spring-session-sample-boot-hazelcast.gradle b/spring-session-samples/spring-session-sample-boot-hazelcast/spring-session-sample-boot-hazelcast.gradle index abaa9867..b997454c 100644 --- a/spring-session-samples/spring-session-sample-boot-hazelcast/spring-session-sample-boot-hazelcast.gradle +++ b/spring-session-samples/spring-session-sample-boot-hazelcast/spring-session-sample-boot-hazelcast.gradle @@ -15,5 +15,6 @@ dependencies { testCompile "org.springframework.boot:spring-boot-starter-test" testCompile "org.junit.jupiter:junit-jupiter-api" testRuntime "org.junit.jupiter:junit-jupiter-engine" - integrationTestCompile seleniumDependencies + integrationTestCompile "org.seleniumhq.selenium:htmlunit-driver" + integrationTestCompile "org.seleniumhq.selenium:selenium-support" } diff --git a/spring-session-samples/spring-session-sample-boot-hazelcast4/spring-session-sample-boot-hazelcast4.gradle b/spring-session-samples/spring-session-sample-boot-hazelcast4/spring-session-sample-boot-hazelcast4.gradle index 7331f9fa..aaca71db 100644 --- a/spring-session-samples/spring-session-sample-boot-hazelcast4/spring-session-sample-boot-hazelcast4.gradle +++ b/spring-session-samples/spring-session-sample-boot-hazelcast4/spring-session-sample-boot-hazelcast4.gradle @@ -16,5 +16,6 @@ dependencies { testCompile "org.springframework.boot:spring-boot-starter-test" testCompile "org.junit.jupiter:junit-jupiter-api" testRuntime "org.junit.jupiter:junit-jupiter-engine" - integrationTestCompile seleniumDependencies + integrationTestCompile "org.seleniumhq.selenium:htmlunit-driver" + integrationTestCompile "org.seleniumhq.selenium:selenium-support" } diff --git a/spring-session-samples/spring-session-sample-boot-jdbc/spring-session-sample-boot-jdbc.gradle b/spring-session-samples/spring-session-sample-boot-jdbc/spring-session-sample-boot-jdbc.gradle index 59ec4ef6..13c12e46 100644 --- a/spring-session-samples/spring-session-sample-boot-jdbc/spring-session-sample-boot-jdbc.gradle +++ b/spring-session-samples/spring-session-sample-boot-jdbc/spring-session-sample-boot-jdbc.gradle @@ -17,5 +17,6 @@ dependencies { testCompile "org.junit.jupiter:junit-jupiter-api" testRuntime "org.junit.jupiter:junit-jupiter-engine" - integrationTestCompile seleniumDependencies + integrationTestCompile "org.seleniumhq.selenium:htmlunit-driver" + integrationTestCompile "org.seleniumhq.selenium:selenium-support" } diff --git a/spring-session-samples/spring-session-sample-boot-redis-json/spring-session-sample-boot-redis-json.gradle b/spring-session-samples/spring-session-sample-boot-redis-json/spring-session-sample-boot-redis-json.gradle index 2a56ebaf..1181e072 100644 --- a/spring-session-samples/spring-session-sample-boot-redis-json/spring-session-sample-boot-redis-json.gradle +++ b/spring-session-samples/spring-session-sample-boot-redis-json/spring-session-sample-boot-redis-json.gradle @@ -19,6 +19,7 @@ dependencies { testCompile "org.junit.jupiter:junit-jupiter-api" testRuntime "org.junit.jupiter:junit-jupiter-engine" - integrationTestCompile seleniumDependencies + integrationTestCompile "org.seleniumhq.selenium:htmlunit-driver" + integrationTestCompile "org.seleniumhq.selenium:selenium-support" integrationTestCompile "org.testcontainers:testcontainers" } diff --git a/spring-session-samples/spring-session-sample-boot-redis-simple/spring-session-sample-boot-redis-simple.gradle b/spring-session-samples/spring-session-sample-boot-redis-simple/spring-session-sample-boot-redis-simple.gradle index dafb79e4..b919150a 100644 --- a/spring-session-samples/spring-session-sample-boot-redis-simple/spring-session-sample-boot-redis-simple.gradle +++ b/spring-session-samples/spring-session-sample-boot-redis-simple/spring-session-sample-boot-redis-simple.gradle @@ -16,6 +16,7 @@ dependencies { testRuntime 'org.junit.jupiter:junit-jupiter-engine' testCompile 'org.springframework.boot:spring-boot-starter-test' - integrationTestCompile seleniumDependencies + integrationTestCompile "org.seleniumhq.selenium:htmlunit-driver" + integrationTestCompile "org.seleniumhq.selenium:selenium-support" integrationTestCompile 'org.testcontainers:testcontainers' } diff --git a/spring-session-samples/spring-session-sample-boot-redis/spring-session-sample-boot-redis.gradle b/spring-session-samples/spring-session-sample-boot-redis/spring-session-sample-boot-redis.gradle index 65a8ec44..88356c5b 100644 --- a/spring-session-samples/spring-session-sample-boot-redis/spring-session-sample-boot-redis.gradle +++ b/spring-session-samples/spring-session-sample-boot-redis/spring-session-sample-boot-redis.gradle @@ -16,6 +16,7 @@ dependencies { testCompile "org.junit.jupiter:junit-jupiter-api" testRuntime "org.junit.jupiter:junit-jupiter-engine" - integrationTestCompile seleniumDependencies + integrationTestCompile "org.seleniumhq.selenium:htmlunit-driver" + integrationTestCompile "org.seleniumhq.selenium:selenium-support" integrationTestCompile "org.testcontainers:testcontainers" } diff --git a/spring-session-samples/spring-session-sample-boot-webflux-custom-cookie/spring-session-sample-boot-webflux-custom-cookie.gradle b/spring-session-samples/spring-session-sample-boot-webflux-custom-cookie/spring-session-sample-boot-webflux-custom-cookie.gradle index 52eda6bb..0adbcea8 100644 --- a/spring-session-samples/spring-session-sample-boot-webflux-custom-cookie/spring-session-sample-boot-webflux-custom-cookie.gradle +++ b/spring-session-samples/spring-session-sample-boot-webflux-custom-cookie/spring-session-sample-boot-webflux-custom-cookie.gradle @@ -12,6 +12,7 @@ dependencies { testCompile "org.junit.jupiter:junit-jupiter-api" testRuntime "org.junit.jupiter:junit-jupiter-engine" - integrationTestCompile seleniumDependencies + integrationTestCompile "org.seleniumhq.selenium:htmlunit-driver" + integrationTestCompile "org.seleniumhq.selenium:selenium-support" integrationTestCompile "org.testcontainers:testcontainers" } diff --git a/spring-session-samples/spring-session-sample-boot-webflux/spring-session-sample-boot-webflux.gradle b/spring-session-samples/spring-session-sample-boot-webflux/spring-session-sample-boot-webflux.gradle index 52eda6bb..0adbcea8 100644 --- a/spring-session-samples/spring-session-sample-boot-webflux/spring-session-sample-boot-webflux.gradle +++ b/spring-session-samples/spring-session-sample-boot-webflux/spring-session-sample-boot-webflux.gradle @@ -12,6 +12,7 @@ dependencies { testCompile "org.junit.jupiter:junit-jupiter-api" testRuntime "org.junit.jupiter:junit-jupiter-engine" - integrationTestCompile seleniumDependencies + integrationTestCompile "org.seleniumhq.selenium:htmlunit-driver" + integrationTestCompile "org.seleniumhq.selenium:selenium-support" integrationTestCompile "org.testcontainers:testcontainers" } diff --git a/spring-session-samples/spring-session-sample-javaconfig-custom-cookie/spring-session-sample-javaconfig-custom-cookie.gradle b/spring-session-samples/spring-session-sample-javaconfig-custom-cookie/spring-session-sample-javaconfig-custom-cookie.gradle index 4ef73b46..96d29a8c 100644 --- a/spring-session-samples/spring-session-sample-javaconfig-custom-cookie/spring-session-sample-javaconfig-custom-cookie.gradle +++ b/spring-session-samples/spring-session-sample-javaconfig-custom-cookie/spring-session-sample-javaconfig-custom-cookie.gradle @@ -1,4 +1,7 @@ -apply plugin: 'io.spring.convention.spring-sample-war' +plugins { + id "org.gretty" version "3.0.7" + id "io.spring.convention.spring-sample-war" +} dependencies { compile project(':spring-session-data-redis') @@ -6,8 +9,12 @@ dependencies { compile "io.lettuce:lettuce-core" compile "org.webjars:bootstrap" compile "org.webjars:webjars-taglib" - compile jstlDependencies - compile slf4jDependencies + compile "javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api" + compile "org.apache.taglibs:taglibs-standard-jstlel" + compile "org.slf4j:slf4j-api" + compile "org.slf4j:jcl-over-slf4j" + compile "org.slf4j:log4j-over-slf4j" + compile "ch.qos.logback:logback-classic" compile "org.testcontainers:testcontainers" providedCompile "javax.servlet:javax.servlet-api" @@ -16,7 +23,8 @@ dependencies { testCompile "org.junit.jupiter:junit-jupiter-api" testRuntime "org.junit.jupiter:junit-jupiter-engine" - integrationTestCompile seleniumDependencies + integrationTestCompile "org.seleniumhq.selenium:htmlunit-driver" + integrationTestCompile "org.seleniumhq.selenium:selenium-support" } gretty { diff --git a/spring-session-samples/spring-session-sample-javaconfig-hazelcast/spring-session-sample-javaconfig-hazelcast.gradle b/spring-session-samples/spring-session-sample-javaconfig-hazelcast/spring-session-sample-javaconfig-hazelcast.gradle index 9255de93..051df2c8 100644 --- a/spring-session-samples/spring-session-sample-javaconfig-hazelcast/spring-session-sample-javaconfig-hazelcast.gradle +++ b/spring-session-samples/spring-session-sample-javaconfig-hazelcast/spring-session-sample-javaconfig-hazelcast.gradle @@ -1,4 +1,7 @@ -apply plugin: 'io.spring.convention.spring-sample-war' +plugins { + id "org.gretty" version "3.0.7" + id "io.spring.convention.spring-sample-war" +} dependencies { compile project(':spring-session-hazelcast') @@ -9,8 +12,12 @@ dependencies { compile "org.webjars:bootstrap" compile "org.webjars:webjars-taglib" compile "com.hazelcast:hazelcast-client" - compile jstlDependencies - compile slf4jDependencies + compile "javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api" + compile "org.apache.taglibs:taglibs-standard-jstlel" + compile "org.slf4j:slf4j-api" + compile "org.slf4j:jcl-over-slf4j" + compile "org.slf4j:log4j-over-slf4j" + compile "ch.qos.logback:logback-classic" providedCompile "javax.servlet:javax.servlet-api" providedCompile "javax.servlet.jsp:javax.servlet.jsp-api" @@ -19,5 +26,6 @@ dependencies { testCompile "org.junit.jupiter:junit-jupiter-api" testRuntime "org.junit.jupiter:junit-jupiter-engine" - integrationTestCompile seleniumDependencies + integrationTestCompile "org.seleniumhq.selenium:htmlunit-driver" + integrationTestCompile "org.seleniumhq.selenium:selenium-support" } diff --git a/spring-session-samples/spring-session-sample-javaconfig-jdbc/spring-session-sample-javaconfig-jdbc.gradle b/spring-session-samples/spring-session-sample-javaconfig-jdbc/spring-session-sample-javaconfig-jdbc.gradle index 4ed115a9..38f67e10 100644 --- a/spring-session-samples/spring-session-sample-javaconfig-jdbc/spring-session-sample-javaconfig-jdbc.gradle +++ b/spring-session-samples/spring-session-sample-javaconfig-jdbc/spring-session-sample-javaconfig-jdbc.gradle @@ -1,4 +1,7 @@ -apply plugin: 'io.spring.convention.spring-sample-war' +plugins { + id "org.gretty" version "3.0.7" + id "io.spring.convention.spring-sample-war" +} dependencies { compile project(':spring-session-jdbc') @@ -6,8 +9,12 @@ dependencies { compile "org.webjars:bootstrap" compile "org.webjars:webjars-taglib" compile "com.h2database:h2" - compile jstlDependencies - compile slf4jDependencies + compile "javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api" + compile "org.apache.taglibs:taglibs-standard-jstlel" + compile "org.slf4j:slf4j-api" + compile "org.slf4j:jcl-over-slf4j" + compile "org.slf4j:log4j-over-slf4j" + compile "ch.qos.logback:logback-classic" providedCompile "javax.servlet:javax.servlet-api" @@ -15,5 +22,6 @@ dependencies { testCompile "org.junit.jupiter:junit-jupiter-api" testRuntime "org.junit.jupiter:junit-jupiter-engine" - integrationTestCompile seleniumDependencies + integrationTestCompile "org.seleniumhq.selenium:htmlunit-driver" + integrationTestCompile "org.seleniumhq.selenium:selenium-support" } diff --git a/spring-session-samples/spring-session-sample-javaconfig-redis/spring-session-sample-javaconfig-redis.gradle b/spring-session-samples/spring-session-sample-javaconfig-redis/spring-session-sample-javaconfig-redis.gradle index 4ef73b46..96d29a8c 100644 --- a/spring-session-samples/spring-session-sample-javaconfig-redis/spring-session-sample-javaconfig-redis.gradle +++ b/spring-session-samples/spring-session-sample-javaconfig-redis/spring-session-sample-javaconfig-redis.gradle @@ -1,4 +1,7 @@ -apply plugin: 'io.spring.convention.spring-sample-war' +plugins { + id "org.gretty" version "3.0.7" + id "io.spring.convention.spring-sample-war" +} dependencies { compile project(':spring-session-data-redis') @@ -6,8 +9,12 @@ dependencies { compile "io.lettuce:lettuce-core" compile "org.webjars:bootstrap" compile "org.webjars:webjars-taglib" - compile jstlDependencies - compile slf4jDependencies + compile "javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api" + compile "org.apache.taglibs:taglibs-standard-jstlel" + compile "org.slf4j:slf4j-api" + compile "org.slf4j:jcl-over-slf4j" + compile "org.slf4j:log4j-over-slf4j" + compile "ch.qos.logback:logback-classic" compile "org.testcontainers:testcontainers" providedCompile "javax.servlet:javax.servlet-api" @@ -16,7 +23,8 @@ dependencies { testCompile "org.junit.jupiter:junit-jupiter-api" testRuntime "org.junit.jupiter:junit-jupiter-engine" - integrationTestCompile seleniumDependencies + integrationTestCompile "org.seleniumhq.selenium:htmlunit-driver" + integrationTestCompile "org.seleniumhq.selenium:selenium-support" } gretty { diff --git a/spring-session-samples/spring-session-sample-javaconfig-rest/spring-session-sample-javaconfig-rest.gradle b/spring-session-samples/spring-session-sample-javaconfig-rest/spring-session-sample-javaconfig-rest.gradle index 5f953393..1a784bc3 100644 --- a/spring-session-samples/spring-session-sample-javaconfig-rest/spring-session-sample-javaconfig-rest.gradle +++ b/spring-session-samples/spring-session-sample-javaconfig-rest/spring-session-sample-javaconfig-rest.gradle @@ -1,4 +1,7 @@ -apply plugin: 'io.spring.convention.spring-sample-war' +plugins { + id "org.gretty" version "3.0.7" + id "io.spring.convention.spring-sample-war" +} dependencies { compile project(':spring-session-data-redis') @@ -7,8 +10,12 @@ dependencies { compile "org.springframework.security:spring-security-config" compile "org.springframework.security:spring-security-web" compile "com.fasterxml.jackson.core:jackson-databind" - compile jstlDependencies - compile slf4jDependencies + compile "javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api" + compile "org.apache.taglibs:taglibs-standard-jstlel" + compile "org.slf4j:slf4j-api" + compile "org.slf4j:jcl-over-slf4j" + compile "org.slf4j:log4j-over-slf4j" + compile "ch.qos.logback:logback-classic" compile "org.testcontainers:testcontainers" providedCompile "javax.servlet:javax.servlet-api" diff --git a/spring-session-samples/spring-session-sample-javaconfig-security/spring-session-sample-javaconfig-security.gradle b/spring-session-samples/spring-session-sample-javaconfig-security/spring-session-sample-javaconfig-security.gradle index e418f640..9b203637 100644 --- a/spring-session-samples/spring-session-sample-javaconfig-security/spring-session-sample-javaconfig-security.gradle +++ b/spring-session-samples/spring-session-sample-javaconfig-security/spring-session-sample-javaconfig-security.gradle @@ -1,4 +1,7 @@ -apply plugin: 'io.spring.convention.spring-sample-war' +plugins { + id "org.gretty" version "3.0.7" + id "io.spring.convention.spring-sample-war" +} dependencies { compile project(':spring-session-data-redis') @@ -8,8 +11,12 @@ dependencies { compile "io.lettuce:lettuce-core" compile "org.webjars:bootstrap" compile "org.webjars:webjars-taglib" - compile jstlDependencies - compile slf4jDependencies + compile "javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api" + compile "org.apache.taglibs:taglibs-standard-jstlel" + compile "org.slf4j:slf4j-api" + compile "org.slf4j:jcl-over-slf4j" + compile "org.slf4j:log4j-over-slf4j" + compile "ch.qos.logback:logback-classic" compile "org.testcontainers:testcontainers" providedCompile "javax.servlet:javax.servlet-api" @@ -20,7 +27,8 @@ dependencies { testCompile "org.junit.jupiter:junit-jupiter-api" testRuntime "org.junit.jupiter:junit-jupiter-engine" - integrationTestCompile seleniumDependencies + integrationTestCompile "org.seleniumhq.selenium:htmlunit-driver" + integrationTestCompile "org.seleniumhq.selenium:selenium-support" } gretty { diff --git a/spring-session-samples/spring-session-sample-misc-hazelcast/spring-session-sample-misc-hazelcast.gradle b/spring-session-samples/spring-session-sample-misc-hazelcast/spring-session-sample-misc-hazelcast.gradle index 5121e401..d9f4e90e 100644 --- a/spring-session-samples/spring-session-sample-misc-hazelcast/spring-session-sample-misc-hazelcast.gradle +++ b/spring-session-samples/spring-session-sample-misc-hazelcast/spring-session-sample-misc-hazelcast.gradle @@ -1,12 +1,19 @@ -apply plugin: 'io.spring.convention.spring-sample-war' +plugins { + id "org.gretty" version "3.0.7" + id "io.spring.convention.spring-sample-war" +} dependencies { compile project(':spring-session-core') compile "org.webjars:bootstrap" compile "org.webjars:webjars-taglib" compile "com.hazelcast:hazelcast-client" - compile jstlDependencies - compile slf4jDependencies + compile "javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api" + compile "org.apache.taglibs:taglibs-standard-jstlel" + compile "org.slf4j:slf4j-api" + compile "org.slf4j:jcl-over-slf4j" + compile "org.slf4j:log4j-over-slf4j" + compile "ch.qos.logback:logback-classic" providedCompile "javax.servlet:javax.servlet-api" @@ -14,5 +21,6 @@ dependencies { testCompile "org.junit.jupiter:junit-jupiter-api" testRuntime "org.junit.jupiter:junit-jupiter-engine" - integrationTestCompile seleniumDependencies + integrationTestCompile "org.seleniumhq.selenium:htmlunit-driver" + integrationTestCompile "org.seleniumhq.selenium:selenium-support" } diff --git a/spring-session-samples/spring-session-sample-xml-jdbc/spring-session-sample-xml-jdbc.gradle b/spring-session-samples/spring-session-sample-xml-jdbc/spring-session-sample-xml-jdbc.gradle index de6e3bcb..637a1c4b 100644 --- a/spring-session-samples/spring-session-sample-xml-jdbc/spring-session-sample-xml-jdbc.gradle +++ b/spring-session-samples/spring-session-sample-xml-jdbc/spring-session-sample-xml-jdbc.gradle @@ -1,4 +1,7 @@ -apply plugin: 'io.spring.convention.spring-sample-war' +plugins { + id "org.gretty" version "3.0.7" + id "io.spring.convention.spring-sample-war" +} dependencies { compile project(':spring-session-jdbc') @@ -6,8 +9,12 @@ dependencies { compile "org.webjars:bootstrap" compile "org.webjars:webjars-taglib" compile "com.h2database:h2" - compile jstlDependencies - compile slf4jDependencies + compile "javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api" + compile "org.apache.taglibs:taglibs-standard-jstlel" + compile "org.slf4j:slf4j-api" + compile "org.slf4j:jcl-over-slf4j" + compile "org.slf4j:log4j-over-slf4j" + compile "ch.qos.logback:logback-classic" providedCompile "javax.servlet:javax.servlet-api" @@ -15,5 +22,6 @@ dependencies { testCompile "org.junit.jupiter:junit-jupiter-api" testRuntime "org.junit.jupiter:junit-jupiter-engine" - integrationTestCompile seleniumDependencies + integrationTestCompile "org.seleniumhq.selenium:htmlunit-driver" + integrationTestCompile "org.seleniumhq.selenium:selenium-support" } diff --git a/spring-session-samples/spring-session-sample-xml-redis/spring-session-sample-xml-redis.gradle b/spring-session-samples/spring-session-sample-xml-redis/spring-session-sample-xml-redis.gradle index e094ce96..64185249 100644 --- a/spring-session-samples/spring-session-sample-xml-redis/spring-session-sample-xml-redis.gradle +++ b/spring-session-samples/spring-session-sample-xml-redis/spring-session-sample-xml-redis.gradle @@ -1,4 +1,7 @@ -apply plugin: 'io.spring.convention.spring-sample-war' +plugins { + id "org.gretty" version "3.0.7" + id "io.spring.convention.spring-sample-war" +} dependencies { compile project(':spring-session-data-redis') @@ -6,8 +9,12 @@ dependencies { compile "io.lettuce:lettuce-core" compile "org.webjars:bootstrap" compile "org.webjars:webjars-taglib" - compile jstlDependencies - compile slf4jDependencies + compile "javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api" + compile "org.apache.taglibs:taglibs-standard-jstlel" + compile "org.slf4j:slf4j-api" + compile "org.slf4j:jcl-over-slf4j" + compile "org.slf4j:log4j-over-slf4j" + compile "ch.qos.logback:logback-classic" compile "org.testcontainers:testcontainers" providedCompile "javax.servlet:javax.servlet-api" @@ -16,7 +23,8 @@ dependencies { testCompile "org.junit.jupiter:junit-jupiter-api" testRuntime "org.junit.jupiter:junit-jupiter-engine" - integrationTestCompile seleniumDependencies + integrationTestCompile "org.seleniumhq.selenium:htmlunit-driver" + integrationTestCompile "org.seleniumhq.selenium:selenium-support" } gretty {