├── .editorconfig ├── .github ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── dco.yml ├── dependabot.yml └── workflows │ ├── dependency-review-action.yml │ ├── dependency-submission.yml │ ├── gradle.yml │ ├── submit-dependency-graph.yml │ └── update-jdks.yml ├── .gitignore ├── .teamcity ├── jdks.yaml ├── pom.xml ├── settings.kts └── src │ └── main │ └── kotlin │ ├── AbstractBuildPluginType.kt │ ├── AbstractBuildType.kt │ ├── BuildPlugins.kt │ └── PublishPlugins.kt ├── LICENSE ├── README.adoc ├── gradle-guides-plugin ├── build.gradle.kts └── src │ ├── functionalTest │ └── groovy │ │ └── org │ │ └── gradle │ │ └── docs │ │ ├── AbstractBaseDocumentationFunctionalTest.groovy │ │ ├── AbstractFunctionalTest.groovy │ │ ├── AbstractRenderableDocumentationFunctionalTest.groovy │ │ ├── AbstractWellBehavePluginFunctionalTest.groovy │ │ ├── DocumentationTrait.groovy │ │ ├── DocumentationWellBehavePluginFunctionalTest.groovy │ │ ├── LegacyPluginTrait.groovy │ │ ├── MixedDocumentationFunctionalTest.groovy │ │ ├── TestFile.java │ │ ├── ZipFileFixture.java │ │ ├── guides │ │ ├── AbstractGuideFunctionalSpec.groovy │ │ ├── BasicGuidesDocumentationFunctionalTest.groovy │ │ ├── CustomLayoutGuidesDocumentationFunctionalTest.groovy │ │ ├── GuideWellBehavePluginFunctionalTest.groovy │ │ ├── GuidesBaseDocumentationFunctionalTest.groovy │ │ ├── GuidesRenderableDocumentationFunctionalTest.groovy │ │ ├── GuidesTrait.groovy │ │ ├── IncrementalGuidesDocumentationFunctionalTest.groovy │ │ ├── MultipleGuidesDocumentationFunctionalTest.groovy │ │ └── PublishingGuidesDocumentationFunctionalTest.groovy │ │ ├── samples │ │ ├── AbstractBasicSampleFunctionalTest.groovy │ │ ├── AbstractBothDslSampleFunctionalTest.groovy │ │ ├── AbstractExemplarBothDslSampleFunctionalTest.groovy │ │ ├── AbstractExemplarGroovyDslSampleFunctionalTest.groovy │ │ ├── AbstractExemplarKotlinDslSampleFunctionalTest.groovy │ │ ├── AbstractGroovyDslSampleFunctionalTest.groovy │ │ ├── AbstractKotlinDslSampleFunctionalTest.groovy │ │ ├── AbstractSampleFunctionalSpec.groovy │ │ ├── AbstractTestWithExemplarSampleFunctionalTest.groovy │ │ ├── BasicConventionalBothDslSampleFunctionalTest.groovy │ │ ├── BasicConventionalGroovyDslSampleFunctionalTest.groovy │ │ ├── BasicConventionalKotlinDslSampleFunctionalTest.groovy │ │ ├── BasicExplicitBothDslSampleFunctionalTest.groovy │ │ ├── BasicExplicitGroovyDslSampleFunctionalTest.groovy │ │ ├── BasicExplicitKotlinDslSampleFunctionalTest.groovy │ │ ├── IncrementalSamplesFunctionalTest.groovy │ │ ├── SamplesBaseDocumentationFunctionalTest.groovy │ │ ├── SamplesPluginFunctionalTest.groovy │ │ ├── SamplesRenderableDocumentationFunctionalTest.groovy │ │ ├── SamplesTrait.groovy │ │ ├── SamplesWellBehavePluginFunctionalTest.groovy │ │ ├── TestWithExemplarConventionalBothDslSampleFunctionalTest.groovy │ │ ├── TestWithExemplarConventionalGroovyDslSampleFunctionalTest.groovy │ │ ├── TestWithExemplarConventionalKotlinDslSampleFunctionalTest.groovy │ │ ├── TestWithExemplarExplicitBothDslSampleFunctionalTest.groovy │ │ ├── TestWithExemplarExplicitGroovyDslSampleFunctionalTest.groovy │ │ └── TestWithExemplarExplicitKotlinDslSampleFunctionalTest.groovy │ │ └── snippets │ │ ├── SnippetsBaseDocumentationFunctionalTest.groovy │ │ └── SnippetsTrait.groovy │ ├── main │ └── java │ │ └── org │ │ └── gradle │ │ └── docs │ │ ├── DocumentationExtension.java │ │ ├── guides │ │ ├── Guide.java │ │ ├── GuideSummary.java │ │ ├── Guides.java │ │ ├── GuidesDistribution.java │ │ └── internal │ │ │ ├── GuideBinary.java │ │ │ ├── GuideContentBinary.java │ │ │ ├── GuideInternal.java │ │ │ ├── GuidesDocumentationPlugin.java │ │ │ ├── GuidesInternal.java │ │ │ ├── LegacyGuideDocumentationPlugin.java │ │ │ ├── TestableAsciidoctorGuideContentBinary.java │ │ │ └── tasks │ │ │ └── GenerateGuidePageAsciidoc.java │ │ ├── internal │ │ ├── Asserts.java │ │ ├── BuildDocumentationPlugin.java │ │ ├── DocumentationBasePlugin.java │ │ ├── DocumentationExtensionInternal.java │ │ ├── FileUtils.java │ │ ├── IOUtils.java │ │ ├── RenderableContentBinary.java │ │ ├── StringUtils.java │ │ ├── TestableAsciidoctorContentBinary.java │ │ ├── TestableRenderedContentLinksBinary.java │ │ ├── ViewableContentBinary.java │ │ ├── configure │ │ │ ├── AsciidoctorTasks.java │ │ │ └── ContentBinaries.java │ │ ├── exemplar │ │ │ ├── AnsiCharactersToPlainTextOutputStream.java │ │ │ ├── AsciidoctorContentTest.java │ │ │ ├── AsciidoctorContentTestCase.java │ │ │ ├── AsciidoctorContentTestConsoleType.java │ │ │ ├── AsciidoctorContentTestParameters.java │ │ │ ├── AsciidoctorContentTestWorkerAction.java │ │ │ ├── GradleUserHomePathOutputNormalizer.java │ │ │ ├── OutputNormalizers.java │ │ │ └── UserInputOutputVerifier.java │ │ └── tasks │ │ │ ├── CheckLinks.java │ │ │ └── ViewDocumentation.java │ │ ├── samples │ │ ├── Dsl.java │ │ ├── Sample.java │ │ ├── SampleSummary.java │ │ ├── Samples.java │ │ ├── SamplesDistribution.java │ │ ├── Template.java │ │ ├── internal │ │ │ ├── LegacySamplesDocumentationPlugin.java │ │ │ ├── SampleArchiveBinary.java │ │ │ ├── SampleBinary.java │ │ │ ├── SampleContentBinary.java │ │ │ ├── SampleExemplarBinary.java │ │ │ ├── SampleInstallBinary.java │ │ │ ├── SampleInternal.java │ │ │ ├── SamplesDocumentationPlugin.java │ │ │ ├── SamplesInternal.java │ │ │ ├── TemplateInternal.java │ │ │ ├── TestableAsciidoctorSampleContentBinary.java │ │ │ └── tasks │ │ │ │ ├── GenerateSampleIndexAsciidoc.java │ │ │ │ ├── GenerateSamplePageAsciidoc.java │ │ │ │ ├── GenerateSanityCheckTests.java │ │ │ │ ├── GenerateTestSource.java │ │ │ │ ├── InstallSample.java │ │ │ │ ├── SamplesReport.java │ │ │ │ ├── SyncWithProvider.java │ │ │ │ ├── ValidateSampleBinary.java │ │ │ │ └── ZipSample.java │ │ └── package-info.java │ │ └── snippets │ │ ├── Snippet.java │ │ ├── Snippets.java │ │ └── internal │ │ ├── SnippetBinary.java │ │ ├── SnippetInternal.java │ │ ├── SnippetsDocumentationPlugin.java │ │ └── SnippetsInternal.java │ └── test │ └── groovy │ └── org │ └── gradle │ └── docs │ └── internal │ ├── CheckLinksSpec.groovy │ └── StringUtilsTest.groovy ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.{yaml,yml,xml}] 12 | indent_size = 2 13 | 14 | [{*.html,*.js,*.css}] 15 | indent_size = 2 16 | 17 | [gradlew.bat] 18 | end_of_line = crlf 19 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @gradle/bt-developer-productivity 2 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # Code of Conduct 5 | 6 | In order to foster a more inclusive community, Gradle has adopted the [Contributor Covenant](https://www.contributor-covenant.org/version/1/4/code-of-conduct/). 7 | 8 | Contributors must follow the Code of Conduct outlined at [https://gradle.org/conduct/](https://gradle.org/conduct/). 9 | -------------------------------------------------------------------------------- /.github/dco.yml: -------------------------------------------------------------------------------- 1 | # Disable sign-off chcecking for members of the Gradle GitHub organization 2 | require: 3 | members: false 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gradle" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | - package-ecosystem: "github-actions" 13 | directory: "/" 14 | schedule: 15 | interval: "daily" 16 | labels: 17 | - "@dev-productivity" 18 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review-action.yml: -------------------------------------------------------------------------------- 1 | name: Dependency review for pull requests 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | 7 | permissions: 8 | contents: write 9 | 10 | jobs: 11 | dependency-submission: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-java@v4 16 | with: 17 | distribution: temurin 18 | java-version: 21 19 | 20 | - name: Generate and submit dependency graph 21 | uses: gradle/actions/dependency-submission@v4 22 | 23 | - name: Perform dependency review 24 | uses: actions/dependency-review-action@v4 25 | -------------------------------------------------------------------------------- /.github/workflows/dependency-submission.yml: -------------------------------------------------------------------------------- 1 | name: Generate and save dependency graph 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - master 8 | pull_request: 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | dependency-submission: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout sources 18 | uses: actions/checkout@v4 19 | - name: Setup Java 20 | uses: actions/setup-java@v4 21 | with: 22 | distribution: 'temurin' 23 | java-version: 21 24 | - name: Generate and save dependency graph 25 | uses: gradle/actions/dependency-submission@v4 26 | with: 27 | dependency-graph: generate-and-upload 28 | -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | # This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time 6 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle 7 | 8 | name: Build with Gradle 9 | 10 | on: 11 | push: 12 | branches: 13 | - master 14 | pull_request: 15 | branches: 16 | - master 17 | workflow_dispatch: 18 | merge_group: 19 | 20 | permissions: 21 | contents: read 22 | 23 | jobs: 24 | build: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v4 28 | - name: Setup Java 29 | uses: actions/setup-java@v4 30 | with: 31 | java-version: '17' 32 | distribution: 'temurin' 33 | - name: Setup Gradle 34 | uses: gradle/actions/setup-gradle@v4 35 | - run: ./gradlew test 36 | -------------------------------------------------------------------------------- /.github/workflows/submit-dependency-graph.yml: -------------------------------------------------------------------------------- 1 | name: Download and submit dependency graph 2 | 3 | on: 4 | workflow_run: 5 | workflows: [ 'Generate and save dependency graph' ] 6 | types: [ completed ] 7 | 8 | permissions: 9 | actions: read 10 | contents: write 11 | 12 | jobs: 13 | submit-dependency-graph: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Download and submit dependency graph 17 | uses: gradle/actions/dependency-submission@v4 18 | with: 19 | dependency-graph: download-and-submit 20 | -------------------------------------------------------------------------------- /.github/workflows/update-jdks.yml: -------------------------------------------------------------------------------- 1 | name: Update jdks.yaml 2 | 3 | on: 4 | workflow_dispatch: # Allows manual triggering of the action 5 | schedule: # Runs the action weekly on Monday at 3:42 UTC 6 | - cron: '42 3 * * 1' 7 | 8 | permissions: 9 | contents: write 10 | pull-requests: write 11 | 12 | jobs: 13 | update-jdks: 14 | if: github.repository == 'gradle/guides' 15 | permissions: 16 | contents: write 17 | pull-requests: write 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout repository 21 | uses: actions/checkout@v4 22 | with: 23 | persist-credentials: false 24 | fetch-depth: 0 25 | - name: Update jdks.yaml 26 | uses: gradle/update-jdks-action@main 27 | - name: Create Pull Request 28 | id: create-pull-request 29 | uses: peter-evans/create-pull-request@v7 30 | with: 31 | signoff: true 32 | sign-commits: true 33 | commit-message: "Update jdks.yaml" 34 | add-paths: .teamcity/jdks.yaml 35 | title: "Update jdks.yaml" 36 | body: "This PR updates JDK to the latest versions available in `.teamcity/jdks.yaml`." 37 | delete-branch: true 38 | branch-suffix: timestamp 39 | labels: | 40 | @dev-productivity 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Vim 2 | *.sw? 3 | 4 | /.gradle 5 | build 6 | .idea 7 | *.iml 8 | .DS_Store 9 | 10 | # maven 11 | /.teamcity/target/ 12 | -------------------------------------------------------------------------------- /.teamcity/jdks.yaml: -------------------------------------------------------------------------------- 1 | version: "v1" 2 | jdks: 3 | - params: 4 | - "env.JAVA_HOME" 5 | os: "linux" 6 | arch: "amd64" 7 | vendor: "temurin" 8 | version: "jdk-17.0.15+6" 9 | sha256: "9616877c733c9249328ea9bd83a5c8c30e0f9a7af180cac8ffda9034161c2df2" 10 | -------------------------------------------------------------------------------- /.teamcity/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | Gradle Guides Plugin DSL Script 5 | Gradle_Guides 6 | Gradle_Guides_dsl 7 | 1.0-SNAPSHOT 8 | 9 | 10 | org.jetbrains.teamcity 11 | configs-dsl-kotlin-parent 12 | 1.0-SNAPSHOT 13 | 14 | 15 | 16 | 17 | jetbrains-all 18 | https://download.jetbrains.com/teamcity-repository 19 | 20 | true 21 | 22 | 23 | 24 | teamcity-server 25 | https://builds.gradle.org/app/dsl-plugins-repository 26 | 27 | true 28 | 29 | 30 | 31 | 32 | 33 | 34 | JetBrains 35 | https://download.jetbrains.com/teamcity-repository 36 | 37 | 38 | 39 | 40 | ${project.basedir}/src/main/kotlin 41 | 42 | 43 | kotlin-maven-plugin 44 | org.jetbrains.kotlin 45 | ${kotlin.version} 46 | 47 | 48 | 49 | 50 | compile 51 | process-sources 52 | 53 | compile 54 | 55 | 56 | 57 | test-compile 58 | process-test-sources 59 | 60 | test-compile 61 | 62 | 63 | 64 | 65 | 66 | org.jetbrains.teamcity 67 | teamcity-configs-maven-plugin 68 | ${teamcity.dsl.version} 69 | 70 | kotlin 71 | target/generated-configs 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | org.jetbrains.teamcity 80 | configs-dsl-kotlin-latest 81 | ${teamcity.dsl.version} 82 | compile 83 | 84 | 85 | org.jetbrains.teamcity 86 | configs-dsl-kotlin-plugins-latest 87 | 1.0-SNAPSHOT 88 | pom 89 | compile 90 | 91 | 92 | org.jetbrains.kotlin 93 | kotlin-stdlib-jdk8 94 | ${kotlin.version} 95 | compile 96 | 97 | 98 | org.jetbrains.kotlin 99 | kotlin-script-runtime 100 | ${kotlin.version} 101 | compile 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /.teamcity/settings.kts: -------------------------------------------------------------------------------- 1 | import jetbrains.buildServer.configs.kotlin.* 2 | 3 | /* 4 | The settings script is an entry point for defining a TeamCity 5 | project hierarchy. The script should contain a single call to the 6 | project() function with a Project instance or an init function as 7 | an argument. 8 | 9 | VcsRoots, BuildTypes, Templates, and subprojects can be 10 | registered inside the project using the vcsRoot(), buildType(), 11 | template(), and subProject() methods respectively. 12 | 13 | To debug settings scripts in command-line, run the 14 | 15 | mvnDebug org.jetbrains.teamcity:teamcity-configs-maven-plugin:generate 16 | 17 | command and attach your debugger to the port 8000. 18 | 19 | To debug in IntelliJ Idea, open the 'Maven Projects' tool window (View 20 | -> Tool Windows -> Maven Projects), find the generate task node 21 | (Plugins -> teamcity-configs -> teamcity-configs:generate), the 22 | 'Debug' option is available in the context menu for the task. 23 | */ 24 | 25 | version = "2025.03" 26 | 27 | project { 28 | buildType(BuildPlugins) 29 | buildType(PublishPlugins) 30 | } 31 | -------------------------------------------------------------------------------- /.teamcity/src/main/kotlin/AbstractBuildPluginType.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import jetbrains.buildServer.configs.kotlin.BuildType 18 | import jetbrains.buildServer.configs.kotlin.DslContext 19 | import jetbrains.buildServer.configs.kotlin.buildFeatures.PullRequests 20 | import jetbrains.buildServer.configs.kotlin.buildFeatures.commitStatusPublisher 21 | import jetbrains.buildServer.configs.kotlin.buildFeatures.pullRequests 22 | import jetbrains.buildServer.configs.kotlin.buildSteps.gradle 23 | import jetbrains.buildServer.configs.kotlin.triggers.vcs 24 | 25 | open class AbstractBuildPluginType(init: BuildType.() -> Unit) : AbstractBuildType({ 26 | features { 27 | pullRequests { 28 | provider = github { 29 | authType = vcsRoot() 30 | filterTargetBranch = "main" 31 | filterAuthorRole = PullRequests.GitHubRoleFilter.MEMBER_OR_COLLABORATOR 32 | ignoreDrafts = true 33 | } 34 | } 35 | 36 | commitStatusPublisher { 37 | vcsRootExtId = DslContext.settingsRoot.id?.value 38 | publisher = github { 39 | githubUrl = "https://api.github.com" 40 | authType = personalToken { 41 | token = "%github.bot-teamcity.token%" 42 | } 43 | } 44 | } 45 | } 46 | 47 | triggers { 48 | vcs { 49 | branchFilter = """ 50 | +:* 51 | -:pull/* 52 | """.trimIndent() 53 | 54 | triggerRules = """ 55 | +:. 56 | """.trimIndent() 57 | 58 | perCheckinTriggering = true 59 | enableQueueOptimization = false 60 | enabled = true 61 | } 62 | } 63 | 64 | steps { 65 | gradle { 66 | useGradleWrapper = true 67 | tasks = ":gradle-guides-plugin:check" 68 | } 69 | } 70 | 71 | init() 72 | }) 73 | -------------------------------------------------------------------------------- /.teamcity/src/main/kotlin/AbstractBuildType.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import jetbrains.buildServer.configs.kotlin.BuildType 18 | import jetbrains.buildServer.configs.kotlin.CheckoutMode 19 | import jetbrains.buildServer.configs.kotlin.DslContext 20 | import jetbrains.buildServer.configs.kotlin.buildFeatures.commitStatusPublisher 21 | 22 | open class AbstractBuildType(init: BuildType.() -> Unit) : BuildType({ 23 | vcs { 24 | root(DslContext.settingsRoot) 25 | checkoutMode = CheckoutMode.ON_AGENT 26 | cleanCheckout = true 27 | } 28 | 29 | params { 30 | param("env.LC_ALL", "en_US.UTF-8") 31 | param("env.GRADLE_CACHE_REMOTE_URL", "%gradle.cache.remote.url%") 32 | param("env.GRADLE_CACHE_REMOTE_USERNAME", "%gradle.cache.remote.username%") 33 | param("env.GRADLE_CACHE_REMOTE_PASSWORD", "%gradle.cache.remote.password%") 34 | param("env.DEVELOCITY_ACCESS_KEY", "%ge.gradle.org.access.key%") 35 | } 36 | 37 | init() 38 | }) 39 | -------------------------------------------------------------------------------- /.teamcity/src/main/kotlin/BuildPlugins.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | object BuildPlugins : AbstractBuildPluginType({ 18 | name = "Build Guides Plugin" 19 | 20 | requirements { 21 | contains("teamcity.agent.jvm.os.name", "Linux") 22 | } 23 | 24 | params { 25 | param("env.JAVA_HOME", "%linux.java17.openjdk.64bit%") 26 | } 27 | 28 | artifactRules = """ 29 | build/published-guides/** => published-guides 30 | """.trimIndent() 31 | }) 32 | -------------------------------------------------------------------------------- /.teamcity/src/main/kotlin/PublishPlugins.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import jetbrains.buildServer.configs.kotlin.ParameterDisplay 18 | import jetbrains.buildServer.configs.kotlin.buildSteps.gradle 19 | 20 | object PublishPlugins : AbstractBuildType({ 21 | name = "Publish Guide Plugins" 22 | type = Type.DEPLOYMENT 23 | 24 | requirements { 25 | contains("teamcity.agent.jvm.os.name", "Linux") 26 | } 27 | 28 | params { 29 | param("env.JAVA_HOME", "%linux.java17.openjdk.64bit%") 30 | password("env.GRADLE_PUBLISH_KEY", "%plugin.portal.publish.key%", display = ParameterDisplay.HIDDEN) 31 | password("env.GRADLE_PUBLISH_SECRET", "%plugin.portal.publish.secret%", display = ParameterDisplay.HIDDEN) 32 | } 33 | 34 | steps { 35 | gradle { 36 | useGradleWrapper = true 37 | gradleParams = "-Dgradle.publish.skip.namespace.check=true" 38 | tasks = ":gradle-guides-plugin:publishPlugins" 39 | } 40 | } 41 | }) 42 | -------------------------------------------------------------------------------- /gradle-guides-plugin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("groovy") 3 | id("java-gradle-plugin") 4 | id("com.gradle.plugin-publish").version("0.20.0") 5 | id("maven-publish") 6 | } 7 | 8 | sourceSets { 9 | val functionalTest by creating { 10 | groovy.srcDirs("src/functionalTest/groovy") 11 | resources.srcDirs("src/functionalTest/resources") 12 | compileClasspath += sourceSets.main.get().output + configurations.testRuntimeClasspath.get() 13 | runtimeClasspath += output + compileClasspath 14 | } 15 | } 16 | 17 | tasks.register("functionalTest") { 18 | description = "Runs the functional tests." 19 | group = "verification" 20 | testClassesDirs = sourceSets["functionalTest"].output.classesDirs 21 | classpath = sourceSets["functionalTest"].runtimeClasspath 22 | shouldRunAfter(tasks.test) 23 | maxParallelForks = 2 24 | systemProperty("gradle.version", gradle.gradleVersion) 25 | } 26 | 27 | tasks.check { 28 | dependsOn(tasks.getByName("functionalTest")) 29 | } 30 | 31 | group = "org.gradle.guides" 32 | version = "0.24.0" 33 | 34 | java { 35 | toolchain { 36 | languageVersion.set(JavaLanguageVersion.of(17)) 37 | } 38 | } 39 | 40 | repositories { 41 | maven { 42 | name = "Gradle public repository" 43 | url = uri("https://repo.gradle.org/gradle/public") 44 | content { 45 | includeModule("org.gradle", "gradle-tooling-api") 46 | includeModuleByRegex("org.gradle", "sample-(check|discovery)") 47 | } 48 | } 49 | mavenCentral() 50 | gradlePluginPortal() 51 | } 52 | 53 | dependencies { 54 | api("org.asciidoctor:asciidoctor-gradle-jvm:4.0.1") 55 | implementation("org.apache.ant:ant:1.10.15") 56 | implementation("org.jsoup:jsoup:1.20.1") 57 | 58 | // For exemplar asciidoctor tests 59 | compileOnly("commons-io:commons-io:2.19.0") 60 | compileOnly("org.apache.commons:commons-lang3:3.17.0") 61 | compileOnly("org.asciidoctor:asciidoctorj:3.0.0") 62 | compileOnly("org.gradle.exemplar:samples-check:1.0.3") 63 | compileOnly("org.gradle:gradle-tooling-api:6.0.1") 64 | 65 | implementation("junit:junit:4.13.2") 66 | implementation("net.rubygrapefruit:ansi-control-sequence-util:0.4") // For rich and verbose console support 67 | implementation("org.asciidoctor:asciidoctor-gradle-base:4.0.1") 68 | implementation("org.asciidoctor:asciidoctorj-api:3.0.0") 69 | implementation("org.gradle.exemplar:samples-discovery:1.0.3") 70 | 71 | testImplementation("org.codehaus.groovy:groovy:3.0.25") 72 | testImplementation("org.spockframework:spock-core:2.1-groovy-3.0") { 73 | exclude(module = "groovy-all") 74 | } 75 | 76 | testImplementation("org.junit.jupiter:junit-jupiter-api:5.13.0") 77 | } 78 | 79 | pluginBundle { 80 | website = "https://guides.gradle.org" 81 | vcsUrl = "https://github.com/gradle/guides" 82 | tags = listOf("gradle", "guides", "documentation") 83 | } 84 | 85 | gradlePlugin { 86 | testSourceSets += sourceSets["functionalTest"] 87 | plugins { 88 | create("guidePlugin") { 89 | id = "org.gradle.guide" 90 | implementationClass = "org.gradle.docs.guides.internal.LegacyGuideDocumentationPlugin" 91 | displayName = "Gradle Guides Plugin" 92 | description = "Plugin for authoring Gradle guides" 93 | } 94 | create("samplePlugin") { 95 | id = "org.gradle.samples" 96 | implementationClass = "org.gradle.docs.samples.internal.LegacySamplesDocumentationPlugin" 97 | displayName = "Gradle Sample Plugin" 98 | description = "Plugin required for authoring new Gradle Samples" 99 | } 100 | create("documentationPlugin") { 101 | id = "org.gradle.documentation" 102 | implementationClass = "org.gradle.docs.internal.BuildDocumentationPlugin" 103 | displayName = "Gradle Documentation Plugin" 104 | description = "Plugin for authoring Gradle documentation" 105 | } 106 | } 107 | } 108 | 109 | tasks.named("publishPlugins") { 110 | onlyIf { !"$version".endsWith("-SNAPSHOT") } 111 | } 112 | 113 | tasks.withType().configureEach { 114 | useJUnitPlatform() 115 | } 116 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/AbstractBaseDocumentationFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs 2 | 3 | import spock.lang.Unroll 4 | 5 | abstract class AbstractBaseDocumentationFunctionalTest extends AbstractFunctionalTest implements DocumentationTrait { 6 | @Unroll 7 | def "fails if disallowed characters in documentation element name (#name)"(String name) { 8 | buildFile << applyDocumentationPlugin() << createDocumentationElement(name) 9 | 10 | expect: 11 | def result = buildAndFail('help') 12 | result.output.contains("'${name}' has disallowed characters") 13 | 14 | where: 15 | name << ['foo_bar', 'foo-bar'] 16 | } 17 | 18 | protected abstract String createDocumentationElement(String name) 19 | } 20 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/AbstractFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradle.docs 18 | 19 | import org.gradle.testkit.runner.BuildResult 20 | import org.gradle.testkit.runner.GradleRunner 21 | import spock.lang.Specification 22 | import spock.lang.TempDir 23 | 24 | import static org.gradle.testkit.runner.TaskOutcome.* 25 | 26 | abstract class AbstractFunctionalTest extends Specification { 27 | @TempDir 28 | File temporaryFolder 29 | TestFile projectDir 30 | TestFile buildFile 31 | TestFile settingsFile 32 | BuildResult result 33 | private String gradleVersion 34 | 35 | def setup() { 36 | projectDir = new TestFile(temporaryFolder) 37 | buildFile = new TestFile(projectDir, 'build.gradle') 38 | settingsFile = new TestFile(projectDir, 'settings.gradle') 39 | file("gradle.properties").text = "org.gradle.jvmargs=-XX:MaxMetaspaceSize=500m -Xmx500m" 40 | } 41 | 42 | protected ExecutionResult build(String... arguments) { 43 | result = createAndConfigureGradleRunner(arguments).build() 44 | return new ExecutionResult(result) 45 | } 46 | 47 | protected ExecutionResult buildAndFail(String... arguments) { 48 | result = createAndConfigureGradleRunner(arguments).buildAndFail() 49 | return new ExecutionResult(result) 50 | } 51 | 52 | private GradleRunner createAndConfigureGradleRunner(String... arguments) { 53 | def allArgs = (arguments as List) + ["-S"] 54 | def runner = GradleRunner.create().withProjectDir(projectDir).withArguments(allArgs).withPluginClasspath().forwardOutput() 55 | if (gradleVersion != null) { 56 | runner.withGradleVersion(gradleVersion) 57 | gradleVersion = null 58 | } 59 | return runner 60 | } 61 | 62 | String getGradleVersion() { 63 | return gradleVersion ?: System.getProperty("gradle.version") 64 | } 65 | 66 | void usingGradleVersion(String gradleVersion) { 67 | this.gradleVersion = gradleVersion 68 | } 69 | 70 | static File createDir(File dir, String subDirName) { 71 | File newDir = new File(dir, subDirName) 72 | 73 | if (!newDir.mkdirs()) { 74 | throw new IOException("Unable to create directory " + subDirName) 75 | } 76 | 77 | newDir 78 | } 79 | 80 | TestFile file(Object... paths) { 81 | return projectDir.file(paths) 82 | } 83 | 84 | static class ExecutionResult implements BuildResult { 85 | private static final SKIPPED_TASK_OUTCOMES = [FROM_CACHE, UP_TO_DATE, SKIPPED, NO_SOURCE] 86 | 87 | @Delegate 88 | private final BuildResult delegate 89 | 90 | ExecutionResult(BuildResult delegate) { 91 | this.delegate = delegate 92 | } 93 | 94 | ExecutionResult assertTasksExecutedAndNotSkipped(Object... taskPaths) { 95 | assertTasksExecuted(taskPaths) 96 | assertTasksNotSkipped(taskPaths) 97 | return this 98 | } 99 | 100 | ExecutionResult assertTasksExecuted(Object... taskPaths) { 101 | def expectedTasks = taskPaths.flatten() as Set 102 | def actualTasks = tasks.collect { it.path } as Set 103 | 104 | assert expectedTasks == actualTasks 105 | return this 106 | } 107 | 108 | ExecutionResult assertTasksNotSkipped(Object... taskPaths) { 109 | def expectedTasks = taskPaths.flatten() as Set 110 | def tasks = tasks.findAll { !(it.outcome in SKIPPED_TASK_OUTCOMES) }.collect { it.path } as Set 111 | 112 | assert expectedTasks == tasks 113 | return this 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/AbstractRenderableDocumentationFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs 2 | 3 | import spock.lang.Unroll 4 | 5 | abstract class AbstractRenderableDocumentationFunctionalTest extends AbstractFunctionalTest implements DocumentationTrait { 6 | def "defaults documentation element display name to title case"() { 7 | buildFile << applyDocumentationPlugin() << createDocumentationElement('foo') << createDocumentationElement('fooBar') 8 | buildFile << """ 9 | tasks.register('verify') { 10 | doLast { 11 | assert ${documentationDsl('foo')}.displayName.get() == 'Foo' 12 | assert ${documentationDsl('fooBar')}.displayName.get() == 'Foo Bar' 13 | } 14 | } 15 | """ 16 | 17 | expect: 18 | build('verify') 19 | } 20 | 21 | def "detaults documentation element description to empty string"() { 22 | buildFile << applyDocumentationPlugin() << createDocumentationElement('foo') << createDocumentationElement('fooBar') 23 | buildFile << """ 24 | tasks.register('verify') { 25 | doLast { 26 | assert ${documentationDsl('foo')}.description.get() == '' 27 | assert ${documentationDsl('fooBar')}.description.get() == '' 28 | } 29 | } 30 | """ 31 | 32 | expect: 33 | build('verify') 34 | } 35 | 36 | def "can detect dead links"() { 37 | given: 38 | makeSingleProject() 39 | writeDocumentationUnderTest() 40 | contentFileUnderTest << ''' 41 | |https://bugs.java.com/bugdatabase/view_bug.do?bug_id=4155278 42 | |https://youtrack.jetbrains.com/issue/TW-65268 43 | |https://not.existant/url 44 | |'''.stripMargin() 45 | 46 | expect: 47 | def result = buildAndFail(checkTaskNameUnderTest, "-i") 48 | result.output.contains(''' > The following links are broken: 49 | https://not.existant/url''') 50 | } 51 | 52 | protected abstract String getCheckTaskNameUnderTest() 53 | 54 | protected abstract String createDocumentationElement(String name) 55 | 56 | protected abstract String documentationDsl(String name) 57 | 58 | protected abstract void makeSingleProject() 59 | 60 | protected abstract void writeDocumentationUnderTest() 61 | 62 | protected abstract TestFile getContentFileUnderTest() 63 | } 64 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/AbstractWellBehavePluginFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs 2 | 3 | abstract class AbstractWellBehavePluginFunctionalTest extends AbstractFunctionalTest { 4 | def "applying plugin should not throw exception"() { 5 | buildFile << """ 6 | plugins { 7 | id '${pluginIdUnderTest}' 8 | } 9 | """ 10 | 11 | expect: 12 | build('help') 13 | } 14 | 15 | protected abstract String getPluginIdUnderTest() 16 | } 17 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/DocumentationTrait.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs 2 | 3 | trait DocumentationTrait { 4 | static String applyDocumentationPlugin() { 5 | return """ 6 | plugins { 7 | id 'org.gradle.documentation' 8 | } 9 | 10 | repositories { 11 | mavenCentral() 12 | maven { url = uri("https://repo.gradle.org/gradle/public") } 13 | } 14 | """ 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/DocumentationWellBehavePluginFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs 2 | 3 | class DocumentationWellBehavePluginFunctionalTest extends AbstractWellBehavePluginFunctionalTest { 4 | @Override 5 | protected String getPluginIdUnderTest() { 6 | return 'org.gradle.documentation' 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/LegacyPluginTrait.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs 2 | 3 | trait LegacyPluginTrait { 4 | static String applyLegacyPlugin() { 5 | return """ 6 | plugins { 7 | id 'org.gradle.guide' 8 | } 9 | 10 | repositories { 11 | mavenCentral() 12 | maven { url = uri("https://repo.gradle.org/gradle/public") } 13 | } 14 | """ 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/MixedDocumentationFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs 2 | 3 | import org.gradle.docs.guides.GuidesTrait 4 | import org.gradle.docs.samples.SamplesTrait 5 | import spock.lang.Ignore 6 | 7 | class MixedDocumentationFunctionalTest extends AbstractFunctionalTest implements SamplesTrait, GuidesTrait, DocumentationTrait { 8 | def "can assemble multiple documentation element type"() { 9 | buildFile << applyDocumentationPlugin() << createGuide('demoGuide') << createSampleWithBothDsl('demoSample') 10 | writeReadmeTo(file('src/docs/samples/demo-sample')) 11 | writeKotlinDslSampleTo(file('src/docs/samples/demo-sample/kotlin')) 12 | writeGroovyDslSampleTo(file('src/docs/samples/demo-sample/groovy')) 13 | writeContentTo(file('src/docs/guides/demo-guide')) 14 | 15 | when: 16 | def result = build('assemble') 17 | 18 | then: 19 | result.assertTasksExecutedAndNotSkipped(':generateDemoSamplePage', ':generateWrapperForSamples', ':zipSampleDemoSampleGroovy', ':zipSampleDemoSampleKotlin', ':assembleDemoSampleSample', ':generateDemoGuidePage', ':assembleGuides', ':guidesMultiPage', ':generateSampleIndex', ':installSampleDemoSampleGroovy', ':generateSanityCheckTests', ':installSampleDemoSampleGroovyForTest', ':installSampleDemoSampleKotlin', ':installSampleDemoSampleKotlinForTest', ':assembleSamples', ':samplesMultiPage', ':assemble') 20 | } 21 | 22 | @Ignore 23 | def "can create different documentation element type with the same name"() { 24 | buildFile << applyDocumentationPlugin() << createGuide('demo') << createSampleWithBothDsl('demo') 25 | writeReadmeTo(file('src/docs/samples/demo')) 26 | writeKotlinDslSampleTo(file('src/docs/samples/demo/kotlin')) 27 | writeGroovyDslSampleTo(file('src/docs/samples/demo/groovy')) 28 | writeContentTo(file('src/docs/guides/demo')) 29 | 30 | when: 31 | def result = build('assemble') 32 | 33 | then: 34 | result.assertTasksExecutedAndNotSkipped(':assemble') 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/TestFile.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs; 2 | 3 | import org.codehaus.groovy.runtime.ResourceGroovyMethods; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.nio.charset.StandardCharsets; 8 | import java.util.Arrays; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertFalse; 11 | import static org.junit.jupiter.api.Assertions.assertTrue; 12 | 13 | // TODO: Make this not dependent on Groovy methods 14 | public class TestFile extends File { 15 | public TestFile(File file, Object... path) { 16 | super(join(file, path).getAbsolutePath()); 17 | } 18 | 19 | public TestFile file(Object... path) { 20 | try { 21 | return new TestFile(this, path); 22 | } catch (RuntimeException e) { 23 | throw new RuntimeException(String.format("Could not locate file '%s' relative to '%s'.", Arrays.toString(path), this), e); 24 | } 25 | } 26 | 27 | public TestFile leftShift(Object content) { 28 | getParentFile().mkdirs(); 29 | try { 30 | ResourceGroovyMethods.leftShift(this, content); 31 | return this; 32 | } catch (IOException e) { 33 | throw new RuntimeException(String.format("Could not append to test file '%s'", this), e); 34 | } 35 | } 36 | 37 | public TestFile setText(String content) { 38 | getParentFile().mkdirs(); 39 | try { 40 | ResourceGroovyMethods.setText(this, content); 41 | return this; 42 | } catch (IOException e) { 43 | throw new RuntimeException(String.format("Could not append to test file '%s'", this), e); 44 | } 45 | } 46 | 47 | public String getText() { 48 | assertIsFile(); 49 | try { 50 | return ResourceGroovyMethods.getText(this, StandardCharsets.UTF_8.name()); 51 | } catch (IOException e) { 52 | throw new RuntimeException(String.format("Could not read from test file '%s'", this), e); 53 | } 54 | } 55 | 56 | public TestFile assertExists() { 57 | assertTrue(exists(), () -> String.format("%s does not exist", this)); 58 | return this; 59 | } 60 | 61 | public TestFile assertIsFile() { 62 | assertTrue(isFile(), () -> String.format("%s is not a file", this)); 63 | return this; 64 | } 65 | 66 | public TestFile assertIsDir() { 67 | assertTrue(isDirectory(), () -> String.format("%s is not a directory.", this)); 68 | return this; 69 | } 70 | 71 | public TestFile assertDoesNotExist() { 72 | assertFalse(exists(), () -> String.format("%s should not exist", this)); 73 | return this; 74 | } 75 | 76 | private static File join(File file, Object[] path) { 77 | File current = file.getAbsoluteFile(); 78 | for (Object p : path) { 79 | current = new File(current, p.toString()); 80 | } 81 | try { 82 | return current.getCanonicalFile(); 83 | } catch (IOException e) { 84 | throw new RuntimeException(String.format("Could not canonicalise '%s'.", current), e); 85 | } 86 | } 87 | 88 | public ZipFileFixture asZip() { 89 | assertIsFile(); 90 | return new ZipFileFixture(this); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/ZipFileFixture.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs; 2 | 3 | import org.hamcrest.Matcher; 4 | 5 | import java.io.ByteArrayOutputStream; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.nio.charset.StandardCharsets; 9 | import java.util.*; 10 | import java.util.zip.ZipEntry; 11 | import java.util.zip.ZipFile; 12 | 13 | import static org.junit.jupiter.api.Assertions.assertEquals; 14 | 15 | public class ZipFileFixture { 16 | private final Map entries; 17 | 18 | ZipFileFixture(TestFile file) { 19 | this.entries = new HashMap<>(); 20 | Set seen = new HashSet<>(); 21 | 22 | try (ZipFile zipFile = new ZipFile(file)) { 23 | Enumeration zipEntries = zipFile.entries(); 24 | while (zipEntries.hasMoreElements()) { 25 | ZipEntry entry = zipEntries.nextElement(); 26 | if (!seen.add(entry.getName())) { 27 | throw new RuntimeException(String.format("Duplicate entry '%s' in file %s.", entry.getName(), file)); 28 | } 29 | if (!entry.isDirectory()) { 30 | String content = getContentForEntry(zipFile, entry); 31 | this.entries.put(entry.getName(), content); 32 | } 33 | } 34 | } catch (IOException e) { 35 | throw new RuntimeException(file + " is not a zip file?", e); 36 | } 37 | } 38 | 39 | private String getContentForEntry(ZipFile zipFile, ZipEntry entry) throws IOException { 40 | // Only keep around content that's <1KB 41 | if (entry.getSize() < 1024*1024) { 42 | InputStream inputStream = zipFile.getInputStream(entry); 43 | ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 44 | int nRead; 45 | byte[] data = new byte[1024]; 46 | while ((nRead = inputStream.read(data, 0, data.length)) != -1) { 47 | buffer.write(data, 0, nRead); 48 | } 49 | buffer.flush(); 50 | return buffer.toString(StandardCharsets.UTF_8); 51 | } 52 | return "File too long"; 53 | } 54 | 55 | public void assertContainsDescendants(String... expectedContent) { 56 | assert entries.keySet().containsAll(Arrays.asList(expectedContent)); 57 | } 58 | 59 | public void assertDescendantHasContent(String descendant, Matcher matcher) { 60 | String actualContent = entries.get(descendant); 61 | assert actualContent != null; 62 | assert matcher.matches(actualContent); 63 | } 64 | 65 | public void assertHasDescendants(String... descendants) { 66 | assertEquals(descendants.length, entries.size()); 67 | Set expectedEntries = new HashSet<>(Arrays.asList(descendants)); 68 | Set actualEntries = new HashSet<>(entries.keySet()); 69 | assertEquals(expectedEntries, actualEntries); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/guides/AbstractGuideFunctionalSpec.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.guides 2 | 3 | import org.gradle.docs.AbstractFunctionalTest 4 | import org.gradle.docs.DocumentationTrait 5 | 6 | class AbstractGuideFunctionalSpec extends AbstractFunctionalTest implements GuidesTrait, DocumentationTrait { 7 | protected void makeSingleProject() { 8 | buildFile << applyDocumentationPlugin() << createGuide('demo') 9 | } 10 | 11 | protected void writeGuideUnderTest(String directory="src/docs/guides/demo") { 12 | file("${directory}/contents/index.adoc") << """ 13 | = Demo 14 | 15 | Some guide 16 | """ 17 | } 18 | 19 | protected String getGuideUnderTestDsl() { 20 | return guideDsl('demo') 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/guides/CustomLayoutGuidesDocumentationFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.guides 2 | 3 | class CustomLayoutGuidesDocumentationFunctionalTest extends AbstractGuideFunctionalSpec { 4 | def "can relocate guide directory"() { 5 | makeSingleProject() 6 | writeGuideUnderTest('.') 7 | buildFile << """ 8 | ${guideUnderTestDsl} { 9 | guideDirectory = projectDir 10 | } 11 | """ 12 | 13 | when: 14 | def result = build("assemble") 15 | 16 | then: 17 | result.assertTasksExecutedAndNotSkipped(':generateDemoPage', ':assembleGuides', ':guidesMultiPage', ':generateSampleIndex', ':assembleSamples', ':samplesMultiPage', ':assemble') 18 | } 19 | 20 | def "relocates Asciidoctor attributes for samples code and output directory"() { 21 | given: 22 | makeSingleProject() 23 | writeGuideUnderTest('custom-location') 24 | file('custom-location/contents/index.adoc') << """ 25 | |* Samples directory: {samples-dir} 26 | |* Samples code directory: {samplescodedir} 27 | |* Samples output directory: {samplesoutputdir} 28 | |""".stripMargin() 29 | buildFile << """ 30 | ${guideUnderTestDsl} { 31 | guideDirectory = file('custom-location') 32 | } 33 | """ 34 | 35 | when: 36 | build('assemble') 37 | 38 | then: 39 | def indexFile = file('build/working/guides/render-guides/demo/index.html') 40 | indexFile.exists() 41 | indexFile.text.contains("Samples directory: ${file('custom-location/samples')}") 42 | indexFile.text.contains("Samples code directory: ${file('custom-location/samples/code')}") 43 | indexFile.text.contains("Samples output directory: ${file('custom-location/samples/output')}") 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/guides/GuideWellBehavePluginFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.guides 2 | 3 | import org.gradle.docs.AbstractWellBehavePluginFunctionalTest 4 | 5 | class GuideWellBehavePluginFunctionalTest extends AbstractWellBehavePluginFunctionalTest { 6 | @Override 7 | protected String getPluginIdUnderTest() { 8 | return 'org.gradle.guide' 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/guides/GuidesBaseDocumentationFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradle.docs.guides 18 | 19 | import org.gradle.docs.AbstractBaseDocumentationFunctionalTest 20 | 21 | class GuidesBaseDocumentationFunctionalTest extends AbstractBaseDocumentationFunctionalTest implements GuidesTrait { 22 | @Override 23 | protected String createDocumentationElement(String name) { 24 | return createGuide(name) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/guides/GuidesRenderableDocumentationFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.guides 2 | 3 | import org.gradle.docs.AbstractRenderableDocumentationFunctionalTest 4 | import org.gradle.docs.TestFile 5 | 6 | class GuidesRenderableDocumentationFunctionalTest extends AbstractRenderableDocumentationFunctionalTest implements GuidesTrait { 7 | @Override 8 | protected String createDocumentationElement(String name) { 9 | return createGuide(name) 10 | } 11 | 12 | @Override 13 | protected String documentationDsl(String name) { 14 | return guideDsl(name) 15 | } 16 | 17 | @Override 18 | protected void makeSingleProject() { 19 | buildFile << applyDocumentationPlugin() << createGuide('demo') 20 | } 21 | 22 | @Override 23 | protected void writeDocumentationUnderTest() { 24 | writeContentTo(file('src/docs/guides/demo')) 25 | } 26 | 27 | @Override 28 | protected TestFile getContentFileUnderTest() { 29 | return file('src/docs/guides/demo/contents/index.adoc') 30 | } 31 | 32 | @Override 33 | protected String getCheckTaskNameUnderTest() { 34 | return 'checkDemoGuideLinks' 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/guides/GuidesTrait.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.guides 2 | 3 | import org.gradle.docs.TestFile 4 | 5 | trait GuidesTrait { 6 | static String createGuide(String name) { 7 | return """ 8 | documentation.guides.publishedGuides.create('${name}') 9 | """ 10 | } 11 | 12 | static String guideDsl(String name) { 13 | return "documentation.guides.publishedGuides.${name}" 14 | } 15 | 16 | static void writeContentTo(TestFile directory) { 17 | directory.file('contents/index.adoc') << """ 18 | |= Demo 19 | | 20 | |Some guide 21 | |""".stripMargin() 22 | } 23 | } -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/guides/IncrementalGuidesDocumentationFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.guides 2 | 3 | import org.gradle.testkit.runner.TaskOutcome 4 | 5 | class IncrementalGuidesDocumentationFunctionalTest extends AbstractGuideFunctionalSpec { 6 | def "asciidoctor is up-to-date on consecutive execution without change"() { 7 | given: 8 | makeSingleProject() 9 | writeGuideUnderTest() 10 | 11 | when: 12 | def result = build('guidesMultiPage') 13 | then: 14 | result.task(':guidesMultiPage').outcome == TaskOutcome.SUCCESS 15 | 16 | when: 17 | result = build('guidesMultiPage', '--info') 18 | then: 19 | result.task(':guidesMultiPage').outcome == TaskOutcome.UP_TO_DATE 20 | 21 | when: 22 | file('src/docs/guides/demo/contents/index.adoc') << """ 23 | |More content. 24 | |""".stripMargin() 25 | result = build('guidesMultiPage') 26 | then: 27 | result.task(':guidesMultiPage').outcome == TaskOutcome.SUCCESS 28 | } 29 | 30 | def "asciidoctor is out of date if samples change"() { 31 | given: 32 | makeSingleProject() 33 | writeGuideUnderTest() 34 | def samplesCodeDir = new File(temporaryFolder, 'samples/code') 35 | def samplesOutputDir = new File(temporaryFolder, 'samples/output') 36 | samplesCodeDir.mkdirs() 37 | samplesOutputDir.mkdirs() 38 | 39 | when: 40 | def result = build('guidesMultiPage') 41 | 42 | then: 43 | result.task(':guidesMultiPage').outcome == TaskOutcome.SUCCESS 44 | 45 | when: 46 | new File(samplesCodeDir, "build.gradle") << 'apply plugin: java' 47 | result = build('guidesMultiPage') 48 | 49 | then: 50 | result.task(':guidesMultiPage').outcome == TaskOutcome.SUCCESS 51 | 52 | when: 53 | new File(samplesOutputDir, "my-task-output.log") << 'Build SUCCESSFUL' 54 | result = build('guidesMultiPage') 55 | 56 | then: 57 | result.task(':guidesMultiPage').outcome == TaskOutcome.SUCCESS 58 | 59 | when: 60 | result = build('guidesMultiPage', '--info') 61 | 62 | then: 63 | result.task(':guidesMultiPage').outcome == TaskOutcome.UP_TO_DATE 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/guides/MultipleGuidesDocumentationFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.guides 2 | 3 | class MultipleGuidesDocumentationFunctionalTest extends AbstractGuideFunctionalSpec { 4 | def "configures attributes per guides"() { 5 | buildFile << applyDocumentationPlugin() << createGuide('foo') << createGuide('bar') 6 | writeGuideUnderTest('src/docs/guides/foo') 7 | writeGuideUnderTest('src/docs/guides/bar') 8 | file('src/docs/guides/foo/contents/index.adoc') << ''' 9 | |* Samples directory: {samples-dir} 10 | |* Samples code directory: {samplescodedir} 11 | |* Samples output directory: {samplesoutputdir} 12 | |'''.stripMargin() 13 | file('src/docs/guides/bar/contents/index.adoc') << ''' 14 | |* Samples directory: {samples-dir} 15 | |* Samples code directory: {samplescodedir} 16 | |* Samples output directory: {samplesoutputdir} 17 | |'''.stripMargin() 18 | 19 | when: 20 | build('assemble') 21 | 22 | then: 23 | def indexFooFile = file('build/working/guides/render-guides/foo/index.html') 24 | indexFooFile.exists() 25 | indexFooFile.text.contains("Samples directory: ${file('src/docs/guides/foo/samples')}") 26 | indexFooFile.text.contains("Samples code directory: ${file('src/docs/guides/foo/samples/code')}") 27 | indexFooFile.text.contains("Samples output directory: ${file('src/docs/guides/foo/samples/output')}") 28 | 29 | and: 30 | def indexBarFile = file('build/working/guides/render-guides/bar/index.html') 31 | indexBarFile.exists() 32 | indexBarFile.text.contains("Samples directory: ${file('src/docs/guides/bar/samples')}") 33 | indexBarFile.text.contains("Samples code directory: ${file('src/docs/guides/bar/samples/code')}") 34 | indexBarFile.text.contains("Samples output directory: ${file('src/docs/guides/bar/samples/output')}") 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/guides/PublishingGuidesDocumentationFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.guides 2 | 3 | import org.gradle.docs.LegacyPluginTrait 4 | 5 | class PublishingGuidesDocumentationFunctionalTest extends AbstractGuideFunctionalSpec implements LegacyPluginTrait { 6 | def "can resolve rendered guides from legacy plugins"() { 7 | settingsFile << """ 8 | include 'legacy' 9 | """ 10 | buildFile << configurePublishProject() << """ 11 | dependencies { 12 | guides project('legacy') 13 | } 14 | """ 15 | file('legacy/build.gradle') << applyLegacyPlugin() 16 | writeGuideUnderTest("legacy") 17 | 18 | when: 19 | assert !file("build/published-guides/legacy/index.html").exists() 20 | def result = build('publish') 21 | 22 | then: 23 | file("build/published-guides/legacy/index.html").exists() 24 | } 25 | 26 | def "can resolve multiple rendered guides from single project using new documentation plugins"() { 27 | settingsFile << """ 28 | include 'guides' 29 | """ 30 | buildFile << configurePublishProject() << """ 31 | dependencies { 32 | guides project('guides') 33 | } 34 | """ 35 | file('guides/build.gradle') << applyDocumentationPlugin() << createGuide('foo') << createGuide('bar') 36 | writeGuideUnderTest('guides/src/docs/guides/foo') 37 | writeGuideUnderTest('guides/src/docs/guides/bar') 38 | 39 | when: 40 | assert !file("build/published-guides/foo/index.html").exists() 41 | assert !file("build/published-guides/bar/index.html").exists() 42 | def result = build('publish') 43 | 44 | then: 45 | file("build/published-guides/foo/index.html").exists() 46 | file("build/published-guides/bar/index.html").exists() 47 | } 48 | 49 | def "can resolve multiple rendered guides from multiple projects using new documentation plugins"() { 50 | settingsFile << """ 51 | include 'foo', 'bar' 52 | """ 53 | buildFile << configurePublishProject() << """ 54 | dependencies { 55 | guides project('foo') 56 | guides project('bar') 57 | } 58 | """ 59 | file('foo/build.gradle') << applyDocumentationPlugin() << createGuide('foo') 60 | writeGuideUnderTest('foo/src/docs/guides/foo') 61 | file('bar/build.gradle') << applyDocumentationPlugin() << createGuide('bar') 62 | writeGuideUnderTest('bar/src/docs/guides/bar') 63 | 64 | when: 65 | assert !file("build/published-guides/foo/index.html").exists() 66 | assert !file("build/published-guides/bar/index.html").exists() 67 | def result = build('publish') 68 | 69 | then: 70 | file("build/published-guides/foo/index.html").exists() 71 | file("build/published-guides/bar/index.html").exists() 72 | } 73 | 74 | def "can resolve multiple rendered guides from multiple projects using both new documentation and legacy plugins"() { 75 | settingsFile << """ 76 | include 'legacy', 'foo' 77 | """ 78 | buildFile << configurePublishProject() << """ 79 | dependencies { 80 | guides project('legacy') 81 | guides project('foo') 82 | } 83 | """ 84 | file('legacy/build.gradle') << applyLegacyPlugin() 85 | writeGuideUnderTest('legacy') 86 | file('foo/build.gradle') << applyDocumentationPlugin() << createGuide('foo') 87 | writeGuideUnderTest('foo/src/docs/guides/foo') 88 | 89 | when: 90 | assert !file("build/published-guides/legacy/index.html").exists() 91 | assert !file("build/published-guides/foo/index.html").exists() 92 | def result = build('publish') 93 | 94 | then: 95 | file("build/published-guides/legacy/index.html").exists() 96 | file("build/published-guides/foo/index.html").exists() 97 | } 98 | 99 | private static String configurePublishProject() { 100 | return ''' 101 | configurations { 102 | guides { 103 | canBeResolved = true 104 | canBeConsumed = false 105 | attributes.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, 'docs')) 106 | attributes.attribute(Attribute.of('type', String), 'guide-docs') 107 | } 108 | } 109 | tasks.register('publish', Sync) { 110 | from(configurations.guides) 111 | into("$buildDir/published-guides") 112 | } 113 | ''' 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/samples/AbstractBasicSampleFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples 2 | 3 | 4 | import org.gradle.docs.TestFile 5 | import org.gradle.testkit.runner.BuildResult 6 | import spock.lang.Ignore 7 | import spock.lang.Unroll 8 | 9 | import static org.gradle.testkit.runner.TaskOutcome.SUCCESS 10 | 11 | abstract class AbstractBasicSampleFunctionalTest extends AbstractSampleFunctionalSpec { 12 | def "can build samples"() { 13 | makeSingleProject() 14 | writeSampleUnderTest() 15 | 16 | when: 17 | build('assemble') 18 | 19 | then: 20 | result.task(":generateSampleIndex").outcome == SUCCESS 21 | result.task(":generateWrapperForSamples").outcome == SUCCESS 22 | assertSampleTasksExecutedAndNotSkipped(result) 23 | and: 24 | def indexFile = file("build/working/samples/docs/index.adoc") 25 | indexFile.text.contains('- <>') 26 | assertReadmeHasContent() 27 | and: 28 | assertDslZipsHaveContent() 29 | } 30 | 31 | def "can assemble sample using a lifecycle task"() { 32 | makeSingleProject() 33 | writeSampleUnderTest() 34 | 35 | when: 36 | build('assembleDemoSample') 37 | 38 | then: 39 | assertSampleTasksExecutedAndNotSkipped(result) 40 | assertDslZipFilesExists() 41 | } 42 | 43 | @Ignore('Wait for next version with `WorkerLeaseService.runAsIsolatedTask` to be released') 44 | def "defaults to Gradle version based on the running distribution"() { 45 | makeSingleProject() 46 | writeSampleUnderTest() 47 | 48 | when: 49 | usingGradleVersion("6.0") 50 | build("assembleDemoSample") 51 | 52 | then: 53 | dslZipFiles.each { 54 | assertGradleWrapperVersion(it, '6.0') 55 | } 56 | 57 | when: 58 | usingGradleVersion('6.0.1') 59 | build("assembleDemoSample") 60 | 61 | then: 62 | dslZipFiles.each { 63 | assertGradleWrapperVersion(it, '6.0.1') 64 | } 65 | } 66 | 67 | def "can relocate sample"() { 68 | makeSingleProject() 69 | writeSampleUnderTest(file('src')) 70 | buildFile << """ 71 | ${sampleUnderTestDsl} { 72 | sampleDirectory = file('src') 73 | } 74 | """ 75 | 76 | when: 77 | build("assembleDemoSample") 78 | 79 | then: 80 | assertSampleTasksExecutedAndNotSkipped(result) 81 | assertDslZipsHaveContent() 82 | } 83 | 84 | def "defaults sample location to `src/docs/samples/`"() { 85 | makeSingleProject() 86 | writeSampleUnderTest() 87 | buildFile << """ 88 | tasks.register('verify') { 89 | doLast { 90 | assert ${sampleUnderTestDsl}.sampleDirectory.get().asFile.absolutePath == '${file('src/docs/samples/demo').canonicalPath}' 91 | } 92 | } 93 | """ 94 | 95 | when: 96 | build("verify") 97 | 98 | then: 99 | noExceptionThrown() 100 | } 101 | 102 | @Unroll 103 | def "excludes '#directory' when building the domain language archive"() { 104 | makeSingleProject() 105 | writeSampleUnderTest() 106 | sampleDirectoryUnderTest.file("common/${directory}/foo.txt") << "Exclude" 107 | buildFile << """ 108 | def sample = ${sampleUnderTestDsl} 109 | sample.common { 110 | from(sample.sampleDirectory.file("common")) 111 | } 112 | """ 113 | when: 114 | build('assembleDemoSample') 115 | 116 | then: 117 | assertSampleTasksExecutedAndNotSkipped(result) 118 | assertDslZipsHaveContent() 119 | 120 | where: 121 | directory << ['.gradle', 'build'] 122 | } 123 | 124 | def "can include sample"() { 125 | given: 126 | makeSingleProject() 127 | writeSampleUnderTest() 128 | sampleDirectoryUnderTest.file('README.adoc') << configureAsciidoctorIncludeSample() 129 | 130 | expect: 131 | build('assemble') 132 | } 133 | 134 | protected abstract List getDslZipFiles() 135 | 136 | protected abstract void assertSampleTasksExecutedAndNotSkipped(BuildResult result) 137 | 138 | protected abstract void assertReadmeHasContent() 139 | 140 | protected abstract void assertDslZipsHaveContent() 141 | 142 | protected abstract void assertDslZipFilesExists() 143 | 144 | protected abstract String configureAsciidoctorIncludeSample() 145 | } 146 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/samples/AbstractBothDslSampleFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples 2 | 3 | import org.gradle.docs.TestFile 4 | import org.gradle.testkit.runner.BuildResult 5 | 6 | abstract class AbstractBothDslSampleFunctionalTest extends AbstractBasicSampleFunctionalTest { 7 | @Override 8 | protected List getDslZipFiles() { 9 | return [groovyDslZipFile, kotlinDslZipFile] 10 | } 11 | 12 | @Override 13 | protected void assertSampleTasksExecutedAndNotSkipped(BuildResult result) { 14 | assertBothDslSampleTasksExecutedAndNotSkipped(result) 15 | } 16 | 17 | @Override 18 | protected void assertReadmeHasContent() { 19 | def groovyReadmeFile = file("build/working/samples/install/demo/groovy/README") 20 | def kotlinReadmeFile = file("build/working/samples/install/demo/kotlin/README") 21 | assert groovyReadmeFile.text == """:samples-dir: ${file('/build/working/samples/install/demo')} 22 | |:gradle-version: ${gradleVersion} 23 | | 24 | |= Demo Sample 25 | | 26 | |[.download] 27 | |- link:zips/sample_demo-groovy-dsl.zip[icon:download[] Groovy DSL] 28 | |- link:zips/sample_demo-kotlin-dsl.zip[icon:download[] Kotlin DSL] 29 | | 30 | | 31 | |= Demo Sample 32 | | 33 | |Some doc 34 | |""".stripMargin() 35 | assert groovyReadmeFile.text == kotlinReadmeFile.text 36 | } 37 | 38 | @Override 39 | protected void assertDslZipsHaveContent() { 40 | kotlinDslZipFile.asZip().assertHasDescendants( 41 | "gradlew", "gradlew.bat", "gradle/wrapper/gradle-wrapper.properties", "gradle/wrapper/gradle-wrapper.jar", 42 | "README", 43 | "build.gradle.kts", "settings.gradle.kts") 44 | groovyDslZipFile.asZip().assertHasDescendants( 45 | "gradlew", "gradlew.bat", "gradle/wrapper/gradle-wrapper.properties", "gradle/wrapper/gradle-wrapper.jar", 46 | "README", 47 | "build.gradle", "settings.gradle") 48 | } 49 | 50 | @Override 51 | protected void assertDslZipFilesExists() { 52 | groovyDslZipFile.assertExists() 53 | kotlinDslZipFile.assertExists() 54 | } 55 | 56 | @Override 57 | protected String configureAsciidoctorIncludeSample() { 58 | return ''' 59 | |==== 60 | |include::sample[dir="groovy", files="settings.gradle[]"] 61 | |include::sample[dir="kotlin", files="settings.gradle.kts[]"] 62 | |==== 63 | |'''.stripMargin() 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/samples/AbstractExemplarBothDslSampleFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples 2 | 3 | import org.gradle.testkit.runner.BuildResult 4 | 5 | abstract class AbstractExemplarBothDslSampleFunctionalTest extends AbstractTestWithExemplarSampleFunctionalTest { 6 | @Override 7 | protected void assertExemplarTasksExecutedAndNotSkipped(BuildResult result) { 8 | assertExemplarTasksExecutedAndNotSkipped(result, "Kotlin") 9 | assertExemplarTasksExecutedAndNotSkipped(result, "Groovy") 10 | } 11 | 12 | @Override 13 | protected List getExpectedTestsFor(String sampleName, String... testNames) { 14 | return testNames.collect { testName -> "org.gradle.exemplar.ExemplarExternalSamplesFunctionalTest.${sampleName}_groovy_${testName}.sample" } + 15 | testNames.collect { testName -> "org.gradle.exemplar.ExemplarExternalSamplesFunctionalTest.${sampleName}_kotlin_${testName}.sample" } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/samples/AbstractExemplarGroovyDslSampleFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples 2 | 3 | import org.gradle.testkit.runner.BuildResult 4 | 5 | abstract class AbstractExemplarGroovyDslSampleFunctionalTest extends AbstractTestWithExemplarSampleFunctionalTest { 6 | @Override 7 | protected void assertExemplarTasksExecutedAndNotSkipped(BuildResult result) { 8 | assertExemplarTasksExecutedAndNotSkipped(result, "Groovy") 9 | } 10 | 11 | @Override 12 | protected List getExpectedTestsFor(String sampleName, String... testNames) { 13 | return testNames.collect { testName -> "org.gradle.exemplar.ExemplarExternalSamplesFunctionalTest.${sampleName}_groovy_${testName}.sample".toString() } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/samples/AbstractExemplarKotlinDslSampleFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples 2 | 3 | import org.gradle.testkit.runner.BuildResult 4 | 5 | abstract class AbstractExemplarKotlinDslSampleFunctionalTest extends AbstractTestWithExemplarSampleFunctionalTest { 6 | @Override 7 | protected void assertExemplarTasksExecutedAndNotSkipped(BuildResult result) { 8 | assertExemplarTasksExecutedAndNotSkipped(result, "Kotlin") 9 | } 10 | 11 | @Override 12 | protected List getExpectedTestsFor(String sampleName, String... testNames) { 13 | return testNames.collect { testName -> "org.gradle.exemplar.ExemplarExternalSamplesFunctionalTest.${sampleName}_kotlin_${testName}.sample".toString() } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/samples/AbstractGroovyDslSampleFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples 2 | 3 | import org.gradle.docs.TestFile 4 | 5 | abstract class AbstractGroovyDslSampleFunctionalTest extends AbstractBasicSampleFunctionalTest { 6 | @Override 7 | protected List getDslZipFiles() { 8 | return [groovyDslZipFile] 9 | } 10 | 11 | @Override 12 | protected void assertDslZipsHaveContent() { 13 | kotlinDslZipFile.assertDoesNotExist() 14 | groovyDslZipFile.asZip().assertHasDescendants( 15 | "gradlew", "gradlew.bat", "gradle/wrapper/gradle-wrapper.properties", "gradle/wrapper/gradle-wrapper.jar", 16 | "README", 17 | "build.gradle", "settings.gradle") 18 | } 19 | 20 | @Override 21 | protected void assertDslZipFilesExists() { 22 | groovyDslZipFile.assertExists() 23 | kotlinDslZipFile.assertDoesNotExist() 24 | } 25 | 26 | @Override 27 | protected void assertReadmeHasContent() { 28 | def groovyReadmeFile = file("build/working/samples/install/demo/groovy/README") 29 | def kotlinReadmeFile = file("build/working/samples/install/demo/kotlin/README") 30 | assert groovyReadmeFile.text == """:samples-dir: ${file('/build/working/samples/install/demo')} 31 | |:gradle-version: ${gradleVersion} 32 | | 33 | |= Demo Sample 34 | | 35 | |[.download] 36 | |- link:zips/sample_demo-groovy-dsl.zip[icon:download[] Groovy DSL] 37 | | 38 | | 39 | |= Demo Sample 40 | | 41 | |Some doc 42 | |""".stripMargin() 43 | kotlinReadmeFile.assertDoesNotExist() 44 | } 45 | 46 | @Override 47 | protected String configureAsciidoctorIncludeSample() { 48 | return ''' 49 | |==== 50 | |include::sample[dir="groovy", files="settings.gradle[]"] 51 | |==== 52 | |'''.stripMargin() 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/samples/AbstractKotlinDslSampleFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples 2 | 3 | import org.gradle.docs.TestFile 4 | 5 | abstract class AbstractKotlinDslSampleFunctionalTest extends AbstractBasicSampleFunctionalTest { 6 | @Override 7 | protected List getDslZipFiles() { 8 | return [kotlinDslZipFile] 9 | } 10 | 11 | @Override 12 | protected void assertDslZipsHaveContent() { 13 | groovyDslZipFile.assertDoesNotExist() 14 | kotlinDslZipFile.asZip().assertHasDescendants("gradlew", "gradlew.bat", "gradle/wrapper/gradle-wrapper.properties", "gradle/wrapper/gradle-wrapper.jar", "README", "build.gradle.kts", "settings.gradle.kts") 15 | } 16 | 17 | @Override 18 | protected void assertDslZipFilesExists() { 19 | groovyDslZipFile.assertDoesNotExist() 20 | kotlinDslZipFile.assertExists() 21 | } 22 | 23 | @Override 24 | protected void assertReadmeHasContent() { 25 | def groovyReadmeFile = file("build/working/samples/install/demo/groovy/README") 26 | def kotlinReadmeFile = file("build/working/samples/install/demo/kotlin/README") 27 | assert kotlinReadmeFile.text == """:samples-dir: ${file('/build/working/samples/install/demo')} 28 | |:gradle-version: ${gradleVersion} 29 | | 30 | |= Demo Sample 31 | | 32 | |[.download] 33 | |- link:zips/sample_demo-kotlin-dsl.zip[icon:download[] Kotlin DSL] 34 | | 35 | | 36 | |= Demo Sample 37 | | 38 | |Some doc 39 | |""".stripMargin() 40 | groovyReadmeFile.assertDoesNotExist() 41 | } 42 | 43 | @Override 44 | protected String configureAsciidoctorIncludeSample() { 45 | return ''' 46 | |==== 47 | |include::sample[dir="kotlin", files="settings.gradle.kts[]"] 48 | |==== 49 | |'''.stripMargin() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/samples/AbstractSampleFunctionalSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradle.docs.samples 18 | 19 | import org.gradle.docs.AbstractFunctionalTest 20 | import org.gradle.docs.DocumentationTrait 21 | import org.gradle.docs.TestFile 22 | import org.gradle.testkit.runner.BuildResult 23 | 24 | import static org.gradle.testkit.runner.TaskOutcome.FROM_CACHE 25 | import static org.gradle.testkit.runner.TaskOutcome.NO_SOURCE 26 | import static org.gradle.testkit.runner.TaskOutcome.SKIPPED 27 | import static org.gradle.testkit.runner.TaskOutcome.SUCCESS 28 | import static org.gradle.testkit.runner.TaskOutcome.UP_TO_DATE 29 | import static org.hamcrest.CoreMatchers.containsString 30 | 31 | abstract class AbstractSampleFunctionalSpec extends AbstractFunctionalTest implements SamplesTrait, DocumentationTrait { 32 | protected static final SKIPPED_TASK_OUTCOMES = [FROM_CACHE, UP_TO_DATE, SKIPPED, NO_SOURCE] 33 | 34 | protected TestFile getSampleDirectoryUnderTest() { 35 | return projectDir.file('src/docs/samples/demo') 36 | } 37 | 38 | protected TestFile getGroovyDslZipFile() { 39 | return file("build/sample-zips/sample_demo-groovy-dsl.zip") 40 | } 41 | 42 | protected TestFile getKotlinDslZipFile() { 43 | return file("build/sample-zips/sample_demo-kotlin-dsl.zip") 44 | } 45 | 46 | protected static String getSampleUnderTestDsl() { 47 | return sampleDsl('demo') 48 | } 49 | 50 | protected static void assertBothDslSampleTasksSkipped(BuildResult result) { 51 | assertCommonSampleTasksSkipped(result) 52 | assertDslSampleTasksSkipped(result, "Groovy") 53 | assertDslSampleTasksSkipped(result, "Kotlin") 54 | } 55 | 56 | protected static void assertBothDslSampleTasksExecutedAndNotSkipped(BuildResult result) { 57 | assertCommonSampleTasksExecutedAndNotSkipped(result) 58 | assertDslSampleTasksExecutedAndNotSkipped(result, "Groovy") 59 | assertDslSampleTasksExecutedAndNotSkipped(result, "Kotlin") 60 | } 61 | 62 | protected static void assertOnlyGroovyDslTasksExecutedAndNotSkipped(BuildResult result) { 63 | assertCommonSampleTasksExecutedAndNotSkipped(result) 64 | assertDslSampleTasksExecutedAndNotSkipped(result, "Groovy") 65 | assertDslSampleTasksNotExecuted(result, "Kotlin") 66 | } 67 | 68 | protected static void assertOnlyKotlinDslTasksExecutedAndNotSkipped(BuildResult result) { 69 | assertCommonSampleTasksExecutedAndNotSkipped(result) 70 | assertDslSampleTasksExecutedAndNotSkipped(result, "Kotlin") 71 | assertDslSampleTasksNotExecuted(result, "Groovy") 72 | } 73 | 74 | private static void assertDslSampleTasksNotExecuted(BuildResult result, String dsl) { 75 | assert result.task(":zipSampleDemo${dsl}") == null 76 | } 77 | 78 | protected static void assertDslSampleTasksExecutedAndNotSkipped(BuildResult result, String dsl) { 79 | assert result.task(":zipSampleDemo${dsl}").outcome == SUCCESS 80 | } 81 | 82 | protected static void assertDslSampleTasksSkipped(BuildResult result, String dsl) { 83 | assert result.task(":zipSampleDemo${dsl}").outcome in SKIPPED_TASK_OUTCOMES 84 | } 85 | 86 | private static void assertCommonSampleTasksExecutedAndNotSkipped(BuildResult result) { 87 | assert result.task(":generateDemoPage").outcome == SUCCESS 88 | assert result.task(":assembleDemoSample").outcome == SUCCESS 89 | } 90 | 91 | private static void assertCommonSampleTasksSkipped(BuildResult result) { 92 | assert result.task(":generateDemoPage").outcome in SKIPPED_TASK_OUTCOMES 93 | assert result.task(":assembleDemoSample").outcome in SKIPPED_TASK_OUTCOMES 94 | } 95 | 96 | protected static void assertGradleWrapperVersion(TestFile file, String expectedGradleVersion) { 97 | file.asZip().assertDescendantHasContent('gradle/wrapper/gradle-wrapper.properties', containsString("-${expectedGradleVersion}-")) 98 | } 99 | 100 | protected void makeSingleProject() { 101 | buildFile << applyDocumentationPlugin() << """ 102 | repositories { 103 | mavenCentral() 104 | maven { 105 | url = "https://repo.gradle.org/gradle/public" 106 | } 107 | } 108 | dependencies { 109 | docsTestImplementation gradleTestKit() 110 | } 111 | 112 | tasks.named('docsTest').configure { 113 | modularity.getInferModulePath().set(false) 114 | } 115 | """ << createSample('demo') 116 | } 117 | 118 | protected void writeSampleUnderTest(TestFile directory = file('src/docs/samples/demo')) { 119 | writeReadmeTo(directory) 120 | writeGroovyDslSampleTo(directory.file('groovy')) 121 | writeKotlinDslSampleTo(directory.file('kotlin')) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/samples/BasicConventionalBothDslSampleFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples 2 | 3 | class BasicConventionalBothDslSampleFunctionalTest extends AbstractBothDslSampleFunctionalTest { 4 | } 5 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/samples/BasicConventionalGroovyDslSampleFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples 2 | 3 | import org.gradle.docs.TestFile 4 | import org.gradle.testkit.runner.BuildResult 5 | 6 | class BasicConventionalGroovyDslSampleFunctionalTest extends AbstractGroovyDslSampleFunctionalTest { 7 | @Override 8 | protected void writeSampleUnderTest(TestFile directory) { 9 | writeReadmeTo(directory) 10 | writeGroovyDslSampleTo(directory.file('groovy')) 11 | } 12 | 13 | @Override 14 | protected void assertSampleTasksExecutedAndNotSkipped(BuildResult result) { 15 | assertOnlyGroovyDslTasksExecutedAndNotSkipped(result) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/samples/BasicConventionalKotlinDslSampleFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples 2 | 3 | import org.gradle.docs.TestFile 4 | import org.gradle.testkit.runner.BuildResult 5 | 6 | class BasicConventionalKotlinDslSampleFunctionalTest extends AbstractKotlinDslSampleFunctionalTest { 7 | @Override 8 | protected void writeSampleUnderTest(TestFile directory) { 9 | writeReadmeTo(directory) 10 | writeKotlinDslSampleTo(directory.file('kotlin')) 11 | } 12 | 13 | @Override 14 | protected void assertSampleTasksExecutedAndNotSkipped(BuildResult result) { 15 | assertOnlyKotlinDslTasksExecutedAndNotSkipped(result) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/samples/BasicExplicitBothDslSampleFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples 2 | 3 | class BasicExplicitBothDslSampleFunctionalTest extends AbstractBothDslSampleFunctionalTest { 4 | @Override 5 | protected void makeSingleProject() { 6 | super.makeSingleProject() 7 | buildFile << """ 8 | import ${Dsl.canonicalName} 9 | documentation.samples.publishedSamples.all { 10 | dsls = [ Dsl.KOTLIN, Dsl.GROOVY ] 11 | } 12 | """ 13 | } 14 | 15 | def "can relocate both DSL sample source"() { 16 | given: 17 | makeSingleProject() 18 | buildFile << """ 19 | ${sampleUnderTestDsl} { 20 | sampleDirectory = file('src') 21 | groovy { 22 | setFrom(file('src/groovy-dsl')) 23 | } 24 | kotlin { 25 | setFrom(file('src/kotlin-dsl')) 26 | } 27 | } 28 | """ 29 | writeReadmeTo(file('src')) 30 | writeGroovyDslSampleTo(file('src/groovy-dsl')) 31 | file('src/groovy/do.not.include') << "should not be included" 32 | 33 | writeKotlinDslSampleTo(file('src/kotlin-dsl')) 34 | file('src/kotlin/do.not.include') << "should not be included" 35 | 36 | when: 37 | build('assembleDemoSample') 38 | 39 | then: 40 | assertSampleTasksExecutedAndNotSkipped(result) 41 | assertDslZipsHaveContent() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/samples/BasicExplicitGroovyDslSampleFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples 2 | 3 | 4 | import org.gradle.testkit.runner.BuildResult 5 | 6 | class BasicExplicitGroovyDslSampleFunctionalTest extends AbstractGroovyDslSampleFunctionalTest { 7 | @Override 8 | protected void makeSingleProject() { 9 | super.makeSingleProject() 10 | buildFile << """ 11 | import ${Dsl.canonicalName} 12 | documentation.samples.publishedSamples.all { 13 | dsls = [ Dsl.GROOVY ] 14 | } 15 | """ 16 | } 17 | 18 | @Override 19 | protected void assertSampleTasksExecutedAndNotSkipped(BuildResult result) { 20 | assertOnlyGroovyDslTasksExecutedAndNotSkipped(result) 21 | } 22 | 23 | def "can relocate Groovy DSL sample source"() { 24 | given: 25 | makeSingleProject() 26 | buildFile << """ 27 | ${sampleUnderTestDsl} { 28 | sampleDirectory = file('src') 29 | groovy { 30 | setFrom(file('src/groovy-dsl')) 31 | } 32 | } 33 | """ 34 | writeReadmeTo(file('src')) 35 | writeGroovyDslSampleTo(file('src/groovy-dsl')) 36 | file('src/groovy/do.not.include') << "should not be included" 37 | 38 | when: 39 | build('assembleDemoSample') 40 | 41 | then: 42 | assertSampleTasksExecutedAndNotSkipped(result) 43 | assertDslZipsHaveContent() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/samples/BasicExplicitKotlinDslSampleFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples 2 | 3 | 4 | import org.gradle.testkit.runner.BuildResult 5 | 6 | class BasicExplicitKotlinDslSampleFunctionalTest extends AbstractKotlinDslSampleFunctionalTest { 7 | @Override 8 | protected void makeSingleProject() { 9 | super.makeSingleProject() 10 | buildFile << """ 11 | import ${Dsl.canonicalName} 12 | documentation.samples.publishedSamples.all { 13 | dsls = [ Dsl.KOTLIN ] 14 | } 15 | """ 16 | } 17 | 18 | @Override 19 | protected void assertSampleTasksExecutedAndNotSkipped(BuildResult result) { 20 | assertOnlyKotlinDslTasksExecutedAndNotSkipped(result) 21 | } 22 | 23 | def "can relocate Kotlin DSL sample source"() { 24 | given: 25 | makeSingleProject() 26 | buildFile << """ 27 | ${sampleUnderTestDsl} { 28 | sampleDirectory = file('src') 29 | kotlin { 30 | setFrom(file('src/kotlin-dsl')) 31 | } 32 | } 33 | """ 34 | writeReadmeTo(file('src')) 35 | writeKotlinDslSampleTo(file('src/kotlin-dsl')) 36 | file('src/kotlin/do.not.include') << "should not be included" 37 | 38 | when: 39 | build('assembleDemoSample') 40 | 41 | then: 42 | assertSampleTasksExecutedAndNotSkipped(result) 43 | assertDslZipsHaveContent() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/samples/IncrementalSamplesFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradle.docs.samples 18 | 19 | 20 | import static org.gradle.testkit.runner.TaskOutcome.SUCCESS 21 | import static org.hamcrest.CoreMatchers.containsString 22 | 23 | class IncrementalSamplesFunctionalTest extends AbstractSampleFunctionalSpec { 24 | def "skips all tasks when no changes"() { 25 | makeSingleProject() 26 | writeSampleUnderTest() 27 | 28 | when: 29 | build("assemble") 30 | 31 | then: 32 | result.task(':generateSampleIndex').outcome == SUCCESS 33 | result.task(":generateWrapperForSamples").outcome == SUCCESS 34 | assertBothDslSampleTasksExecutedAndNotSkipped(result) 35 | 36 | when: 37 | build("assemble") 38 | 39 | then: 40 | result.task(':generateSampleIndex').outcome in SKIPPED_TASK_OUTCOMES 41 | result.task(":generateWrapperForSamples").outcome in SKIPPED_TASK_OUTCOMES 42 | assertBothDslSampleTasksSkipped(result) 43 | } 44 | 45 | def "executes generate, install and Zip tasks when README content changes"() { 46 | makeSingleProject() 47 | writeSampleUnderTest() 48 | 49 | when: 50 | build("assemble") 51 | 52 | then: 53 | assertBothDslSampleTasksExecutedAndNotSkipped(result) 54 | 55 | when: 56 | build("assemble") 57 | 58 | then: 59 | assertBothDslSampleTasksSkipped(result) 60 | 61 | when: 62 | sampleDirectoryUnderTest.file('README.adoc') << "More content\n" 63 | and: 64 | build("assemble") 65 | 66 | then: 67 | result.task(':generateWrapperForSamples').outcome in SKIPPED_TASK_OUTCOMES 68 | result.task(':generateSampleIndex').outcome in SKIPPED_TASK_OUTCOMES 69 | assertBothDslSampleTasksExecutedAndNotSkipped(result) 70 | and: 71 | def samplePage = file("build/working/samples/docs/sample_demo.adoc") 72 | samplePage.text.contains("More content") 73 | 74 | when: 75 | build("assemble") 76 | then: 77 | assertBothDslSampleTasksSkipped(result) 78 | } 79 | 80 | def "change to Groovy content causes only Groovy to be out-of-date"() { 81 | makeSingleProject() 82 | writeSampleUnderTest() 83 | build("assemble") 84 | 85 | when: 86 | build("assemble") 87 | 88 | then: 89 | result.task(':generateSampleIndex').outcome in SKIPPED_TASK_OUTCOMES 90 | result.task(":generateWrapperForSamples").outcome in SKIPPED_TASK_OUTCOMES 91 | assertBothDslSampleTasksSkipped(result) 92 | 93 | when: 94 | sampleDirectoryUnderTest.file('groovy/build.gradle') << "// This is a change" 95 | and: 96 | build("assemble") 97 | then: 98 | result.task(':generateSampleIndex').outcome in SKIPPED_TASK_OUTCOMES 99 | result.task(":generateWrapperForSamples").outcome in SKIPPED_TASK_OUTCOMES 100 | assertDslSampleTasksExecutedAndNotSkipped(result, "Groovy") 101 | assertDslSampleTasksSkipped(result, "Kotlin") 102 | file("build/sample-zips/sample_demo-groovy-dsl.zip").asZip().assertDescendantHasContent("build.gradle", containsString("// This is a change")) 103 | } 104 | 105 | def "change to Kotlin content causes only Kotlin to be out-of-date"() { 106 | makeSingleProject() 107 | writeSampleUnderTest() 108 | build("assemble") 109 | 110 | when: 111 | build("assemble") 112 | 113 | then: 114 | result.task(':generateSampleIndex').outcome in SKIPPED_TASK_OUTCOMES 115 | result.task(":generateWrapperForSamples").outcome in SKIPPED_TASK_OUTCOMES 116 | assertBothDslSampleTasksSkipped(result) 117 | 118 | when: 119 | sampleDirectoryUnderTest.file('kotlin/build.gradle.kts') << "// This is a change" 120 | and: 121 | build("assemble") 122 | then: 123 | result.task(':generateSampleIndex').outcome in SKIPPED_TASK_OUTCOMES 124 | result.task(":generateWrapperForSamples").outcome in SKIPPED_TASK_OUTCOMES 125 | assertDslSampleTasksExecutedAndNotSkipped(result, "Kotlin") 126 | assertDslSampleTasksSkipped(result, "Groovy") 127 | file("build/sample-zips/sample_demo-kotlin-dsl.zip").asZip().assertDescendantHasContent("build.gradle.kts", containsString("// This is a change")) 128 | } 129 | 130 | def "index is regenerated when sample is added or removed"() { 131 | makeSingleProject() 132 | writeSampleUnderTest() 133 | build("generateSampleIndex") 134 | 135 | when: 136 | build("generateSampleIndex") 137 | 138 | then: 139 | result.task(':generateSampleIndex').outcome in SKIPPED_TASK_OUTCOMES 140 | 141 | when: 142 | writeSampleUnderTest(file('src/docs/samples/new-sample')) 143 | buildFile << createSampleWithBothDsl('newSample') 144 | and: 145 | build("generateSampleIndex") 146 | then: 147 | result.task(':generateSampleIndex').outcome == SUCCESS 148 | def indexFile = file("build/tmp/generateSampleIndex/index.adoc") 149 | indexFile.text.contains('- <>') 150 | indexFile.text.contains('- <>') 151 | } 152 | // TODO: Output are cached 153 | } 154 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/samples/SamplesBaseDocumentationFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradle.docs.samples 18 | 19 | import org.gradle.docs.AbstractBaseDocumentationFunctionalTest 20 | 21 | class SamplesBaseDocumentationFunctionalTest extends AbstractBaseDocumentationFunctionalTest implements SamplesTrait { 22 | @Override 23 | protected String createDocumentationElement(String name) { 24 | return createSampleWithBothDsl(name) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/samples/SamplesRenderableDocumentationFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples 2 | 3 | import org.gradle.docs.AbstractRenderableDocumentationFunctionalTest 4 | import org.gradle.docs.TestFile 5 | 6 | class SamplesRenderableDocumentationFunctionalTest extends AbstractRenderableDocumentationFunctionalTest implements SamplesTrait { 7 | @Override 8 | protected String createDocumentationElement(String name) { 9 | return createSampleWithBothDsl(name) 10 | } 11 | 12 | @Override 13 | protected String documentationDsl(String name) { 14 | return sampleDsl(name) 15 | } 16 | 17 | @Override 18 | protected void makeSingleProject() { 19 | buildFile << applyDocumentationPlugin() << createSampleWithBothDsl('demo') 20 | } 21 | 22 | @Override 23 | protected void writeDocumentationUnderTest() { 24 | writeReadmeTo(file('src/docs/samples/demo')) 25 | writeGroovyDslSampleTo(file('src/docs/samples/demo/groovy')) 26 | writeKotlinDslSampleTo(file('src/docs/samples/demo/kotlin')) 27 | } 28 | 29 | @Override 30 | protected TestFile getContentFileUnderTest() { 31 | return file('src/docs/samples/demo/README.adoc') 32 | } 33 | 34 | @Override 35 | protected String getCheckTaskNameUnderTest() { 36 | return 'checkDemoSampleLinks' 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/samples/SamplesTrait.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples 2 | 3 | import org.gradle.docs.TestFile 4 | 5 | trait SamplesTrait { 6 | static String createSample(String name) { 7 | return """ 8 | documentation.samples.publishedSamples.create('${name}') 9 | """ 10 | } 11 | 12 | static String createSampleWithBothDsl(String name) { 13 | return """ 14 | documentation.samples.publishedSamples.create('${name}') { 15 | dsls = [${Dsl.canonicalName}.KOTLIN, ${Dsl.canonicalName}.GROOVY] 16 | } 17 | """ 18 | } 19 | 20 | static String configureSampleKotlinDsl(String name) { 21 | return """ 22 | ${sampleDsl(name)}.dsls.add(${Dsl.canonicalName}.KOTLIN) 23 | """ 24 | } 25 | 26 | static String configureSampleGroovyDsl(String name) { 27 | return """ 28 | ${sampleDsl(name)}.dsls.add(${Dsl.canonicalName}.GROOVY) 29 | """ 30 | } 31 | 32 | static String sampleDsl(String name) { 33 | return "documentation.samples.publishedSamples.${name}" 34 | } 35 | 36 | static void writeReadmeTo(TestFile directory, String file = 'README.adoc') { 37 | directory.file(file) << ''' 38 | |= Demo Sample 39 | | 40 | |Some doc 41 | |'''.stripMargin() 42 | } 43 | 44 | static void writeGroovyDslSampleTo(TestFile directory) { 45 | directory.file('build.gradle') << ''' 46 | |// tag::println[] 47 | |println "Hello, world!" 48 | |// end:println[] 49 | |'''.stripMargin() 50 | directory.file('settings.gradle') << ''' 51 | |// tag::root-project-name[] 52 | |rootProject.name = 'demo' 53 | |// end:root-project-name[] 54 | |'''.stripMargin() 55 | } 56 | 57 | static void writeKotlinDslSampleTo(TestFile directory) { 58 | directory.file('build.gradle.kts') << ''' 59 | |// tag::println[] 60 | |println("Hello, world!") 61 | |// end:println[] 62 | |'''.stripMargin() 63 | directory.file('settings.gradle.kts') << ''' 64 | |// tag::root-project-name[] 65 | |rootProject.name = "demo" 66 | |// end:root-project-name[] 67 | |'''.stripMargin() 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/samples/SamplesWellBehavePluginFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples 2 | 3 | import org.gradle.docs.AbstractWellBehavePluginFunctionalTest 4 | 5 | class SamplesWellBehavePluginFunctionalTest extends AbstractWellBehavePluginFunctionalTest { 6 | @Override 7 | protected String getPluginIdUnderTest() { 8 | return 'org.gradle.samples' 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/samples/TestWithExemplarConventionalBothDslSampleFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples 2 | 3 | class TestWithExemplarConventionalBothDslSampleFunctionalTest extends AbstractExemplarBothDslSampleFunctionalTest { 4 | } 5 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/samples/TestWithExemplarConventionalGroovyDslSampleFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples 2 | 3 | import org.gradle.docs.TestFile 4 | 5 | class TestWithExemplarConventionalGroovyDslSampleFunctionalTest extends AbstractExemplarGroovyDslSampleFunctionalTest { 6 | @Override 7 | protected void writeSampleUnderTest(TestFile directory) { 8 | writeReadmeTo(directory) 9 | writeGroovyDslSampleTo(directory.file('groovy')) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/samples/TestWithExemplarConventionalKotlinDslSampleFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples 2 | 3 | import org.gradle.docs.TestFile 4 | 5 | class TestWithExemplarConventionalKotlinDslSampleFunctionalTest extends AbstractExemplarKotlinDslSampleFunctionalTest { 6 | @Override 7 | protected void writeSampleUnderTest(TestFile directory) { 8 | writeReadmeTo(directory) 9 | writeKotlinDslSampleTo(directory.file('kotlin')) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/samples/TestWithExemplarExplicitBothDslSampleFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples 2 | 3 | import spock.lang.Unroll 4 | 5 | class TestWithExemplarExplicitBothDslSampleFunctionalTest extends AbstractExemplarBothDslSampleFunctionalTest { 6 | @Override 7 | protected void makeSingleProject() { 8 | super.makeSingleProject() 9 | buildFile << """ 10 | import ${Dsl.canonicalName} 11 | documentation.samples.publishedSamples.all { 12 | dsls = [ Dsl.KOTLIN, Dsl.GROOVY ] 13 | } 14 | """ 15 | } 16 | 17 | @Unroll 18 | def "tests from tests-#dsl directory are executed in #dsl tests only"() { 19 | given: 20 | makeSingleProject() 21 | writeSampleUnderTest() 22 | def destination = file( 'src/docs/samples/demo') 23 | destination.file("tests-${dsl}/mytest.sample.conf").text = """ 24 | | executable: gradle 25 | | args: help 26 | |""".stripMargin() 27 | buildFile << expectTestsExecuted(["org.gradle.exemplar.ExemplarExternalSamplesFunctionalTest.demo_${dsl}_mytest.sample"] + getExpectedTestsFor('demo', 'sanityCheck')) 28 | build("generateSamplesExemplarFunctionalTest") 29 | when: 30 | build('docsTest') 31 | then: 32 | assertExemplarTasksExecutedAndNotSkipped(result) 33 | 34 | where: 35 | dsl << Dsl.values().collect { it.displayName.toLowerCase() } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/samples/TestWithExemplarExplicitGroovyDslSampleFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples 2 | 3 | class TestWithExemplarExplicitGroovyDslSampleFunctionalTest extends AbstractExemplarGroovyDslSampleFunctionalTest { 4 | @Override 5 | protected void makeSingleProject() { 6 | super.makeSingleProject() 7 | buildFile << """ 8 | import ${Dsl.canonicalName} 9 | documentation.samples.publishedSamples.all { 10 | dsls = [ Dsl.GROOVY ] 11 | } 12 | """ 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/samples/TestWithExemplarExplicitKotlinDslSampleFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples 2 | 3 | class TestWithExemplarExplicitKotlinDslSampleFunctionalTest extends AbstractExemplarKotlinDslSampleFunctionalTest { 4 | @Override 5 | protected void makeSingleProject() { 6 | super.makeSingleProject() 7 | buildFile << """ 8 | import ${Dsl.canonicalName} 9 | documentation.samples.publishedSamples.all { 10 | dsls = [ Dsl.KOTLIN ] 11 | } 12 | """ 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/snippets/SnippetsBaseDocumentationFunctionalTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradle.docs.snippets 18 | 19 | import org.gradle.docs.AbstractBaseDocumentationFunctionalTest 20 | 21 | class SnippetsBaseDocumentationFunctionalTest extends AbstractBaseDocumentationFunctionalTest implements SnippetsTrait { 22 | @Override 23 | protected String createDocumentationElement(String name) { 24 | return createSnippet(name) << configureSnippetKotlinDsl(name) << configureSnippetGroovyDsl(name) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/functionalTest/groovy/org/gradle/docs/snippets/SnippetsTrait.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradle.docs.snippets 18 | 19 | import org.gradle.docs.samples.Dsl 20 | 21 | trait SnippetsTrait { 22 | static String createSnippet(String name) { 23 | return """ 24 | documentation.snippets.publishedSnippets.create('${name}') 25 | """ 26 | } 27 | 28 | static String snippetDsl(String name) { 29 | return "documentation.snippets.publishedSnippets.${name}" 30 | } 31 | 32 | static String configureSnippetKotlinDsl(String name) { 33 | return """ 34 | ${snippetDsl(name)}.dsls.add(${Dsl.canonicalName}.KOTLIN) 35 | """ 36 | } 37 | 38 | static String configureSnippetGroovyDsl(String name) { 39 | return """ 40 | ${snippetDsl(name)}.dsls.add(${Dsl.canonicalName}.GROOVY) 41 | """ 42 | } 43 | } -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/DocumentationExtension.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs; 2 | 3 | import org.gradle.api.Action; 4 | import org.gradle.docs.guides.Guides; 5 | import org.gradle.docs.samples.Samples; 6 | import org.gradle.docs.snippets.Snippets; 7 | 8 | public interface DocumentationExtension { 9 | Guides getGuides(); 10 | 11 | void guides(Action action); 12 | 13 | Samples getSamples(); 14 | 15 | void samples(Action action); 16 | 17 | Snippets getSnippets(); 18 | 19 | void snippets(Action action); 20 | } 21 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/guides/Guide.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradle.docs.guides; 18 | 19 | import org.gradle.api.Named; 20 | import org.gradle.api.file.DirectoryProperty; 21 | import org.gradle.api.provider.Property; 22 | 23 | /** 24 | * @since 0.1 25 | */ 26 | public interface Guide extends Named, GuideSummary { 27 | /** 28 | * By convention, this is the guide name off the extension's guide root directory. 29 | * 30 | * @return Property for configuring the guide root directory. 31 | */ 32 | DirectoryProperty getGuideDirectory(); 33 | 34 | /** 35 | * Path of repository relative to {@code https://github.com}. 36 | * 37 | * @return Property for configuring the guide repository. 38 | * @since 0.15.8 39 | */ 40 | Property getRepositoryPath(); 41 | 42 | /** 43 | * Minimum Gradle version this guide works on. 44 | * 45 | * @return Property for configuring the minimum Gradle version for the guide. 46 | * @since 0.15.7 47 | */ 48 | Property getMinimumGradleVersion(); 49 | 50 | /** 51 | * Short description of the guide. 52 | * 53 | * @return Property for configuring the description. 54 | * @since 0.15.8 55 | */ 56 | Property getDescription(); 57 | 58 | /** 59 | * Guide name used to locate the project within the repository and tag issues with the right label. 60 | * 61 | * @return Property for configuring the repository URL and issue label. 62 | * @since 0.16.1 63 | */ 64 | Property getGuideName(); 65 | } 66 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/guides/GuideSummary.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.guides; 2 | 3 | import org.gradle.api.provider.Property; 4 | 5 | public interface GuideSummary { 6 | /** 7 | * @return Property for configuring the category this guide appears in. 8 | */ 9 | Property getCategory(); 10 | 11 | /** 12 | * @return Property for configuring the guide documentation permalink. 13 | */ 14 | Property getPermalink(); 15 | 16 | /** 17 | * @return Property for configuring the guide display name. The display name is used within the guide index. 18 | */ 19 | Property getDisplayName(); 20 | } 21 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/guides/Guides.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.guides; 2 | 3 | import org.gradle.api.NamedDomainObjectContainer; 4 | import org.gradle.api.file.DirectoryProperty; 5 | 6 | public interface Guides { 7 | /** 8 | * By convention, this is src/docs/guides 9 | * 10 | * @return The root guide directory. 11 | */ 12 | DirectoryProperty getGuidesRoot(); 13 | 14 | /** 15 | * @return Buildable elements of all the available guides. 16 | */ 17 | GuidesDistribution getDistribution(); 18 | 19 | /** 20 | * @return Container of all published guides. This is the primary configuration point for all guides. 21 | */ 22 | NamedDomainObjectContainer getPublishedGuides(); 23 | } 24 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/guides/GuidesDistribution.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.guides; 2 | 3 | import org.gradle.api.file.ConfigurableFileCollection; 4 | 5 | public interface GuidesDistribution { 6 | /** 7 | * This is HTML and resources. 8 | * 9 | * @return collection of rendered documentation 10 | */ 11 | ConfigurableFileCollection getRenderedDocumentation(); 12 | } 13 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/guides/internal/GuideBinary.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.guides.internal; 2 | 3 | import org.gradle.api.Named; 4 | 5 | public abstract class GuideBinary implements Named { 6 | private final String name; 7 | 8 | public GuideBinary(String name) { 9 | this.name = name; 10 | } 11 | 12 | @Override 13 | public String getName() { 14 | return name; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/guides/internal/GuideContentBinary.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.guides.internal; 2 | 3 | import org.gradle.api.file.DirectoryProperty; 4 | import org.gradle.api.file.RegularFileProperty; 5 | import org.gradle.api.provider.Property; 6 | import org.gradle.docs.internal.RenderableContentBinary; 7 | import org.gradle.docs.internal.TestableRenderedContentLinksBinary; 8 | import org.gradle.docs.internal.ViewableContentBinary; 9 | 10 | import javax.inject.Inject; 11 | 12 | import static org.gradle.docs.internal.StringUtils.capitalize; 13 | 14 | public abstract class GuideContentBinary extends GuideBinary implements ViewableContentBinary, TestableRenderedContentLinksBinary, RenderableContentBinary { 15 | @Inject 16 | public GuideContentBinary(String name) { 17 | super(name); 18 | } 19 | 20 | public abstract Property getGradleVersion(); 21 | 22 | public abstract Property getRepositoryPath(); 23 | 24 | public abstract Property getDisplayName(); 25 | 26 | public abstract RegularFileProperty getIndexPageFile(); 27 | 28 | public abstract DirectoryProperty getGuideDirectory(); 29 | 30 | public abstract Property getBaseDirectory(); 31 | 32 | public abstract Property getRenderedPermalink(); 33 | 34 | public abstract Property getSourcePermalink(); 35 | 36 | public abstract RegularFileProperty getRenderedIndexPageFile(); 37 | 38 | public abstract RegularFileProperty getInstalledIndexPageFile(); 39 | 40 | @Override 41 | public String getViewTaskName() { 42 | return "view" + capitalize(getName()) + "Guide"; 43 | } 44 | 45 | @Override 46 | public String getCheckLinksTaskName() { 47 | return "check" + capitalize(getName()) + "GuideLinks"; 48 | } 49 | 50 | public abstract RegularFileProperty getSourcePageFile(); 51 | 52 | public abstract Property getGuideName(); 53 | } 54 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/guides/internal/GuideInternal.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.guides.internal; 2 | 3 | import org.gradle.docs.guides.Guide; 4 | 5 | import javax.inject.Inject; 6 | 7 | public abstract class GuideInternal implements Guide { 8 | private final String name; 9 | 10 | @Inject 11 | public GuideInternal(String name) { 12 | this.name = name; 13 | } 14 | 15 | @Override 16 | public String getName() { 17 | return name; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/guides/internal/GuidesInternal.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.guides.internal; 2 | 3 | import org.gradle.api.DomainObjectSet; 4 | import org.gradle.api.NamedDomainObjectContainer; 5 | import org.gradle.api.file.DirectoryProperty; 6 | import org.gradle.api.model.ObjectFactory; 7 | import org.gradle.docs.guides.Guides; 8 | import org.gradle.docs.guides.GuidesDistribution; 9 | 10 | import javax.inject.Inject; 11 | 12 | public abstract class GuidesInternal implements Guides { 13 | private final NamedDomainObjectContainer publishedGuides; 14 | private final DomainObjectSet binaries; 15 | private final GuidesDistribution guidesDistribution; 16 | 17 | @Inject 18 | public GuidesInternal(ObjectFactory objectFactory) { 19 | this.publishedGuides = objectFactory.domainObjectContainer(GuideInternal.class, name -> objectFactory.newInstance(GuideInternal.class, name)); 20 | this.binaries = objectFactory.domainObjectSet(GuideBinary.class); 21 | this.guidesDistribution = objectFactory.newInstance(GuidesDistribution.class); 22 | } 23 | 24 | @Override 25 | public NamedDomainObjectContainer getPublishedGuides() { 26 | return publishedGuides; 27 | } 28 | 29 | public DomainObjectSet getBinaries() { 30 | return binaries; 31 | } 32 | 33 | @Override 34 | public GuidesDistribution getDistribution() { 35 | return guidesDistribution; 36 | } 37 | 38 | /** 39 | * By convention, this is buildDir/working/guides/docs 40 | * 41 | * @return The root directory for all documentation. 42 | */ 43 | public abstract DirectoryProperty getDocumentationInstallRoot(); 44 | 45 | /** 46 | * By convention, this is buildDir/working/guides/render-samples 47 | * 48 | * @return The root directory for rendered documentation. 49 | */ 50 | public abstract DirectoryProperty getRenderedDocumentationRoot(); 51 | } 52 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/guides/internal/LegacyGuideDocumentationPlugin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradle.docs.guides.internal; 18 | 19 | import org.gradle.api.Plugin; 20 | import org.gradle.api.Project; 21 | import org.gradle.docs.DocumentationExtension; 22 | import org.gradle.docs.guides.Guide; 23 | 24 | import static org.gradle.docs.internal.StringUtils.toLowerCamelCase; 25 | 26 | /** 27 | * The guides base plugin provides conventions for all Gradle guides. 28 | *

29 | * Adds the custom attributes to {@link org.asciidoctor.gradle.jvm.AsciidoctorTask} for reference in Asciidoc files: 30 | *

    31 | *
  • {@literal samplescodedir}: The directory containing samples code defined as {@literal "$projectDir/samples/code"}
  • 32 | *
  • {@literal samplesoutputdir}: The directory containing expected samples output defined as {@literal "$projectDir/samples/output"}
  • 33 | *
34 | */ 35 | public class LegacyGuideDocumentationPlugin implements Plugin { 36 | public static final String GUIDE_EXTENSION_NAME = "guide"; 37 | 38 | public void apply(Project project) { 39 | project.getPluginManager().apply(GuidesDocumentationPlugin.class); 40 | 41 | addGuidesExtension(project); 42 | } 43 | 44 | private Guide addGuidesExtension(Project project) { 45 | Guide result = project.getExtensions().getByType(DocumentationExtension.class).getGuides().getPublishedGuides().create(toLowerCamelCase(project.getName())); 46 | project.getExtensions().add(Guide.class, GUIDE_EXTENSION_NAME, result); 47 | result.getGuideName().set(project.getName()); 48 | result.getDescription().set(result.getDisplayName()); 49 | result.getGuideDirectory().set(project.getProjectDir()); 50 | result.getPermalink().set(project.getName()); 51 | return result; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/guides/internal/TestableAsciidoctorGuideContentBinary.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradle.docs.guides.internal; 18 | 19 | import org.gradle.docs.internal.TestableAsciidoctorContentBinary; 20 | 21 | import javax.inject.Inject; 22 | 23 | public abstract class TestableAsciidoctorGuideContentBinary extends GuideBinary implements TestableAsciidoctorContentBinary { 24 | @Inject 25 | public TestableAsciidoctorGuideContentBinary(String name) { 26 | super(name); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/guides/internal/tasks/GenerateGuidePageAsciidoc.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.guides.internal.tasks; 2 | 3 | import org.gradle.api.DefaultTask; 4 | import org.gradle.api.UncheckedIOException; 5 | import org.gradle.api.file.RegularFileProperty; 6 | import org.gradle.api.provider.MapProperty; 7 | import org.gradle.api.tasks.Input; 8 | import org.gradle.api.tasks.InputFile; 9 | import org.gradle.api.tasks.OutputFile; 10 | import org.gradle.api.tasks.TaskAction; 11 | 12 | import java.io.BufferedOutputStream; 13 | import java.io.File; 14 | import java.io.FileOutputStream; 15 | import java.io.IOException; 16 | import java.nio.file.Files; 17 | import java.nio.file.Path; 18 | import java.util.Map; 19 | 20 | public abstract class GenerateGuidePageAsciidoc extends DefaultTask { 21 | @Input 22 | public abstract MapProperty getAttributes(); 23 | 24 | @InputFile 25 | public abstract RegularFileProperty getIndexFile(); 26 | 27 | @OutputFile 28 | public abstract RegularFileProperty getOutputFile(); 29 | 30 | @TaskAction 31 | public void generate() { 32 | File outputFile = getOutputFile().get().getAsFile(); 33 | Path sourceFile = getIndexFile().get().getAsFile().toPath(); 34 | 35 | try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(outputFile))) { 36 | bos.write(generateHeaderForGuidePage()); 37 | Files.copy(sourceFile, bos); 38 | } catch (IOException e) { 39 | throw new UncheckedIOException(e); 40 | } 41 | } 42 | 43 | private byte[] generateHeaderForGuidePage() { 44 | StringBuilder sb = new StringBuilder(); 45 | writeAttributes(sb); 46 | return sb.toString().getBytes(); 47 | } 48 | 49 | private void writeAttributes(StringBuilder sb) { 50 | Map attributes = getAttributes().get(); 51 | 52 | attributes.forEach((key, value) -> sb.append(":").append(key).append(": ").append(value).append("\n")); 53 | sb.append('\n'); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/internal/Asserts.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.internal; 2 | 3 | import org.gradle.api.Named; 4 | 5 | public class Asserts { 6 | public static void assertNameDoesNotContainsDisallowedCharacters(Named element, String format, Object... args) { 7 | if (element.getName().contains("_") || element.getName().contains("-")) { 8 | throw new IllegalArgumentException(String.format(format, args)); 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/internal/BuildDocumentationPlugin.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.internal; 2 | 3 | import org.gradle.api.Plugin; 4 | import org.gradle.api.Project; 5 | import org.gradle.docs.guides.internal.GuidesDocumentationPlugin; 6 | import org.gradle.docs.samples.internal.SamplesDocumentationPlugin; 7 | import org.gradle.docs.snippets.internal.SnippetsDocumentationPlugin; 8 | 9 | public class BuildDocumentationPlugin implements Plugin { 10 | @Override 11 | public void apply(Project project) { 12 | project.getPluginManager().apply(GuidesDocumentationPlugin.class); 13 | project.getPluginManager().apply(SamplesDocumentationPlugin.class); 14 | project.getPluginManager().apply(SnippetsDocumentationPlugin.class); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/internal/DocumentationBasePlugin.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.internal; 2 | 3 | import org.gradle.api.Plugin; 4 | import org.gradle.api.Project; 5 | import org.gradle.api.Task; 6 | import org.gradle.api.file.ProjectLayout; 7 | import org.gradle.api.tasks.SourceSet; 8 | import org.gradle.api.tasks.SourceSetContainer; 9 | import org.gradle.api.tasks.TaskContainer; 10 | import org.gradle.api.tasks.TaskProvider; 11 | import org.gradle.api.tasks.testing.Test; 12 | import org.gradle.language.base.plugins.LifecycleBasePlugin; 13 | 14 | public class DocumentationBasePlugin implements Plugin { 15 | public static final String DOCUMENTATION_GROUP_NAME = "Documentation"; 16 | public static final String DOCUMENTATION_EXTENSION_NAME = "documentation"; 17 | public static final String DOCS_TEST_SOURCE_SET_NAME = "docsTest"; 18 | public static final String DOCS_TEST_TASK_NAME = "docsTest"; 19 | public static final String DOCS_TEST_IMPLEMENTATION_CONFIGURATION_NAME = "docsTestImplementation"; 20 | 21 | @Override 22 | public void apply(Project project) { 23 | TaskContainer tasks = project.getTasks(); 24 | ProjectLayout layout = project.getLayout(); 25 | 26 | project.getPluginManager().apply("base"); 27 | project.getExtensions().create(DOCUMENTATION_EXTENSION_NAME, DocumentationExtensionInternal.class); 28 | 29 | TaskProvider check = tasks.named(LifecycleBasePlugin.CHECK_TASK_NAME); 30 | 31 | // Testing 32 | configureTesting(project, layout, tasks, check); 33 | } 34 | 35 | private void configureTesting(Project project, ProjectLayout layout, TaskContainer tasks, TaskProvider check) { 36 | project.getPluginManager().apply("groovy-base"); 37 | 38 | SourceSet sourceSet = project.getExtensions().getByType(SourceSetContainer.class).create(DOCS_TEST_SOURCE_SET_NAME); 39 | 40 | TaskProvider docsTestTask = tasks.register(sourceSet.getName(), Test.class, task -> { 41 | task.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP); 42 | task.setDescription("Test documentation"); 43 | task.setTestClassesDirs(sourceSet.getRuntimeClasspath()); 44 | task.setClasspath(sourceSet.getRuntimeClasspath()); 45 | task.setWorkingDir(layout.getProjectDirectory().getAsFile()); 46 | }); 47 | 48 | check.configure(task -> task.dependsOn(docsTestTask)); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/internal/DocumentationExtensionInternal.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.internal; 2 | 3 | import org.gradle.api.Action; 4 | import org.gradle.api.model.ObjectFactory; 5 | import org.gradle.docs.DocumentationExtension; 6 | import org.gradle.docs.guides.Guides; 7 | import org.gradle.docs.guides.internal.GuidesInternal; 8 | import org.gradle.docs.samples.Samples; 9 | import org.gradle.docs.samples.internal.SamplesInternal; 10 | import org.gradle.docs.snippets.Snippets; 11 | import org.gradle.docs.snippets.internal.SnippetsInternal; 12 | 13 | import javax.inject.Inject; 14 | 15 | public abstract class DocumentationExtensionInternal implements DocumentationExtension { 16 | private final GuidesInternal guides; 17 | private final SamplesInternal samples; 18 | private final SnippetsInternal snippets; 19 | 20 | @Inject 21 | public DocumentationExtensionInternal(ObjectFactory objectFactory) { 22 | this.guides = objectFactory.newInstance(GuidesInternal.class); 23 | this.samples = objectFactory.newInstance(SamplesInternal.class); 24 | this.snippets = objectFactory.newInstance(SnippetsInternal.class); 25 | } 26 | 27 | @Override 28 | public GuidesInternal getGuides() { 29 | return guides; 30 | } 31 | 32 | @Override 33 | public void guides(Action action) { 34 | action.execute(guides); 35 | } 36 | 37 | @Override 38 | public SamplesInternal getSamples() { 39 | return samples; 40 | } 41 | 42 | @Override 43 | public void samples(Action action) { 44 | action.execute(samples); 45 | } 46 | 47 | @Override 48 | public SnippetsInternal getSnippets() { 49 | return snippets; 50 | } 51 | 52 | @Override 53 | public void snippets(Action action) { 54 | action.execute(snippets); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/internal/FileUtils.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.internal; 2 | 3 | import java.io.File; 4 | import java.nio.file.Files; 5 | 6 | public class FileUtils { 7 | public static void deleteDirectory(File file) { 8 | File[] contents = file.listFiles(); 9 | if (contents != null) { 10 | for (File f : contents) { 11 | if (!Files.isSymbolicLink(f.toPath())) { 12 | deleteDirectory(f); 13 | } 14 | } 15 | } 16 | file.delete(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/internal/IOUtils.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.internal; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.nio.charset.Charset; 7 | 8 | public class IOUtils { 9 | public static String toString(InputStream inStream, Charset encoding) throws IOException { 10 | ByteArrayOutputStream outStream = new ByteArrayOutputStream(); 11 | int d; 12 | while ((d = inStream.read()) != -1) { 13 | outStream.write(d); 14 | } 15 | 16 | return outStream.toString(encoding); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/internal/RenderableContentBinary.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.internal; 2 | 3 | import org.gradle.api.file.ConfigurableFileCollection; 4 | import org.gradle.api.file.CopySpec; 5 | import org.gradle.api.provider.Property; 6 | 7 | public interface RenderableContentBinary { 8 | Property getResourceSpec(); 9 | 10 | ConfigurableFileCollection getResourceFiles(); 11 | 12 | Property getSourcePattern(); 13 | } 14 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/internal/StringUtils.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.internal; 2 | 3 | import java.util.Arrays; 4 | import java.util.regex.Matcher; 5 | import java.util.regex.Pattern; 6 | import java.util.stream.Collectors; 7 | 8 | public class StringUtils { 9 | private static final Pattern UPPER_LOWER = Pattern.compile("(?m)([A-Z]*)([a-z0-9]*)"); 10 | /** 11 | * Capitalizes the first letter of the string. 12 | * 13 | * example to Example 14 | * 15 | * @param s the string 16 | * @return transformed string 17 | */ 18 | public static String capitalize(String s) { 19 | return Character.toUpperCase(s.charAt(0)) + s.substring(1); 20 | } 21 | 22 | /** 23 | * Makes the first letter of the string lowercase. 24 | * 25 | * Example to example 26 | * 27 | * @param s the string 28 | * @return transformed string 29 | */ 30 | public static String uncapitalize(String s) { 31 | return Character.toLowerCase(s.charAt(0)) + s.substring(1); 32 | } 33 | 34 | /** 35 | * Capitalizes every word in a string. 36 | * 37 | * this is an example to This Is An Example 38 | * 39 | * @param s the string 40 | * @return transformed string 41 | */ 42 | public static String toTitleCase(String s) { 43 | return Arrays.stream(toWords(s).split(" ")).map(StringUtils::capitalize).collect(Collectors.joining(" ")); 44 | } 45 | 46 | /** 47 | * Converts an arbitrary string to space-separated words. 48 | * Eg, camelCase -> camel case, with_underscores -> with underscores 49 | */ 50 | private static String toWords(CharSequence string) { 51 | if (string == null) { 52 | return null; 53 | } 54 | char separator = ' '; 55 | StringBuilder builder = new StringBuilder(); 56 | int pos = 0; 57 | Matcher matcher = UPPER_LOWER.matcher(string); 58 | while (pos < string.length()) { 59 | matcher.find(pos); 60 | if (matcher.end() == pos) { 61 | // Not looking at a match 62 | pos++; 63 | continue; 64 | } 65 | if (!builder.isEmpty()) { 66 | builder.append(separator); 67 | } 68 | String group1 = matcher.group(1).toLowerCase(); 69 | String group2 = matcher.group(2); 70 | if (group2.isEmpty()) { 71 | builder.append(group1); 72 | } else { 73 | if (group1.length() > 1) { 74 | builder.append(group1, 0, group1.length() - 1); 75 | builder.append(separator); 76 | builder.append(group1.substring(group1.length() - 1)); 77 | } else { 78 | builder.append(group1); 79 | } 80 | builder.append(group2); 81 | } 82 | pos = matcher.end(); 83 | } 84 | 85 | return builder.toString(); 86 | } 87 | 88 | /** 89 | * Capitalizes every word in a string, removes all spaces and joins them together as one identifier. 90 | * 91 | * This is like a conventional Java identifier. 92 | * 93 | * this is an example to thisIsAnExample 94 | * 95 | * @param s the string 96 | * @return transformed string 97 | */ 98 | public static String toLowerCamelCase(String s) { 99 | return uncapitalize(String.join("", toTitleCase(s).split(" "))); 100 | } 101 | 102 | /** 103 | * Splits a string based on uppercase letters and rejoins them with underscores and makes the identifier lowercase. 104 | * 105 | * ThisIsAnExample to this_is_an_example 106 | * 107 | * @param s the string 108 | * @return transformed string 109 | */ 110 | public static String toSnakeCase(String s) { 111 | return s.replaceAll("(.)(\\p{Upper})", "$1_$2").toLowerCase(); 112 | } 113 | 114 | /** 115 | * Like {@link #toSnakeCase(String)}, except separated by hyphens. 116 | * 117 | * ThisIsAnExample to This-is-an-example 118 | * 119 | * @param s the string 120 | * @return transformed string 121 | */ 122 | public static String toKebabCase(String s) { 123 | Matcher m = Pattern.compile("(?<=[a-z0-9])[A-Z]").matcher(s); 124 | StringBuilder sb = new StringBuilder(); 125 | while (m.find()) { 126 | m.appendReplacement(sb, "-"+m.group().toLowerCase()); 127 | } 128 | m.appendTail(sb); 129 | return sb.toString(); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/internal/TestableAsciidoctorContentBinary.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradle.docs.internal; 18 | 19 | import org.gradle.api.file.DirectoryProperty; 20 | import org.gradle.api.file.RegularFileProperty; 21 | import org.gradle.docs.guides.internal.GuideContentBinary; 22 | import org.gradle.docs.samples.internal.SampleArchiveBinary; 23 | import org.gradle.docs.samples.internal.SampleContentBinary; 24 | 25 | public interface TestableAsciidoctorContentBinary { 26 | /** 27 | * @return Linked to {@link SampleContentBinary#getInstalledIndexPageFile()} or {@link GuideContentBinary#getInstalledIndexPageFile()} 28 | */ 29 | RegularFileProperty getContentFile(); 30 | 31 | /** 32 | * @return Linked to {@link SampleArchiveBinary#getInstallDirectory()} or null if no starting sample should be used 33 | */ 34 | DirectoryProperty getStartingSampleDirectory(); 35 | } 36 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/internal/TestableRenderedContentLinksBinary.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.internal; 2 | 3 | import org.gradle.api.file.RegularFileProperty; 4 | 5 | public interface TestableRenderedContentLinksBinary { 6 | String getCheckLinksTaskName(); 7 | 8 | RegularFileProperty getRenderedPageFile(); 9 | } 10 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/internal/ViewableContentBinary.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.internal; 2 | 3 | import org.gradle.api.file.RegularFileProperty; 4 | 5 | public interface ViewableContentBinary { 6 | String getViewTaskName(); 7 | 8 | RegularFileProperty getViewablePageFile(); 9 | } 10 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/internal/configure/AsciidoctorTasks.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.internal.configure; 2 | 3 | import groovy.lang.Closure; 4 | import org.asciidoctor.gradle.jvm.AsciidoctorTask; 5 | import org.gradle.api.Action; 6 | import org.gradle.api.Task; 7 | import org.gradle.api.file.CopySpec; 8 | import org.gradle.api.file.DuplicatesStrategy; 9 | import org.gradle.api.tasks.util.PatternSet; 10 | import org.gradle.docs.internal.RenderableContentBinary; 11 | 12 | import java.util.Collection; 13 | import java.util.Collections; 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | import java.util.stream.Collectors; 17 | 18 | import static org.gradle.docs.internal.FileUtils.deleteDirectory; 19 | 20 | public class AsciidoctorTasks { 21 | private static final Object IGNORED_CLOSURE_OWNER = new Object(); 22 | 23 | public static void configureResources(AsciidoctorTask task, Collection binaries) { 24 | task.getInputs().files(binaries.stream().map(RenderableContentBinary::getResourceFiles).collect(Collectors.toList())).withPropertyName("resourceFiles").optional(true); 25 | task.resources(new Closure(IGNORED_CLOSURE_OWNER) { 26 | public Object doCall(Object ignore) { 27 | CopySpec copySpec = (CopySpec) this.getDelegate(); 28 | copySpec.setDuplicatesStrategy(DuplicatesStrategy.EXCLUDE); 29 | binaries.stream() 30 | .map(RenderableContentBinary::getResourceSpec) 31 | .forEach(spec -> copySpec.with(spec.get())); 32 | return null; 33 | } 34 | }); 35 | } 36 | 37 | public static void configureSources(AsciidoctorTask task, Collection binaries) { 38 | task.sources(new Closure(IGNORED_CLOSURE_OWNER) { 39 | public Object doCall(Object ignore) { 40 | ((PatternSet)this.getDelegate()).setIncludes(binaries.stream().map(it -> it.getSourcePattern().get()).collect(Collectors.toList())); 41 | return null; 42 | } 43 | }); 44 | } 45 | 46 | public static void cleanStaleFiles(AsciidoctorTask task) { 47 | // It seems Asciidoctor task is copying the resource as opposed to synching them. Let's delete the output folder first. 48 | task.doFirst(t -> deleteDirectory(task.getOutputDir())); 49 | } 50 | 51 | public static Map genericAttributes() { 52 | Map attributes = new HashMap<>(); 53 | attributes.put("doctype", "book"); 54 | attributes.put("icons", "font"); 55 | attributes.put("source-highlighter", "prettify"); 56 | attributes.put("toc", "auto"); 57 | attributes.put("toclevels", 1); 58 | attributes.put("toc-title", "Contents"); 59 | return Collections.unmodifiableMap(attributes); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/internal/configure/ContentBinaries.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.internal.configure; 2 | 3 | import org.gradle.api.Project; 4 | import org.gradle.api.Task; 5 | import org.gradle.api.artifacts.Configuration; 6 | import org.gradle.api.artifacts.dsl.DependencyHandler; 7 | import org.gradle.api.tasks.TaskContainer; 8 | import org.gradle.api.tasks.TaskProvider; 9 | import org.gradle.docs.internal.TestableAsciidoctorContentBinary; 10 | import org.gradle.docs.internal.TestableRenderedContentLinksBinary; 11 | import org.gradle.docs.internal.ViewableContentBinary; 12 | import org.gradle.docs.internal.exemplar.AsciidoctorContentTest; 13 | import org.gradle.docs.internal.exemplar.AsciidoctorContentTestConsoleType; 14 | import org.gradle.docs.internal.tasks.CheckLinks; 15 | import org.gradle.docs.internal.tasks.ViewDocumentation; 16 | import org.gradle.docs.samples.internal.SampleContentBinary; 17 | import org.gradle.docs.samples.internal.tasks.GenerateSamplePageAsciidoc; 18 | import org.gradle.language.base.plugins.LifecycleBasePlugin; 19 | 20 | import java.time.Duration; 21 | import java.util.Collection; 22 | 23 | import static org.gradle.docs.internal.DocumentationBasePlugin.DOCUMENTATION_GROUP_NAME; 24 | import static org.gradle.docs.internal.StringUtils.capitalize; 25 | 26 | public class ContentBinaries { 27 | public static void createTasksForSampleContentBinary(TaskContainer tasks, SampleContentBinary binary) { 28 | TaskProvider generateSamplePage = tasks.register("generate" + capitalize(binary.getName()) + "Page", GenerateSamplePageAsciidoc.class, task -> { 29 | task.setDescription("Generates asciidoc page for sample '" + binary.getName() + "'"); 30 | 31 | task.getAttributes().put("samples-dir", binary.getSampleInstallDirectory().get().getAsFile().getAbsolutePath()); 32 | task.getAttributes().put("gradle-version", binary.getGradleVersion()); 33 | 34 | task.getSampleSummary().convention(binary.getSummary()); 35 | task.getReadmeFile().convention(binary.getSourcePageFile()); 36 | task.getOutputFile().set( 37 | task.getProject().getLayout().getBuildDirectory() 38 | .file(binary.getBaseName().map(fileName -> "tmp/" + fileName + ".adoc"))); 39 | }); 40 | binary.getIndexPageFile().convention(generateSamplePage.flatMap(GenerateSamplePageAsciidoc::getOutputFile)); 41 | } 42 | 43 | public static void createTasksForContentBinary(TaskContainer tasks, ViewableContentBinary binary) { 44 | tasks.register(binary.getViewTaskName(), ViewDocumentation.class, task -> { 45 | task.setGroup(DOCUMENTATION_GROUP_NAME); 46 | task.setDescription("Open in the browser the rendered documentation"); 47 | task.getIndexFile().convention(binary.getViewablePageFile()); 48 | }); 49 | } 50 | 51 | public static void createCheckTasksForContentBinary(TaskContainer tasks, TestableRenderedContentLinksBinary binary, TaskProvider check) { 52 | TaskProvider checkLinksTask = tasks.register(binary.getCheckLinksTaskName(), CheckLinks.class, task -> { 53 | task.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP); 54 | task.setDescription("Check for any dead link in the rendered documentation"); 55 | task.getIndexDocument().convention(binary.getRenderedPageFile()); 56 | task.getTimeout().convention(Duration.ofMinutes(30)); 57 | }); 58 | 59 | check.configure(it -> it.dependsOn(checkLinksTask)); 60 | } 61 | 62 | public static void createCheckTaskForAsciidoctorContentBinary(Project project, String taskName, Collection binaries, TaskProvider check) { 63 | Configuration configuration = project.getConfigurations().create(taskName); 64 | DependencyHandler dependencies = project.getDependencies(); 65 | dependencies.add(configuration.getName(), "org.gradle:gradle-tooling-api:6.0.1"); 66 | dependencies.add(configuration.getName(), "org.apache.commons:commons-lang3:3.9"); 67 | dependencies.add(configuration.getName(), "org.gradle.exemplar:samples-check:1.0.3"); 68 | dependencies.add(configuration.getName(), "junit:junit:4.12"); 69 | 70 | TaskProvider asciidoctorContentDocsTest = project.getTasks().register(taskName, AsciidoctorContentTest.class, task -> { 71 | task.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP); 72 | task.setDescription("Check Asciidoctor content steps commands."); 73 | task.getClasspath().from(configuration); 74 | task.getGradleVersion().convention(project.getGradle().getGradleVersion()); 75 | task.getDefaultConsoleType().convention(taskName.contains("Sample") ? AsciidoctorContentTestConsoleType.RICH : AsciidoctorContentTestConsoleType.PLAIN); 76 | binaries.forEach(binary -> task.testCase(testCase -> { 77 | testCase.getContentFile().set(binary.getContentFile()); 78 | testCase.getStartingSample().set(binary.getStartingSampleDirectory()); 79 | })); 80 | }); 81 | 82 | check.configure(it -> it.dependsOn(asciidoctorContentDocsTest)); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/internal/exemplar/AnsiCharactersToPlainTextOutputStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradle.docs.internal.exemplar; 18 | 19 | import net.rubygrapefruit.ansi.AnsiParser; 20 | import net.rubygrapefruit.ansi.console.AnsiConsole; 21 | import net.rubygrapefruit.ansi.token.NewLine; 22 | import net.rubygrapefruit.ansi.token.Text; 23 | 24 | import java.io.IOException; 25 | import java.io.OutputStream; 26 | 27 | public class AnsiCharactersToPlainTextOutputStream extends OutputStream { 28 | private final OutputStream delegate; 29 | private final AnsiConsole console; 30 | 31 | public AnsiCharactersToPlainTextOutputStream() { 32 | AnsiParser parser = new AnsiParser(); 33 | 34 | this.console = new AnsiConsole(); 35 | this.delegate = parser.newParser("utf-8", console); 36 | } 37 | 38 | @Override 39 | public void write(int b) throws IOException { 40 | delegate.write(b); 41 | } 42 | 43 | @Override 44 | public void write(byte[] b) throws IOException { 45 | delegate.write(b); 46 | } 47 | 48 | @Override 49 | public void write(byte[] b, int off, int len) throws IOException { 50 | delegate.write(b, off, len); 51 | } 52 | 53 | @Override 54 | public void flush() throws IOException { 55 | delegate.flush(); 56 | } 57 | 58 | @Override 59 | public void close() throws IOException { 60 | delegate.flush(); 61 | } 62 | 63 | @Override 64 | public String toString() { 65 | StringBuilder result = new StringBuilder(); 66 | console.contents(token -> { 67 | if (token instanceof Text) { 68 | result.append(((Text) token).getText()); 69 | } else if (token instanceof NewLine) { 70 | result.append("\n"); 71 | } 72 | }); 73 | return result.toString(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/internal/exemplar/AsciidoctorContentTest.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.internal.exemplar; 2 | 3 | import org.gradle.api.Action; 4 | import org.gradle.api.DefaultTask; 5 | import org.gradle.api.file.ConfigurableFileCollection; 6 | import org.gradle.api.model.ObjectFactory; 7 | import org.gradle.api.provider.Property; 8 | import org.gradle.api.tasks.Classpath; 9 | import org.gradle.api.tasks.Input; 10 | import org.gradle.api.tasks.Internal; 11 | import org.gradle.api.tasks.Nested; 12 | import org.gradle.api.tasks.Optional; 13 | import org.gradle.api.tasks.TaskAction; 14 | import org.gradle.workers.WorkQueue; 15 | import org.gradle.workers.WorkerExecutor; 16 | 17 | import javax.inject.Inject; 18 | import java.io.File; 19 | import java.io.IOException; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | public abstract class AsciidoctorContentTest extends DefaultTask { 24 | private final List testCases = new ArrayList<>(); 25 | 26 | @Nested 27 | protected List getTestCases() { 28 | return testCases; 29 | } 30 | 31 | public void testCase(Action action) { 32 | AsciidoctorContentTestCase testCase = getObjectFactory().newInstance(AsciidoctorContentTestCase.class); 33 | action.execute(testCase); 34 | testCases.add(testCase); 35 | } 36 | 37 | @Inject 38 | protected ObjectFactory getObjectFactory() { 39 | throw new UnsupportedOperationException(); 40 | } 41 | 42 | @Classpath 43 | public abstract ConfigurableFileCollection getClasspath(); 44 | 45 | /** 46 | * Note: The default console type doesn't affect the console choice requireing user interaction like `init` task or `--scan` flag 47 | * @return a property for configuring the console type to use as defined by {@link AsciidoctorContentTestConsoleType} 48 | */ 49 | @Input @Optional 50 | public abstract Property getDefaultConsoleType(); 51 | 52 | @Internal 53 | public abstract Property getGradleVersion(); 54 | 55 | @Inject 56 | protected abstract WorkerExecutor getWorkerExecutor(); 57 | 58 | @TaskAction 59 | public void doTest() throws IOException { 60 | WorkQueue workQueue = getWorkerExecutor().classLoaderIsolation(spec -> spec.getClasspath().from(getClasspath())); 61 | 62 | workQueue.submit(AsciidoctorContentTestWorkerAction.class, parameter -> { 63 | parameter.getTestCases().set(getTestCases()); 64 | parameter.getWorkspaceDirectory().set(getTemporaryDir()); 65 | parameter.getGradleUserHomeDirectory().set(new File(getTemporaryDir(), "gradle-user-home")); 66 | parameter.getGradleVersion().set(getGradleVersion()); 67 | parameter.getDefaultConsoleType().set(getDefaultConsoleType()); 68 | }); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/internal/exemplar/AsciidoctorContentTestCase.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.internal.exemplar; 2 | 3 | import org.gradle.api.file.DirectoryProperty; 4 | import org.gradle.api.file.RegularFileProperty; 5 | import org.gradle.api.tasks.InputDirectory; 6 | import org.gradle.api.tasks.InputFile; 7 | import org.gradle.api.tasks.Optional; 8 | 9 | public interface AsciidoctorContentTestCase { 10 | @InputDirectory @Optional 11 | DirectoryProperty getStartingSample(); 12 | 13 | @InputFile 14 | RegularFileProperty getContentFile(); 15 | } 16 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/internal/exemplar/AsciidoctorContentTestConsoleType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradle.docs.internal.exemplar; 18 | 19 | public enum AsciidoctorContentTestConsoleType { 20 | PLAIN, VERBOSE, RICH 21 | } 22 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/internal/exemplar/AsciidoctorContentTestParameters.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.internal.exemplar; 2 | 3 | import org.gradle.api.file.DirectoryProperty; 4 | import org.gradle.api.provider.ListProperty; 5 | import org.gradle.api.provider.Property; 6 | import org.gradle.workers.WorkParameters; 7 | 8 | public interface AsciidoctorContentTestParameters extends WorkParameters { 9 | ListProperty getTestCases(); 10 | 11 | DirectoryProperty getWorkspaceDirectory(); 12 | 13 | DirectoryProperty getGradleUserHomeDirectory(); 14 | 15 | Property getGradleVersion(); 16 | 17 | Property getDefaultConsoleType(); 18 | } 19 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/internal/exemplar/GradleUserHomePathOutputNormalizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradle.docs.internal.exemplar; 18 | 19 | import org.gradle.exemplar.executor.ExecutionMetadata; 20 | import org.gradle.exemplar.test.normalizer.OutputNormalizer; 21 | 22 | import java.io.File; 23 | 24 | public class GradleUserHomePathOutputNormalizer implements OutputNormalizer { 25 | private final File gradleUserHomeDirectory; 26 | 27 | public GradleUserHomePathOutputNormalizer(File gradleUserHomeDirectory) { 28 | this.gradleUserHomeDirectory = gradleUserHomeDirectory; 29 | } 30 | 31 | @Override 32 | public String normalize(String s, ExecutionMetadata executionMetadata) { 33 | return s.replace(gradleUserHomeDirectory.getAbsolutePath(), "/home/user/.gradle"); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/internal/exemplar/OutputNormalizers.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.internal.exemplar; 2 | 3 | import org.gradle.exemplar.test.normalizer.OutputNormalizer; 4 | 5 | import java.util.function.UnaryOperator; 6 | 7 | public class OutputNormalizers { 8 | @SafeVarargs 9 | public static OutputNormalizer composite(OutputNormalizer... normalizers) { 10 | return (commandOutput, executionMetadata) -> { 11 | for (OutputNormalizer normalizer : normalizers) { 12 | commandOutput = normalizer.normalize(commandOutput, executionMetadata); 13 | } 14 | return commandOutput; 15 | }; 16 | } 17 | 18 | public static UnaryOperator toFunctional(OutputNormalizer normalizer) { 19 | return s -> normalizer.normalize(s, null); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/internal/exemplar/UserInputOutputVerifier.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.internal.exemplar; 2 | 3 | import org.gradle.exemplar.test.verifier.OutputVerifier; 4 | 5 | import java.util.Arrays; 6 | import java.util.List; 7 | 8 | import static org.junit.Assert.assertTrue; 9 | import static org.junit.Assert.fail; 10 | 11 | public class UserInputOutputVerifier implements OutputVerifier { 12 | private final List actualUserInputs; 13 | 14 | public UserInputOutputVerifier(List actualUserInputs) { 15 | this.actualUserInputs = actualUserInputs; 16 | } 17 | 18 | public void verify(String expected, String actual, boolean allowAdditionalOutput) { 19 | List expectedLines = Arrays.asList(expected.split("\\r?\\n")); 20 | List actualLines = Arrays.asList(actual.split("\\r?\\n")); 21 | int expectedIndex = 0; 22 | int actualIndex = 0; 23 | int userInputIndex = 0; 24 | if (allowAdditionalOutput) { 25 | actualIndex = this.findFirstMatchingLine(actualLines, expectedLines.get(expectedIndex)); 26 | } 27 | 28 | while(actualIndex < actualLines.size() && expectedIndex < expectedLines.size()) { 29 | String expectedLine = expectedLines.get(expectedIndex); 30 | String actualLine = actualLines.get(actualIndex); 31 | if (isAskingQuestionToUser(expectedLine, actualLine)) { 32 | String expectedUserInput = expectedLine.substring(actualLine.length()).trim(); 33 | 34 | if (expectedUserInput.equals(actualUserInputs.get(userInputIndex))) { 35 | userInputIndex++; 36 | 37 | // Ensure the new line is empty demonstrating an user input 38 | assertTrue(expectedLines.get(++expectedIndex).isEmpty()); 39 | } else { 40 | fail(String.format("Unexpected value at line %d.%nExpected: %s%nActual: %s%nActual output:%n%s%n", actualIndex + 1, expectedLine, actualLine, actual)); 41 | } 42 | } else if (!expectedLine.equals(actualLine)) { 43 | fail(String.format("Unexpected value at line %d.%nExpected: %s%nActual: %s%nActual output:%n%s%n", actualIndex + 1, expectedLine, actualLine, actual)); 44 | } 45 | 46 | ++actualIndex; 47 | ++expectedIndex; 48 | } 49 | 50 | if (actualIndex == actualLines.size() && expectedIndex < expectedLines.size()) { 51 | fail(String.format("Lines missing from actual result, starting at expected line %d.%nExpected: %s%nActual output:%n%s%n", expectedIndex, expectedLines.get(expectedIndex), actual)); 52 | } 53 | 54 | if (!allowAdditionalOutput && actualIndex < actualLines.size() && expectedIndex == expectedLines.size()) { 55 | fail(String.format("Extra lines in actual result, starting at line %d.%nActual: %s%nActual output:%n%s%n", actualIndex + 1, actualLines.get(actualIndex), actual)); 56 | } 57 | 58 | } 59 | 60 | private boolean isAskingQuestionToUser(String expectedLine, String actualLine) { 61 | // NOTE: This is very opinionated to build init user interaction. 62 | if ((actualLine.startsWith("Enter selection") || actualLine.startsWith("Project name") || actualLine.startsWith("Source package")) && expectedLine.startsWith(actualLine)) { 63 | return true; 64 | 65 | // NOTE: This will detect user input where expected lines contains an explicit user input. User input that use the default suggestion will not be detected and requires the previous, opinionated, check to be detected. 66 | } else if (!expectedLine.equals(actualLine) && expectedLine.startsWith(actualLine)) { 67 | return true; 68 | } 69 | 70 | // No user input detected 71 | return false; 72 | } 73 | 74 | private int findFirstMatchingLine(List actualLines, String expected) { 75 | for(int index = 0; index < actualLines.size(); ++index) { 76 | if (actualLines.get(index).equals(expected)) { 77 | return index; 78 | } 79 | } 80 | 81 | return actualLines.size(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/internal/tasks/CheckLinks.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradle.docs.internal.tasks; 18 | 19 | import org.gradle.api.DefaultTask; 20 | import org.gradle.api.GradleException; 21 | import org.gradle.api.file.RegularFileProperty; 22 | import org.gradle.api.logging.Logging; 23 | import org.gradle.api.tasks.InputFile; 24 | import org.gradle.api.tasks.TaskAction; 25 | import org.gradle.workers.WorkAction; 26 | import org.gradle.workers.WorkParameters; 27 | import org.gradle.workers.WorkQueue; 28 | import org.gradle.workers.WorkerExecutor; 29 | import org.jsoup.Jsoup; 30 | import org.jsoup.nodes.Document; 31 | import org.slf4j.Logger; 32 | 33 | import javax.inject.Inject; 34 | import java.io.IOException; 35 | import java.net.HttpURLConnection; 36 | import java.net.URI; 37 | import java.net.URISyntaxException; 38 | import java.net.URLConnection; 39 | import java.util.HashSet; 40 | import java.util.Set; 41 | import java.util.stream.Collectors; 42 | 43 | /** 44 | * 45 | */ 46 | public abstract class CheckLinks extends DefaultTask { 47 | 48 | @InputFile 49 | public abstract RegularFileProperty getIndexDocument(); 50 | 51 | @Inject 52 | public abstract WorkerExecutor getWorkerExecuter(); 53 | 54 | @TaskAction 55 | public void exec() { 56 | WorkQueue queue = getWorkerExecuter().noIsolation(); 57 | queue.submit(CheckLinksAction.class, params -> params.getIndexDocument().set(getIndexDocument())); 58 | } 59 | 60 | public interface CheckLinksParameters extends WorkParameters { 61 | RegularFileProperty getIndexDocument(); 62 | } 63 | 64 | public static abstract class CheckLinksAction implements WorkAction { 65 | private static final Logger logger = Logging.getLogger(CheckLinksAction.class); 66 | 67 | @Override 68 | public void execute() { 69 | try { 70 | Set failures = new HashSet<>(); 71 | URI documentUri = getParameters().getIndexDocument().get().getAsFile().toURI(); 72 | URLConnection connection = documentUri.toURL().openConnection(); 73 | connection.addRequestProperty("User-Agent", "Non empty"); 74 | 75 | String html = new String(connection.getInputStream().readAllBytes()); 76 | getAnchors(html).forEach(anchor -> { 77 | if (anchor.isAbsolute()) { 78 | if (anchor.getScheme().startsWith("http")) { 79 | if (!isValid(anchor)) { 80 | failures.add(anchor); 81 | } 82 | } else { 83 | logger.debug("SKIPPED (Not http/s): {}", anchor); 84 | } 85 | } else { 86 | logger.debug("SKIPPED (relative): {}", anchor); 87 | } 88 | }); 89 | 90 | if (!failures.isEmpty()) { 91 | throw new GradleException("The following links are broken:\n " + failures.stream().map(URI::toString).collect(Collectors.joining("\n")) + "\n"); 92 | } 93 | } catch (IOException e) { 94 | throw new RuntimeException(e); 95 | } 96 | } 97 | 98 | private boolean isValid(URI anchor) { 99 | for (int i=0; i<3; i++) { 100 | try { 101 | HttpURLConnection con = (HttpURLConnection) anchor.toURL().openConnection(); 102 | con.setInstanceFollowRedirects(true); 103 | con.setRequestMethod("HEAD"); 104 | // Fake being a browser 105 | con.addRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0"); 106 | // timeout in 5 seconds 107 | con.setConnectTimeout(5000); 108 | con.setReadTimeout(5000); 109 | int responseCode = con.getResponseCode(); 110 | logger.info("RESPONSE: {} = {}", anchor, responseCode); 111 | return true; 112 | } catch (IOException e) { 113 | logger.error("FAILED: {}", anchor, e); 114 | // https://github.com/gradle/gradle-private/issues/3109 115 | // Server is accessible, but we don't keep sessions 116 | if(e.getMessage().contains("Server redirected too many")) { 117 | return true; 118 | } 119 | } 120 | } 121 | return false; 122 | } 123 | } 124 | 125 | /** 126 | * Extracts all anchor href URIs from an HTML document. 127 | * 128 | * @param html The HTML content to parse 129 | * @return A set of URIs found in anchor tags 130 | */ 131 | public static Set getAnchors(String html) { 132 | Document doc = Jsoup.parse(html); 133 | 134 | return doc.select("a[href]").stream() 135 | .map(element -> { 136 | try { 137 | return new URI(element.attr("href")); 138 | } catch (URISyntaxException ex) { 139 | throw new RuntimeException(ex); 140 | } 141 | }) 142 | .collect(Collectors.toSet()); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/internal/tasks/ViewDocumentation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradle.docs.internal.tasks; 18 | 19 | import org.gradle.api.DefaultTask; 20 | import org.gradle.api.file.RegularFileProperty; 21 | import org.gradle.api.specs.Specs; 22 | import org.gradle.api.tasks.InputFile; 23 | import org.gradle.api.tasks.TaskAction; 24 | 25 | import javax.inject.Inject; 26 | import java.awt.*; 27 | import java.io.IOException; 28 | 29 | public abstract class ViewDocumentation extends DefaultTask { 30 | 31 | @InputFile 32 | public abstract RegularFileProperty getIndexFile(); 33 | 34 | @Inject 35 | public ViewDocumentation() { 36 | getOutputs().upToDateWhen(Specs.satisfyNone()); 37 | } 38 | 39 | @TaskAction 40 | void action() throws IOException { 41 | Desktop.getDesktop().open(getIndexFile().get().getAsFile()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/samples/Dsl.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples; 2 | 3 | import java.util.Locale; 4 | 5 | public enum Dsl { 6 | GROOVY("Groovy"), KOTLIN("Kotlin"); 7 | 8 | private final String displayName; 9 | 10 | Dsl(String displayName) { 11 | this.displayName = displayName; 12 | } 13 | 14 | public String getDisplayName() { 15 | return displayName; 16 | } 17 | 18 | public String getConventionalDirectory() { 19 | return name().toLowerCase(Locale.ENGLISH); 20 | } 21 | 22 | public String getDslLabel() { 23 | return getConventionalDirectory() + "-dsl"; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/samples/Sample.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples; 2 | 3 | import org.gradle.api.Action; 4 | import org.gradle.api.Named; 5 | import org.gradle.api.file.ConfigurableFileCollection; 6 | import org.gradle.api.file.DirectoryProperty; 7 | import org.gradle.api.file.RegularFileProperty; 8 | 9 | /** 10 | * Represent a sample to be documented. Each sample must contain at least a Groovy or Kotlin DSL sample. 11 | */ 12 | public interface Sample extends Named, SampleSummary { 13 | /** 14 | * By convention, this is the sample name off the extension's sample root directory. 15 | * 16 | * @return Property for configuring the sample root directory. 17 | */ 18 | DirectoryProperty getSampleDirectory(); 19 | 20 | /** 21 | * By Convention, this is README.adoc within the sample directory. 22 | * 23 | * @return Property for configuring the readme file for the sample in Asciidoctor format. 24 | */ 25 | RegularFileProperty getReadmeFile(); 26 | 27 | /** 28 | * @return Sample content that is shared by all DSLs. 29 | * 30 | * By convention, this is the wrapper files and LICENSE. 31 | */ 32 | ConfigurableFileCollection getCommonContent(); 33 | 34 | /** 35 | * Configure common content. 36 | * 37 | * @param action configuration action 38 | */ 39 | void common(Action action); 40 | 41 | /** 42 | * By convention, this is the "groovy" directory in the sample directory. 43 | * 44 | * @return Sample content that is used for Groovy DSL. 45 | */ 46 | ConfigurableFileCollection getGroovyContent(); 47 | 48 | /** 49 | * Configure Groovy content. 50 | * 51 | * @param action configuration action 52 | */ 53 | void groovy(Action action); 54 | 55 | /** 56 | * By convention, this is the "kotlin" directory in the sample directory. 57 | * 58 | * @return Sample content that is used for Kotlin DSL. 59 | */ 60 | ConfigurableFileCollection getKotlinContent(); 61 | 62 | /** 63 | * Configure Kotlin content. 64 | * 65 | * @param action configuration action 66 | */ 67 | void kotlin(Action action); 68 | 69 | /** 70 | * @return Sample content that is used for Exemplar testing 71 | */ 72 | ConfigurableFileCollection getTestsContent(); 73 | 74 | /** 75 | * Configure testing content. 76 | * 77 | * @param action configuration action 78 | */ 79 | void tests(Action action); 80 | } 81 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/samples/SampleSummary.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples; 2 | 3 | import org.gradle.api.provider.Property; 4 | import org.gradle.api.provider.SetProperty; 5 | import org.gradle.api.tasks.Input; 6 | 7 | /** 8 | * 9 | */ 10 | public interface SampleSummary { 11 | /** 12 | * @return Property for configuring the sample description. The description is used within the sample index. 13 | */ 14 | @Input 15 | Property getDescription(); 16 | 17 | /** 18 | * @return Property for configuring the sample display name. The display name is used within the sample index. 19 | */ 20 | @Input 21 | Property getDisplayName(); 22 | 23 | /** 24 | * @return Property for configuring the sample documentation name. This is the name other parts of the documentation would use to refer to the sample. 25 | */ 26 | @Input 27 | Property getSampleDocName(); 28 | 29 | /** 30 | * @return Property for configuring the category this sample appears in. 31 | */ 32 | @Input 33 | Property getCategory(); 34 | 35 | /** 36 | * By convention, this is both Groovy and Kotlin. 37 | * Every sample must have at least one DSL. 38 | * 39 | * @return DSLs that should be expected for this sample. 40 | */ 41 | @Input 42 | SetProperty getDsls(); 43 | 44 | /** 45 | * By Convention this is true. 46 | * 47 | * @return true if the samples should be listed in the samples index. 48 | */ 49 | @Input 50 | Property getPromoted(); 51 | } 52 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/samples/Samples.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples; 2 | 3 | import org.gradle.api.NamedDomainObjectContainer; 4 | import org.gradle.api.file.DirectoryProperty; 5 | import org.gradle.api.file.RegularFileProperty; 6 | 7 | public interface Samples { 8 | 9 | /** 10 | * By convention, this is src/samples 11 | * 12 | * @return The root sample directory. 13 | */ 14 | DirectoryProperty getSamplesRoot(); 15 | 16 | /** 17 | * By convention, this is src/samples/templates 18 | * 19 | * @return The root template directory. 20 | */ 21 | DirectoryProperty getTemplatesRoot(); 22 | 23 | /** 24 | * This is an asciidoc file, not the generated HTML. 25 | * 26 | * By convention, this is documentationRoot/index.adoc 27 | * 28 | * @return The generated samples index file. 29 | */ 30 | RegularFileProperty getSampleIndexFile(); 31 | 32 | /** 33 | * @return Buildable elements of all the available samples. 34 | */ 35 | SamplesDistribution getDistribution(); 36 | 37 | /** 38 | * @return Container of all published samples. This is the primary configuration point for all samples. 39 | */ 40 | NamedDomainObjectContainer getPublishedSamples(); 41 | 42 | /** 43 | * @return Container of all templates. Templates are reusable parts of a sample. 44 | */ 45 | NamedDomainObjectContainer getTemplates(); 46 | } 47 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/samples/SamplesDistribution.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples; 2 | 3 | import org.gradle.api.file.ConfigurableFileCollection; 4 | import org.gradle.api.file.ConfigurableFileTree; 5 | 6 | /** 7 | * This represents the outputs of the samples section of the documentation. 8 | */ 9 | public interface SamplesDistribution { 10 | 11 | /** 12 | * @return the collection of samples extracted as a consumer would see them 13 | */ 14 | ConfigurableFileTree getInstalledSamples(); 15 | 16 | /** 17 | * @return a collection of samples in zip form 18 | */ 19 | ConfigurableFileCollection getZippedSamples(); 20 | 21 | /** 22 | * @return Collection of samples ready for testing 23 | */ 24 | ConfigurableFileTree getTestedInstalledSamples(); 25 | 26 | /** 27 | * This is HTML and resources. 28 | * 29 | * @return collection of rendered documentation 30 | */ 31 | ConfigurableFileCollection getRenderedDocumentation(); 32 | } 33 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/samples/Template.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples; 2 | 3 | import org.gradle.api.Named; 4 | import org.gradle.api.file.DirectoryProperty; 5 | import org.gradle.api.provider.Property; 6 | 7 | public interface Template extends Named { 8 | /** 9 | * @return Source directory of the template 10 | */ 11 | DirectoryProperty getSourceDirectory(); 12 | 13 | /** 14 | * @return subdirectory to place the template in 15 | */ 16 | Property getTarget(); 17 | 18 | /** 19 | * @return destination directory for the template 20 | */ 21 | DirectoryProperty getTemplateDirectory(); 22 | } 23 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/samples/internal/LegacySamplesDocumentationPlugin.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples.internal; 2 | 3 | import org.gradle.api.Plugin; 4 | import org.gradle.api.Project; 5 | import org.gradle.docs.internal.DocumentationExtensionInternal; 6 | import org.gradle.docs.samples.Samples; 7 | 8 | @SuppressWarnings("UnstableApiUsage") 9 | public class LegacySamplesDocumentationPlugin implements Plugin { 10 | @Override 11 | public void apply(Project project) { 12 | project.getPluginManager().apply(SamplesDocumentationPlugin.class); 13 | 14 | // Register a samples extension to configure published samples 15 | SamplesInternal extension = project.getExtensions().getByType(DocumentationExtensionInternal.class).getSamples(); 16 | 17 | project.getExtensions().add(Samples.class, "samples", extension); 18 | extension.getSamplesRoot().set(project.getLayout().getProjectDirectory().dir("src/samples")); 19 | extension.getTemplatesRoot().convention(project.getLayout().getProjectDirectory().dir("src/samples/templates")); 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/samples/internal/SampleArchiveBinary.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples.internal; 2 | 3 | import org.gradle.api.file.RegularFileProperty; 4 | import org.gradle.api.provider.Property; 5 | import org.gradle.docs.samples.Dsl; 6 | 7 | import javax.inject.Inject; 8 | 9 | /** 10 | * Represents a sample tailored for a particular DSL. 11 | */ 12 | public abstract class SampleArchiveBinary extends SampleInstallBinary { 13 | @Inject 14 | public SampleArchiveBinary(String name) { 15 | super(name); 16 | } 17 | 18 | /** 19 | * @return The language this sample is written for 20 | */ 21 | public abstract Property getDsl(); 22 | 23 | /** 24 | * @return Gets the validation report for this sample. 25 | */ 26 | public abstract RegularFileProperty getValidationReport(); 27 | 28 | /** 29 | * @return A zip containing this sample. This is the primary thing produced by a sample for a given language. 30 | */ 31 | public abstract RegularFileProperty getZipFile(); 32 | } 33 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/samples/internal/SampleBinary.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples.internal; 2 | 3 | import org.gradle.api.Named; 4 | 5 | import javax.inject.Inject; 6 | 7 | public abstract class SampleBinary implements Named { 8 | private final String name; 9 | 10 | @Inject 11 | public SampleBinary(String name) { 12 | this.name = name; 13 | } 14 | 15 | @Override 16 | public String getName() { 17 | return name; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/samples/internal/SampleContentBinary.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples.internal; 2 | 3 | import org.gradle.api.file.DirectoryProperty; 4 | import org.gradle.api.file.RegularFileProperty; 5 | import org.gradle.api.provider.Property; 6 | import org.gradle.docs.internal.RenderableContentBinary; 7 | import org.gradle.docs.internal.TestableRenderedContentLinksBinary; 8 | import org.gradle.docs.internal.ViewableContentBinary; 9 | import org.gradle.docs.samples.SampleSummary; 10 | 11 | import javax.inject.Inject; 12 | 13 | import static org.gradle.docs.internal.StringUtils.capitalize; 14 | 15 | public abstract class SampleContentBinary extends SampleBinary implements ViewableContentBinary, TestableRenderedContentLinksBinary, RenderableContentBinary { 16 | @Inject 17 | public SampleContentBinary(String name) { 18 | super(name); 19 | } 20 | 21 | public abstract Property getDisplayName(); 22 | 23 | public abstract DirectoryProperty getSampleDirectory(); 24 | 25 | public abstract Property getBaseName(); 26 | 27 | public abstract Property getRenderedPermalink(); 28 | 29 | public abstract Property getSourcePermalink(); 30 | 31 | public abstract RegularFileProperty getIndexPageFile(); 32 | 33 | public abstract RegularFileProperty getRenderedIndexPageFile(); 34 | 35 | public abstract Property getSummary(); 36 | 37 | public abstract DirectoryProperty getSampleInstallDirectory(); 38 | 39 | @Override 40 | public String getViewTaskName() { 41 | return "view" + capitalize(getName()) + "Sample"; 42 | } 43 | 44 | @Override 45 | public String getCheckLinksTaskName() { 46 | return "check" + capitalize(getName()) + "SampleLinks"; 47 | } 48 | 49 | public abstract RegularFileProperty getSourcePageFile(); 50 | 51 | public abstract RegularFileProperty getInstalledIndexPageFile(); 52 | 53 | public abstract Property getGradleVersion(); 54 | } 55 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/samples/internal/SampleExemplarBinary.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples.internal; 2 | 3 | import org.gradle.api.file.ConfigurableFileCollection; 4 | import org.gradle.api.file.DirectoryProperty; 5 | import org.gradle.api.provider.Property; 6 | 7 | import javax.inject.Inject; 8 | 9 | public abstract class SampleExemplarBinary extends SampleBinary { 10 | @Inject 11 | public SampleExemplarBinary(String name) { 12 | super(name); 13 | } 14 | 15 | public abstract DirectoryProperty getTestedInstallDirectory(); 16 | 17 | public abstract DirectoryProperty getTestedWorkingDirectory(); 18 | 19 | public abstract ConfigurableFileCollection getTestsContent(); 20 | 21 | public abstract Property getExplicitSanityCheck(); 22 | } 23 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/samples/internal/SampleInstallBinary.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples.internal; 2 | 3 | import org.gradle.api.file.ConfigurableFileCollection; 4 | import org.gradle.api.file.DirectoryProperty; 5 | import org.gradle.api.provider.ListProperty; 6 | import org.gradle.api.provider.Property; 7 | 8 | import javax.inject.Inject; 9 | 10 | public abstract class SampleInstallBinary extends SampleBinary { 11 | @Inject 12 | public SampleInstallBinary(String name) { 13 | super(name); 14 | } 15 | 16 | /** 17 | * @return Working directory used by the plugin to expose an assembled sample to consumers. 18 | */ 19 | public abstract DirectoryProperty getWorkingDirectory(); 20 | 21 | /** 22 | * @return The documentation page for this sample 23 | */ 24 | public abstract Property getSampleLinkName(); 25 | 26 | /** 27 | * @return All content to include in the sample. 28 | */ 29 | public abstract ConfigurableFileCollection getContent(); 30 | 31 | /** 32 | * @return Content that is specific to the DSL language. 33 | */ 34 | public abstract ConfigurableFileCollection getDslSpecificContent(); 35 | 36 | /** 37 | * @return Exclude patterns for files included in this sample 38 | */ 39 | public abstract ListProperty getExcludes(); 40 | 41 | /** 42 | * @return A installation directory containing this sample. This can be used to get an installed version of the sample. 43 | */ 44 | public abstract DirectoryProperty getInstallDirectory(); 45 | } 46 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/samples/internal/SampleInternal.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples.internal; 2 | 3 | import org.gradle.api.Action; 4 | import org.gradle.api.Task; 5 | import org.gradle.api.file.ConfigurableFileCollection; 6 | import org.gradle.api.file.DirectoryProperty; 7 | import org.gradle.api.tasks.TaskContainer; 8 | import org.gradle.api.tasks.TaskProvider; 9 | import org.gradle.docs.samples.Sample; 10 | 11 | import javax.inject.Inject; 12 | 13 | import static org.gradle.docs.internal.StringUtils.capitalize; 14 | 15 | public abstract class SampleInternal implements Sample { 16 | private final String name; 17 | private final TaskProvider assemble; 18 | private final TaskProvider check; 19 | 20 | @Inject 21 | public SampleInternal(String name, TaskContainer tasks) { 22 | this.name = name; 23 | this.assemble = tasks.register("assemble" + capitalize(name + "Sample")); 24 | this.check = tasks.register("check" + capitalize(name + "Sample")); 25 | } 26 | 27 | @Override 28 | public String getName() { 29 | return name; 30 | } 31 | 32 | @Override 33 | public void common(Action action) { 34 | action.execute(getCommonContent()); 35 | } 36 | @Override 37 | public void groovy(Action action) { 38 | action.execute(getGroovyContent()); 39 | } 40 | @Override 41 | public void kotlin(Action action) { 42 | action.execute(getKotlinContent()); 43 | } 44 | @Override 45 | public void tests(Action action) { 46 | action.execute(getTestsContent()); 47 | } 48 | 49 | /** 50 | * @return Lifecycle task for assembling this sample. 51 | */ 52 | public TaskProvider getAssembleTask() { 53 | return assemble; 54 | } 55 | 56 | /** 57 | * @return Lifecycle task for checking this sample. 58 | */ 59 | public TaskProvider getCheckTask() { 60 | return check; 61 | } 62 | 63 | /** 64 | * @return Root installation directory for each DSL. 65 | */ 66 | public abstract DirectoryProperty getInstallDirectory(); 67 | } 68 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/samples/internal/SamplesInternal.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples.internal; 2 | 3 | import org.gradle.api.DomainObjectSet; 4 | import org.gradle.api.NamedDomainObjectContainer; 5 | import org.gradle.api.file.DirectoryProperty; 6 | import org.gradle.api.model.ObjectFactory; 7 | import org.gradle.docs.samples.Samples; 8 | import org.gradle.docs.samples.SamplesDistribution; 9 | 10 | import javax.inject.Inject; 11 | 12 | public abstract class SamplesInternal implements Samples { 13 | private final NamedDomainObjectContainer publishedSamples; 14 | private final DomainObjectSet binaries; 15 | private final NamedDomainObjectContainer templates; 16 | private final SamplesDistribution distribution; 17 | 18 | @Inject 19 | public SamplesInternal(ObjectFactory objectFactory) { 20 | this.publishedSamples = objectFactory.domainObjectContainer(SampleInternal.class, name -> objectFactory.newInstance(SampleInternal.class, name)); 21 | this.binaries = objectFactory.domainObjectSet(SampleBinary.class); 22 | this.templates = objectFactory.domainObjectContainer(TemplateInternal.class, name -> objectFactory.newInstance(TemplateInternal.class, name)); 23 | this.distribution = objectFactory.newInstance(SamplesDistribution.class); 24 | } 25 | 26 | @Override 27 | public NamedDomainObjectContainer getPublishedSamples() { 28 | return publishedSamples; 29 | } 30 | 31 | public DomainObjectSet getBinaries() { 32 | return binaries; 33 | } 34 | 35 | @Override 36 | public NamedDomainObjectContainer getTemplates() { 37 | return templates; 38 | } 39 | 40 | @Override 41 | public SamplesDistribution getDistribution() { 42 | return distribution; 43 | } 44 | 45 | /** 46 | * By convention, this is buildDir/working/samples/install 47 | * 48 | * @return The root install directory. 49 | */ 50 | public abstract DirectoryProperty getInstallRoot(); 51 | 52 | /** 53 | * By convention, this is buildDir/working/samples/testing 54 | * 55 | * @return location of installed samples ready for testing 56 | */ 57 | public abstract DirectoryProperty getTestedInstallRoot(); 58 | 59 | /** 60 | * By convention, this is buildDir/working/samples/docs 61 | * 62 | * @return The root directory for all documentation. 63 | */ 64 | public abstract DirectoryProperty getDocumentationInstallRoot(); 65 | 66 | /** 67 | * By convention, this is buildDir/working/samples/render-samples 68 | * 69 | * @return The root directory for rendered documentation. 70 | */ 71 | public abstract DirectoryProperty getRenderedDocumentationRoot(); 72 | } 73 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/samples/internal/TemplateInternal.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples.internal; 2 | 3 | import org.gradle.api.file.DirectoryProperty; 4 | import org.gradle.docs.samples.Template; 5 | 6 | import javax.inject.Inject; 7 | import java.util.concurrent.Callable; 8 | 9 | public abstract class TemplateInternal implements Template, Callable { 10 | private final String name; 11 | 12 | @Inject 13 | public TemplateInternal(String name) { 14 | this.name = name; 15 | } 16 | 17 | @Override 18 | public String getName() { 19 | return name; 20 | } 21 | 22 | @Override 23 | public DirectoryProperty call() throws Exception { 24 | return getTemplateDirectory(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/samples/internal/TestableAsciidoctorSampleContentBinary.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradle.docs.samples.internal; 18 | 19 | import org.gradle.docs.internal.TestableAsciidoctorContentBinary; 20 | 21 | import javax.inject.Inject; 22 | 23 | public abstract class TestableAsciidoctorSampleContentBinary extends SampleBinary implements TestableAsciidoctorContentBinary { 24 | @Inject 25 | public TestableAsciidoctorSampleContentBinary(String name) { 26 | super(name); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/samples/internal/tasks/GenerateSampleIndexAsciidoc.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples.internal.tasks; 2 | 3 | import org.gradle.api.DefaultTask; 4 | import org.gradle.api.file.RegularFileProperty; 5 | import org.gradle.api.provider.ListProperty; 6 | import org.gradle.api.tasks.Nested; 7 | import org.gradle.api.tasks.OutputFile; 8 | import org.gradle.api.tasks.TaskAction; 9 | import org.gradle.docs.samples.SampleSummary; 10 | 11 | import java.io.FileNotFoundException; 12 | import java.io.PrintWriter; 13 | import java.util.ArrayList; 14 | import java.util.Collections; 15 | import java.util.Comparator; 16 | import java.util.List; 17 | import java.util.Map; 18 | import java.util.TreeMap; 19 | 20 | public abstract class GenerateSampleIndexAsciidoc extends DefaultTask { 21 | @Nested 22 | public abstract ListProperty getSamples(); 23 | 24 | @OutputFile 25 | public abstract RegularFileProperty getOutputFile(); 26 | 27 | @TaskAction 28 | public void doGenerate() throws FileNotFoundException { 29 | try (PrintWriter out = new PrintWriter(getOutputFile().get().getAsFile())) { 30 | out.println("= Sample Index"); 31 | out.println(); 32 | 33 | if (getSamples().get().isEmpty()) { 34 | out.println("No available samples."); 35 | } else { 36 | Map> categorizedSamples = new TreeMap<>(); 37 | getSamples().get().forEach(sample -> { 38 | if (sample.getPromoted().get()) { 39 | String category = sample.getCategory().get(); 40 | List groupedSamples = categorizedSamples.computeIfAbsent(category, k -> new ArrayList<>()); 41 | groupedSamples.add(sample); 42 | } 43 | }); 44 | 45 | categorizedSamples.forEach((category, samples) -> { 46 | samples.sort(Comparator.comparing(sample -> sample.getDisplayName().get())); 47 | out.println("== " + category); 48 | out.println(); 49 | samples.forEach(sample -> { 50 | String description = sample.getDescription().get(); 51 | if (description.isEmpty()) { 52 | out.println(String.format("- <<%s#,%s>>", sample.getSampleDocName().get(), sample.getDisplayName().get())); 53 | } else { 54 | out.println(String.format("- <<%s#,%s>>: %s", sample.getSampleDocName().get(), sample.getDisplayName().get(), description)); 55 | } 56 | }); 57 | out.println(); 58 | }); 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/samples/internal/tasks/GenerateSamplePageAsciidoc.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples.internal.tasks; 2 | 3 | import org.gradle.api.DefaultTask; 4 | import org.gradle.api.UncheckedIOException; 5 | import org.gradle.api.file.RegularFileProperty; 6 | import org.gradle.api.provider.MapProperty; 7 | import org.gradle.api.provider.Property; 8 | import org.gradle.api.tasks.Input; 9 | import org.gradle.api.tasks.InputFile; 10 | import org.gradle.api.tasks.Nested; 11 | import org.gradle.api.tasks.OutputFile; 12 | import org.gradle.api.tasks.TaskAction; 13 | import org.gradle.docs.samples.Dsl; 14 | import org.gradle.docs.samples.SampleSummary; 15 | 16 | import java.io.BufferedOutputStream; 17 | import java.io.File; 18 | import java.io.FileOutputStream; 19 | import java.io.IOException; 20 | import java.nio.file.Files; 21 | import java.nio.file.Path; 22 | import java.util.Map; 23 | import java.util.Set; 24 | import java.util.TreeSet; 25 | 26 | public abstract class GenerateSamplePageAsciidoc extends DefaultTask { 27 | @Input 28 | public abstract MapProperty getAttributes(); 29 | 30 | @Nested 31 | public abstract Property getSampleSummary(); 32 | 33 | @InputFile 34 | public abstract RegularFileProperty getReadmeFile(); 35 | 36 | @OutputFile 37 | public abstract RegularFileProperty getOutputFile(); 38 | 39 | @TaskAction 40 | public void generate() { 41 | // TODO: Add useful information to the readme 42 | File outputFile = getOutputFile().get().getAsFile(); 43 | Path sourceFile = getReadmeFile().get().getAsFile().toPath(); 44 | 45 | try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(outputFile))) { 46 | bos.write(generateHeaderForSamplePage()); 47 | Files.copy(sourceFile, bos); 48 | } catch (IOException e) { 49 | throw new UncheckedIOException(e); 50 | } 51 | } 52 | 53 | private byte[] generateHeaderForSamplePage() { 54 | SampleSummary sampleSummary = getSampleSummary().get(); 55 | String sampleDisplayName = sampleSummary.getDisplayName().get(); 56 | String sampleDocName = sampleSummary.getSampleDocName().get(); 57 | 58 | StringBuilder sb = new StringBuilder(); 59 | writeAttributes(sb); 60 | sb.append("= ").append(sampleDisplayName).append(" Sample\n\n"); 61 | sb.append("[.download]\n"); 62 | Set dsls = new TreeSet<>(sampleSummary.getDsls().get()); 63 | for (Dsl dsl : dsls) { 64 | sb.append(String.format("- link:zips/%s-%s.zip[icon:download[] %s DSL]\n", sampleDocName, dsl.getDslLabel(), dsl.getDisplayName())); 65 | } 66 | sb.append('\n'); 67 | return sb.toString().getBytes(); 68 | } 69 | 70 | private void writeAttributes(StringBuilder sb) { 71 | Map attributes = getAttributes().get(); 72 | 73 | attributes.forEach((key, value) -> sb.append(":").append(key).append(": ").append(value).append("\n")); 74 | sb.append('\n'); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/samples/internal/tasks/GenerateSanityCheckTests.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples.internal.tasks; 2 | 3 | import org.gradle.api.DefaultTask; 4 | import org.gradle.api.file.RegularFileProperty; 5 | import org.gradle.api.tasks.OutputFile; 6 | import org.gradle.api.tasks.TaskAction; 7 | 8 | import java.io.File; 9 | import java.io.FileWriter; 10 | import java.io.IOException; 11 | 12 | public abstract class GenerateSanityCheckTests extends DefaultTask { 13 | @OutputFile 14 | public abstract RegularFileProperty getOutputFile(); 15 | 16 | @TaskAction 17 | public void generate() throws IOException { 18 | File outputFile = getOutputFile().get().getAsFile(); 19 | StringBuilder sb = new StringBuilder(); 20 | sb.append("commands: [{\n"); 21 | sb.append(" executable: gradle\n"); 22 | sb.append(" args: tasks -q\n"); 23 | sb.append("}]\n"); 24 | try (FileWriter fw = new FileWriter(outputFile)) { 25 | fw.write(sb.toString()); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/samples/internal/tasks/GenerateTestSource.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples.internal.tasks; 2 | 3 | import org.gradle.api.DefaultTask; 4 | import org.gradle.api.file.Directory; 5 | import org.gradle.api.file.DirectoryProperty; 6 | import org.gradle.api.tasks.OutputDirectory; 7 | import org.gradle.api.tasks.TaskAction; 8 | 9 | import java.io.IOException; 10 | import java.nio.file.Files; 11 | 12 | public abstract class GenerateTestSource extends DefaultTask { 13 | @OutputDirectory 14 | public abstract DirectoryProperty getOutputDirectory(); 15 | 16 | @TaskAction 17 | public void generate() { 18 | String content = """ 19 | //CHECKSTYLE:OFF 20 | package org.gradle.exemplar; 21 | 22 | import org.gradle.exemplar.test.normalizer.FileSeparatorOutputNormalizer; 23 | import org.gradle.exemplar.test.normalizer.JavaObjectSerializationOutputNormalizer; 24 | import org.gradle.exemplar.test.normalizer.GradleOutputNormalizer; 25 | import org.gradle.exemplar.test.runner.GradleSamplesRunner; 26 | import org.gradle.exemplar.test.runner.SamplesOutputNormalizers; 27 | import org.gradle.exemplar.test.runner.SamplesRoot; 28 | import org.junit.runner.RunWith; 29 | 30 | @RunWith(GradleSamplesRunner.class) 31 | @SamplesOutputNormalizers({ 32 | JavaObjectSerializationOutputNormalizer.class, 33 | FileSeparatorOutputNormalizer.class, 34 | GradleOutputNormalizer.class 35 | }) 36 | public class ExemplarExternalSamplesFunctionalTest {} 37 | """; 38 | try { 39 | Directory sourceDirectory = getOutputDirectory().dir("org/gradle/docs/samples/").get(); 40 | sourceDirectory.getAsFile().mkdirs(); 41 | Files.write(sourceDirectory.file("ExemplarExternalSamplesFunctionalTest.java").getAsFile().toPath(), content.getBytes()); 42 | } catch (IOException e) { 43 | throw new UnsupportedOperationException(e); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/samples/internal/tasks/InstallSample.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples.internal.tasks; 2 | 3 | import org.gradle.api.DefaultTask; 4 | import org.gradle.api.file.ConfigurableFileCollection; 5 | import org.gradle.api.file.DirectoryProperty; 6 | import org.gradle.api.file.FileSystemOperations; 7 | import org.gradle.api.file.FileTree; 8 | import org.gradle.api.provider.ListProperty; 9 | import org.gradle.api.provider.Property; 10 | import org.gradle.api.tasks.Input; 11 | import org.gradle.api.tasks.InputFiles; 12 | import org.gradle.api.tasks.Internal; 13 | import org.gradle.api.tasks.Optional; 14 | import org.gradle.api.tasks.OutputDirectory; 15 | import org.gradle.api.tasks.SkipWhenEmpty; 16 | import org.gradle.api.tasks.TaskAction; 17 | import org.gradle.internal.work.WorkerLeaseService; 18 | 19 | import javax.inject.Inject; 20 | import java.util.Collections; 21 | 22 | /** 23 | * Installs the sample's zip to the given directory. 24 | */ 25 | public abstract class InstallSample extends DefaultTask { 26 | @InputFiles 27 | @SkipWhenEmpty 28 | protected FileTree getSourceAsTree() { 29 | return getSource().getAsFileTree(); 30 | } 31 | 32 | @Internal 33 | public abstract ConfigurableFileCollection getSource(); 34 | 35 | @Input 36 | @Optional 37 | public abstract Property getReadmeName(); 38 | 39 | @Input 40 | @Optional 41 | public abstract ListProperty getExcludes(); 42 | 43 | @OutputDirectory 44 | public abstract DirectoryProperty getInstallDirectory(); 45 | 46 | @Inject 47 | public abstract WorkerLeaseService getWorkerLeaseService(); 48 | 49 | @Inject 50 | protected abstract FileSystemOperations getFs(); 51 | 52 | @TaskAction 53 | public void doInstall() { 54 | // TODO: Use the Worker API instead of releasing lock manually 55 | getWorkerLeaseService().runAsIsolatedTask(() -> { 56 | getFs().sync(spec -> { 57 | spec.from(getSource()); 58 | spec.into(getInstallDirectory()); 59 | spec.exclude(getExcludes().getOrElse(Collections.emptyList())); 60 | spec.rename(getReadmeName().getOrElse("README"), "README"); 61 | }); 62 | }); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/samples/internal/tasks/SyncWithProvider.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples.internal.tasks; 2 | 3 | import org.gradle.api.file.DirectoryProperty; 4 | import org.gradle.api.file.ProjectLayout; 5 | import org.gradle.api.provider.ProviderFactory; 6 | import org.gradle.api.tasks.OutputDirectory; 7 | import org.gradle.api.tasks.Sync; 8 | import org.gradle.internal.work.WorkerLeaseService; 9 | 10 | import javax.inject.Inject; 11 | 12 | // TODO: Upstream this 13 | public abstract class SyncWithProvider extends Sync { 14 | @Inject 15 | public SyncWithProvider(ProjectLayout layout, ProviderFactory providers) { 16 | getDestinationDirectory().set(layout.dir(providers.provider(this::getDestinationDir))); 17 | } 18 | 19 | @Inject 20 | public abstract WorkerLeaseService getWorkerLeaseService(); 21 | 22 | @Override 23 | protected void copy() { 24 | getWorkerLeaseService().runAsIsolatedTask(super::copy); 25 | } 26 | 27 | @OutputDirectory 28 | public abstract DirectoryProperty getDestinationDirectory(); 29 | } 30 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/samples/internal/tasks/ValidateSampleBinary.java: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.samples.internal.tasks; 2 | 3 | import org.gradle.api.DefaultTask; 4 | import org.gradle.api.GradleException; 5 | import org.gradle.api.file.RegularFileProperty; 6 | import org.gradle.api.provider.Property; 7 | import org.gradle.api.tasks.Input; 8 | import org.gradle.api.tasks.InputFile; 9 | import org.gradle.api.tasks.Internal; 10 | import org.gradle.api.tasks.OutputFile; 11 | import org.gradle.api.tasks.TaskAction; 12 | import org.gradle.docs.samples.Dsl; 13 | 14 | import java.io.File; 15 | import java.io.FileNotFoundException; 16 | import java.io.IOException; 17 | import java.io.PrintWriter; 18 | import java.util.Arrays; 19 | import java.util.List; 20 | import java.util.zip.ZipEntry; 21 | import java.util.zip.ZipFile; 22 | 23 | public abstract class ValidateSampleBinary extends DefaultTask { 24 | @InputFile 25 | public abstract RegularFileProperty getZipFile(); 26 | 27 | @Input 28 | public abstract Property getDsl(); 29 | 30 | @Internal 31 | public abstract Property getSampleName(); 32 | 33 | @OutputFile 34 | public abstract RegularFileProperty getReportFile(); 35 | 36 | @TaskAction 37 | public void validate() { 38 | // TODO: check for exemplar conf files 39 | Dsl dsl = getDsl().get(); 40 | String settingsFileName = getSettingsFileName(dsl); 41 | String name = getSampleName().get(); 42 | List requiredContents = Arrays.asList("README", settingsFileName, "gradlew", "gradlew.bat", "gradle/wrapper/gradle-wrapper.jar", "gradle/wrapper/gradle-wrapper.properties"); 43 | 44 | // Does the Zip look correct? 45 | File zipFile = getZipFile().get().getAsFile(); 46 | try (ZipFile zip = new ZipFile(zipFile)) { 47 | for (String required : requiredContents) { 48 | assertZipContains(name, dsl, zip, required); 49 | } 50 | } catch (IOException e) { 51 | throw new RuntimeException(e); 52 | } 53 | 54 | // TODO: Would be nice to put the failures in this file too. 55 | File reportFile = getReportFile().get().getAsFile(); 56 | try (PrintWriter fw = new PrintWriter(reportFile)) { 57 | fw.println(name + " looks valid."); 58 | } catch (FileNotFoundException e) { 59 | throw new RuntimeException(e); 60 | } 61 | } 62 | 63 | private void assertZipContains(String name, Dsl dsl, ZipFile zip, String file) { 64 | ZipEntry entry = zip.getEntry(file); 65 | if (entry == null) { 66 | throw new GradleException("Sample '" + name + "' for " + dsl.getDisplayName() + " DSL is invalid due to missing '" + file + "' file."); 67 | } 68 | } 69 | 70 | private String getSettingsFileName(Dsl dsl) { 71 | return switch (dsl) { 72 | case GROOVY -> "settings.gradle"; 73 | case KOTLIN -> "settings.gradle.kts"; 74 | }; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/samples/package-info.java: -------------------------------------------------------------------------------- 1 | @org.gradle.api.NonNullApi 2 | package org.gradle.docs.samples; 3 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/snippets/Snippet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradle.docs.snippets; 18 | 19 | import org.gradle.api.Named; 20 | import org.gradle.api.provider.SetProperty; 21 | import org.gradle.docs.samples.Dsl; 22 | 23 | public interface Snippet extends Named { 24 | /** 25 | * By convention, this is both Groovy and Kotlin. 26 | * Every sample must have at least one DSL. 27 | * 28 | * @return DSLs that should be expected for this sample. 29 | */ 30 | SetProperty getDsls(); 31 | } 32 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/snippets/Snippets.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradle.docs.snippets; 18 | 19 | import org.gradle.api.NamedDomainObjectContainer; 20 | import org.gradle.api.file.DirectoryProperty; 21 | 22 | public interface Snippets { 23 | /** 24 | * By convention, this is src/docs/snippets 25 | * 26 | * @return The root sample directory. 27 | */ 28 | DirectoryProperty getSnippetsRoot(); 29 | 30 | /** 31 | * @return Container of all published snippets. This is the primary configuration point for all snippets. 32 | */ 33 | NamedDomainObjectContainer getPublishedSnippets(); 34 | } 35 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/snippets/internal/SnippetBinary.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradle.docs.snippets.internal; 18 | 19 | import org.gradle.api.Named; 20 | 21 | import javax.inject.Inject; 22 | 23 | public abstract class SnippetBinary implements Named { 24 | private final String name; 25 | 26 | @Inject 27 | public SnippetBinary(String name) { 28 | this.name = name; 29 | } 30 | 31 | @Override 32 | public String getName() { 33 | return name; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/snippets/internal/SnippetInternal.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradle.docs.snippets.internal; 18 | 19 | import org.gradle.docs.snippets.Snippet; 20 | 21 | import javax.inject.Inject; 22 | 23 | public abstract class SnippetInternal implements Snippet { 24 | private final String name; 25 | 26 | @Inject 27 | public SnippetInternal(String name) { 28 | this.name = name; 29 | } 30 | 31 | @Override 32 | public String getName() { 33 | return name; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/snippets/internal/SnippetsDocumentationPlugin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradle.docs.snippets.internal; 18 | 19 | import org.gradle.api.Plugin; 20 | import org.gradle.api.Project; 21 | import org.gradle.api.file.ProjectLayout; 22 | import org.gradle.docs.internal.DocumentationBasePlugin; 23 | import org.gradle.docs.internal.DocumentationExtensionInternal; 24 | 25 | import static org.gradle.docs.internal.Asserts.assertNameDoesNotContainsDisallowedCharacters; 26 | 27 | public class SnippetsDocumentationPlugin implements Plugin { 28 | @Override 29 | public void apply(Project project) { 30 | ProjectLayout layout = project.getLayout(); 31 | 32 | project.getPluginManager().apply(DocumentationBasePlugin.class); 33 | 34 | // Register a samples extension to configure published samples 35 | SnippetsInternal extension = configureSnippetsExtension(project, layout); 36 | 37 | // Trigger everything by realizing sample container 38 | project.afterEvaluate(p -> realizeSnippets(extension)); 39 | } 40 | 41 | private SnippetsInternal configureSnippetsExtension(Project project, ProjectLayout layout) { 42 | SnippetsInternal extension = project.getExtensions().getByType(DocumentationExtensionInternal.class).getSnippets(); 43 | 44 | extension.getSnippetsRoot().convention(layout.getProjectDirectory().dir("src/docs/snippets")); 45 | 46 | return extension; 47 | } 48 | 49 | private void realizeSnippets(SnippetsInternal extension) { 50 | // TODO: Project is passed strictly for zipTree method 51 | // TODO: Disallow changes to published samples container after this point. 52 | for (SnippetInternal snippet : extension.getPublishedSnippets()) { 53 | assertNameDoesNotContainsDisallowedCharacters(snippet, "Snippet '%s' has disallowed characters", snippet.getName()); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/main/java/org/gradle/docs/snippets/internal/SnippetsInternal.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradle.docs.snippets.internal; 18 | 19 | import org.gradle.api.DomainObjectSet; 20 | import org.gradle.api.NamedDomainObjectContainer; 21 | import org.gradle.api.model.ObjectFactory; 22 | import org.gradle.docs.snippets.Snippets; 23 | 24 | import javax.inject.Inject; 25 | 26 | public abstract class SnippetsInternal implements Snippets { 27 | private final NamedDomainObjectContainer publishedSnippets; 28 | private final DomainObjectSet binaries; 29 | 30 | @Inject 31 | public SnippetsInternal(ObjectFactory objectFactory) { 32 | this.publishedSnippets = objectFactory.domainObjectContainer(SnippetInternal.class, name -> objectFactory.newInstance(SnippetInternal.class, name)); 33 | this.binaries = objectFactory.domainObjectSet(SnippetBinary.class); 34 | } 35 | 36 | @Override 37 | public NamedDomainObjectContainer getPublishedSnippets() { 38 | return publishedSnippets; 39 | } 40 | 41 | public DomainObjectSet getBinaries() { 42 | return binaries; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/test/groovy/org/gradle/docs/internal/CheckLinksSpec.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.internal 2 | 3 | import org.gradle.docs.internal.tasks.CheckLinks 4 | import spock.lang.Specification 5 | import spock.lang.TempDir 6 | 7 | import java.nio.file.Files 8 | import java.nio.file.Path 9 | 10 | class CheckLinksSpec extends Specification { 11 | @TempDir 12 | Path tempDir 13 | 14 | def "should extract absolute links from HTML"() { 15 | given: 16 | def html = """ 17 | 18 | 19 | Example 20 | Gradle 21 | 22 | 23 | """ 24 | 25 | when: 26 | def anchors = CheckLinks.getAnchors(html) 27 | 28 | then: 29 | anchors.size() == 2 30 | anchors.contains(new URI("https://example.com")) 31 | anchors.contains(new URI("https://gradle.org")) 32 | } 33 | 34 | def "should extract relative links from HTML"() { 35 | given: 36 | def html = """ 37 | 38 | 39 | Relative 40 | Another 41 | 42 | 43 | """ 44 | 45 | when: 46 | def anchors = CheckLinks.getAnchors(html) 47 | 48 | then: 49 | anchors.size() == 2 50 | anchors.contains(new URI("/relative/path")) 51 | anchors.contains(new URI("another/path")) 52 | } 53 | 54 | def "should handle empty document"() { 55 | given: 56 | def html = "" 57 | 58 | when: 59 | def anchors = CheckLinks.getAnchors(html) 60 | 61 | then: 62 | anchors.isEmpty() 63 | } 64 | 65 | def "should handle malformed HTML"() { 66 | given: 67 | def html = """ 68 | 69 | 70 | Example 71 | Gradle 72 | 73 | 74 | """ 75 | 76 | when: 77 | def anchors = CheckLinks.getAnchors(html) 78 | 79 | then: 80 | anchors.size() == 2 81 | anchors.contains(new URI("https://example.com")) 82 | anchors.contains(new URI("https://gradle.org")) 83 | } 84 | 85 | private File createHtmlFile(String content) { 86 | def filePath = tempDir.resolve("test.html") 87 | Files.writeString(filePath, content) 88 | return filePath.toFile() 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /gradle-guides-plugin/src/test/groovy/org/gradle/docs/internal/StringUtilsTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradle.docs.internal 2 | 3 | import spock.lang.Specification 4 | 5 | class StringUtilsTest extends Specification { 6 | def "capitalize works"() { 7 | expect: 8 | StringUtils.capitalize("abcdef") == "Abcdef" 9 | StringUtils.capitalize("ABCDEF") == "ABCDEF" 10 | StringUtils.capitalize("Abcdef") == "Abcdef" 11 | StringUtils.capitalize("12345") == "12345" 12 | } 13 | 14 | def "uncapitalize works"() { 15 | expect: 16 | StringUtils.uncapitalize("abcdef") == "abcdef" 17 | StringUtils.uncapitalize("ABCDEF") == "aBCDEF" 18 | StringUtils.uncapitalize("Abcdef") == "abcdef" 19 | StringUtils.uncapitalize("12345") == "12345" 20 | } 21 | 22 | def "toTitleCase works"() { 23 | expect: 24 | StringUtils.toTitleCase("this is an example") == "This Is An Example" 25 | StringUtils.toTitleCase("abcdef") == "Abcdef" 26 | StringUtils.toTitleCase("ABCDEF") == "Abcdef" 27 | StringUtils.toTitleCase("Abcdef") == "Abcdef" 28 | StringUtils.toTitleCase("12345") == "12345" 29 | } 30 | 31 | def "toLowerCamelCase works"() { 32 | expect: 33 | StringUtils.toLowerCamelCase("ThisIsAnExample") == "thisIsAnExample" 34 | StringUtils.toLowerCamelCase("abcdef") == "abcdef" 35 | StringUtils.toLowerCamelCase("ABCDEF") == "abcdef" 36 | StringUtils.toLowerCamelCase("Abcdef") == "abcdef" 37 | StringUtils.toLowerCamelCase("12345") == "12345" 38 | } 39 | 40 | def "toSnakeCase works"() { 41 | expect: 42 | StringUtils.toSnakeCase("ThisIsAnExample") == "this_is_an_example" 43 | StringUtils.toSnakeCase("abcdef") == "abcdef" 44 | StringUtils.toSnakeCase("ABCDEF") == "a_bc_de_f" 45 | StringUtils.toSnakeCase("Abcdef") == "abcdef" 46 | StringUtils.toSnakeCase("12345") == "12345" 47 | StringUtils.toSnakeCase("javaJunit4IntegrationTestForListLibrary") == "java_junit4_integration_test_for_list_library" 48 | } 49 | 50 | def "toKebabCase works"() { 51 | expect: 52 | StringUtils.toKebabCase("ThisIsAnExample") == "This-is-an-example" 53 | StringUtils.toKebabCase("abcdef") == "abcdef" 54 | StringUtils.toKebabCase("ABCDEF") == "ABCDEF" 55 | StringUtils.toKebabCase("Abcdef") == "Abcdef" 56 | StringUtils.toKebabCase("12345") == "12345" 57 | StringUtils.toKebabCase("javaJunit4IntegrationTestForListLibrary") == "java-junit4-integration-test-for-list-library" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx2500m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 2 | org.gradle.parallel=true 3 | org.gradle.caching=true 4 | dependency.analysis.print.build.health=true 5 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gradle/guides/8837671975cf2650b7ed38d16b04f3c703bbd031/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.gradle.develocity").version("3.18.1") 3 | id("com.autonomousapps.build-health").version("2.17.0") 4 | id("io.github.gradle.gradle-enterprise-conventions-plugin").version("0.10.2") 5 | } 6 | 7 | rootProject.name = "gradle-guides" 8 | 9 | include("gradle-guides-plugin") 10 | --------------------------------------------------------------------------------