diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 0dfe5680c89..12340e9f585 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -24,10 +24,6 @@ on: - '[4-9]+.[0-9]+.x' - '[3-9]+.[3-9]+.x' workflow_dispatch: -env: - # To prevent throttling of GitHub API usage, include the GitHub token as an environment variable - # as the build will use it when fetching git tags in CreateReleaseDropDown.groovy - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # queue jobs and only allow 1 run per branch due to the likelihood of hitting GitHub resource limits concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -393,6 +389,8 @@ jobs: run: curl -s https://api.ipify.org - name: "📥 Checkout the repository" uses: actions/checkout@v4 + with: + fetch-tags: true - name: "🔀 Store current branch name" run: | TARGET_BRANCH="${GITHUB_BASE_REF:-${GITHUB_REF#refs/heads/}}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a0f507f2ed0..3497860f601 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -42,6 +42,8 @@ jobs: echo "repository_name=${GITHUB_REPOSITORY##*/}" >> $GITHUB_OUTPUT - name: "📥 Checkout repository" uses: actions/checkout@v4 + with: + fetch-tags: true - name: 'Ensure Common Build Date' # to ensure a reproducible build run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >> "$GITHUB_ENV" - name: "Ensure source files use common date" @@ -487,6 +489,7 @@ jobs: - name: "📥 Checkout repository" uses: actions/checkout@v4 with: + fetch-tags: true token: ${{ secrets.GITHUB_TOKEN }} ref: v${{ needs.publish.outputs.release_version }} - name: 'Ensure Common Build Date' # to ensure a reproducible build diff --git a/RENAME.md b/RENAME.md index 824c7cc2b27..fe68654dc03 100644 --- a/RENAME.md +++ b/RENAME.md @@ -170,7 +170,6 @@ Below is a reference of all migrated artifacts - both their old and new name. | GRADLE | | | | | | | | org.grails | grails-gradle-plugin | org.apache.grails | grails-gradle-plugins | | | grails-gradle-plugin | | org.grails.grails-core | org.grails.grails-core.gradle.plugin | org.apache.grails.gradle.grails-app | org.apache.grails.gradle.grails-app.gradle.plugin | grails | grails-app | grails-gradle-plugin | -| org.grails.grails-doc | org.grails.grails-doc.gradle.plugin | org.apache.grails.gradle.grails-docs | org.apache.grails.gradle.grails-docs.gradle.plugin | grails-doc | grails-docs | grails-gradle-plugin | | org.grails.grails-gsp | org.grails.grails-gsp.gradle.plugin | org.apache.grails.gradle.grails-gsp | org.apache.grails.gradle.grails-gsp.gradle.plugin | grails-gsp | grails-gsp | grails-gradle-plugin | | org.grails.grails-plugin | org.grails.grails-plugin.gradle.plugin | org.apache.grails.gradle.grails-plugin | org.apache.grails.gradle.grails-plugin.gradle.plugin | grails-plugin | grails-plugin | grails-gradle-plugin | | org.grails.grails-profile | org.grails.grails-profile.gradle.plugin | org.apache.grails.gradle.grails-profile | org.apache.grails.gradle.grails-profile.gradle.plugin | grails-profile | grails-profile | grails-gradle-plugin | diff --git a/buildSrc/src/main/groovy/grails/doc/AddReleaseDropDown.groovy b/buildSrc/src/main/groovy/grails/doc/AddReleaseDropDown.groovy deleted file mode 100644 index c40913834f6..00000000000 --- a/buildSrc/src/main/groovy/grails/doc/AddReleaseDropDown.groovy +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package grails.doc - -import grails.doc.dropdown.SoftwareVersion -import groovy.json.JsonSlurper -import groovy.transform.CompileDynamic -import groovy.transform.CompileStatic -import org.gradle.api.DefaultTask -import org.gradle.api.GradleException -import org.gradle.api.Project -import org.gradle.api.file.ConfigurableFileCollection -import org.gradle.api.file.DirectoryProperty -import org.gradle.api.model.ObjectFactory -import org.gradle.api.provider.Property -import org.gradle.api.tasks.* - -import javax.inject.Inject -import java.nio.file.* -import java.nio.file.attribute.BasicFileAttributes -import java.util.stream.Collectors - -/** - * Duplicates the documentation and modifies source files to add a release dropdown to the documentation - * @since 6.2.1 - */ -@CompileStatic -@CacheableTask -abstract class AddReleaseDropDown extends DefaultTask { - - private static final String GRAILS_DOC_BASE_URL = "https://docs.grails.org" - private static final String GITHUB_API_BASE_URL = "https://api.github.com" - - @Input - final Property slug - - @InputDirectory - @PathSensitive(PathSensitivity.RELATIVE) - final DirectoryProperty docsDirectory - - @Input - final Property version - - @InputFiles - @PathSensitive(PathSensitivity.RELATIVE) - final ConfigurableFileCollection inputFiles - - @OutputDirectory - final DirectoryProperty outputDir - - @Inject - AddReleaseDropDown(ObjectFactory objects, Project project) { - slug = objects.property(String).convention('apache/grails-doc') - docsDirectory = objects.directoryProperty().convention(project.layout.buildDirectory.dir('manual')) - version = objects.property(String).convention(project.provider { project.version.toString() }) - inputFiles = objects.fileCollection() - outputDir = objects.directoryProperty().convention(project.layout.buildDirectory.dir("modified-pages")) - group = 'documentation' - } - - /** - * Add the release dropdown to the documentation - */ - @TaskAction - void addReleaseDropDown() { - String projectVersion = version.get() - - final Object result = listRepoTags("apache/grails-core") - List softwareVersions = parseSoftwareVersions(result) - logger.lifecycle("Detected Project Version: ${projectVersion} and Software Versions: ${softwareVersions*.versionText.join(',')}") - - final String versionHtml = "

Version: ${projectVersion}

" - Path guideDirectory = docsDirectory.get().asFile.toPath() - Path targetOutputDirectory = outputDir.get().asFile.toPath() - - Map filesToChange = inputFiles.collectEntries { [it.absolutePath, it.toPath()] } - Files.walkFileTree(guideDirectory, new SimpleFileVisitor() { - @Override - FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { - Path targetDir = targetOutputDirectory.resolve(guideDirectory.relativize(dir)) - if (!Files.exists(targetDir)) { - Files.createDirectories(targetDir) - } - FileVisitResult.CONTINUE - } - - @Override - FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - Path targetFile = targetOutputDirectory.resolve(guideDirectory.relativize(file)) - if (Files.exists(targetFile)) { - Files.deleteIfExists(targetFile) - } - - String absolutePath = targetFile.toAbsolutePath().toString() - if (filesToChange.containsKey(absolutePath)) { - //Need to add the version dropdown - String page = guideDirectory.toFile().relativePath(file.toFile()) - String selectHtml = select(options(projectVersion, page, softwareVersions)) - - final String versionWithSelectHtml = "

Version: ${selectHtml}

" - targetFile.toFile().text = file.text.replace(versionHtml, versionWithSelectHtml) - - filesToChange.remove(absolutePath) - } - Files.copy(file, targetFile, StandardCopyOption.REPLACE_EXISTING) - FileVisitResult.CONTINUE - } - - @Override - FileVisitResult visitFileFailed(Path file, IOException e) throws IOException { - throw new GradleException("Unable to copy file: ${file}", e) - } - }) - } - - /** - * Generate the options for the select tag. - * - * @param version The current version of the documentation - * @param page The page to add the dropdown to - * @param softwareVersions The list of software versions to include in the dropdown - * @return The list of options for the select tag - */ - private List options(String version, String page, List softwareVersions) { - List options = [] - final String snapshotHref = GRAILS_DOC_BASE_URL + "/snapshot" + page - options << option(snapshotHref, "SNAPSHOT", version.endsWith("-SNAPSHOT")) - - softwareVersions - .forEach { softwareVersion -> - final String versionName = softwareVersion.versionText - final String href = GRAILS_DOC_BASE_URL + "/" + versionName + page - options << option(href, versionName, version == versionName) - } - options - } - - /** - * List all tags in the repository using the GitHub API. - * - * @param repoSlug The slug of the repository. e.g. apache/grails-core - * @return The list of tags in the repository - */ - private Object listRepoTags(String repoSlug) { - URL url = new URL(GITHUB_API_BASE_URL + "/repos/" + repoSlug + "/tags") - URLConnection connection = url.openConnection() - connection.setRequestProperty("User-Agent", "apache/grails-core") - - // See https://github.com/orgs/community/discussions/42748#discussioncomment-4709316 - String token = System.getenv('GITHUB_TOKEN') - if(token) { - connection.setRequestProperty("Authorization", "Bearer ${token}") - } - - final String json = connection.inputStream.text - - def result = new JsonSlurper().parseText(json) - result - } - - - /** - * Generate the select tag - * - * @param options The List of options tags for the select tag - * @return The select tag with the options - */ - private String select(List options) { - String selectHtml = "' - selectHtml - } - - /** - * Generate the option tag - * - * @param value The URL to navigate to - * @param text The version to display - * @param selected Whether the option is selected - * - * @return The option tag - */ - private String option(String value, String text, boolean selected = false) { - if (selected) { - return "" - } else { - return "" - } - } - - /** - * Parse the software versions from the resultant JSON - * - * @param result List of all tags in the repository. - * @return The list of software versions - */ - @CompileDynamic - private List parseSoftwareVersions(def result) { - result.stream() - .filter(v -> v.name.startsWith('v')) - .map(v -> v.name.replace('v', '')) - .map(SoftwareVersion::build) - .sorted() - .distinct() - .collect(Collectors.toList()) - .reverse() - } -} diff --git a/grails-doc/build.gradle b/grails-doc/build.gradle index 22869adf3c5..bb59665b350 100644 --- a/grails-doc/build.gradle +++ b/grails-doc/build.gradle @@ -17,8 +17,9 @@ * under the License. */ -import grails.doc.AddReleaseDropDown -import grails.doc.gradle.PublishGuide +import grails.doc.git.FetchTagsTask +import grails.doc.dropdown.CreateReleaseDropDownTask +import grails.doc.gradle.PublishGuideTask plugins { id 'base' @@ -66,30 +67,30 @@ combinedGroovydoc.configure { Groovydoc gdoc -> .findAll { it.findProperty('includeInApiDocs') } def sources = docProjects - .collectMany {Project it -> + .collectMany { Project it -> List possibleSources = [it.sourceSets.main] def testFixturesSource = it.sourceSets.findByName('testFixtures') - if(testFixturesSource) { + if (testFixturesSource) { possibleSources.add(testFixturesSource) } def astSource = it.sourceSets.findByName('ast') - if(astSource) { + if (astSource) { possibleSources.add(astSource) } possibleSources } .flatten() - gdoc.source(sources.collect{ SourceSet it -> [it.allSource.srcDirs, it.allSource.srcDirs] }.flatten().findAll { File srcDir -> - if(!(srcDir.name in ['java', 'groovy'])) { + gdoc.source(sources.collect { SourceSet it -> [it.allSource.srcDirs, it.allSource.srcDirs] }.flatten().findAll { File srcDir -> + if (!(srcDir.name in ['java', 'groovy'])) { return false } srcDir.exists() }.unique()) - gdoc.classpath = files(sources.collect{ SourceSet it -> it.compileClasspath.filter(File.&isDirectory) }.flatten().unique()) + gdoc.classpath = files(sources.collect { SourceSet it -> it.compileClasspath.filter(File.&isDirectory) }.flatten().unique()) gdoc.destinationDir = project.layout.buildDirectory.dir('combined-api/api').get().asFile gdoc.inputs.files(gdoc.source) @@ -101,51 +102,6 @@ System.setProperty('grails.docs.clean.html', 'true') // creates single.html.before.xml and single.html.after.xml files for debugging pdf input when enabled //System.setProperty('grails.docs.debug.pdf','true') -asciidoctor { - resources { - from('resources') - } - - options template_dirs: ["${projectDir}/src/docs/templates"] - attributes 'experimental': 'true', - 'compat-mode': 'true', - 'icons': 'font', - 'linkcss': 'true', - 'docinfo1': '', - 'toc': 'left', - 'version': project.version, - 'sourcedir': rootProject.projectDir.absolutePath -} - -asciidoctor.dependsOn('aggregateGroovydoc') - -tasks.register('dist', Zip).configure { Zip it -> - it.dependsOn 'docs' - it.from outputDir -} - -tasks.register('createReleaseDropdown', AddReleaseDropDown).configure { AddReleaseDropDown it -> - it.group = 'documentation' - it.dependsOn 'publishGuide' - - it.inputFiles = project.layout.buildDirectory.files('original-guide/guide/single.html') - it.docsDirectory = project.layout.buildDirectory.dir('original-guide') - it.outputDir = project.layout.buildDirectory.dir('modified-guide') -} - -tasks.register('resolveGroovyVersion').configure { Task versionTask -> - versionTask.group = 'documentation' - versionTask.description = 'Resolve Groovy Version from the BOM' - ext.resolved = configurations.compileClasspath - .resolvedConfiguration - .resolvedArtifacts - .find { - it.moduleVersion.id.group == 'org.apache.groovy' && - it.moduleVersion.id.name.contains('groovy') - }.moduleVersion.id.version - logger.lifecycle('Resolved Groovy version for Guide links: {}', ext.resolved) -} - String getVersion(String artifact) { String version = configurations.runtimeClasspath .resolvedConfiguration @@ -153,14 +109,47 @@ String getVersion(String artifact) { .find { it.moduleVersion.id.name == artifact }?.moduleVersion?.id?.version - if(!version) { + if (!version) { throw new GradleException("Could not find ${artifact} version.") } version } -tasks.register('publishGuide', PublishGuide).configure { PublishGuide publish -> - publish.dependsOn(['generateBomDocumentation', 'aggregateGroovydoc', 'jar', 'resolveGroovyVersion', 'processTestResources', 'compileTestJava', 'compileTestGroovy', 'test']) +def generateBomDocumentation = tasks.register('generateBomDocumentation') +generateBomDocumentation.configure { Task it -> + it.dependsOn(':grails-bom:extractConstraints') + + it.description = 'Generates an AsciiDoc table listing Group, Artifact, and Version for project dependencies.' + it.group = 'documentation' + + it.inputs.files(project(':grails-bom').layout.projectDirectory.asFileTree) + it.outputs.file(project.layout.projectDirectory.file('src/en/ref/Versions/Grails BOM.adoc')) + + def versionsDir = project.layout.projectDirectory.dir('src/en/ref/Versions') + it.doFirst { + versionsDir.asFile.mkdirs() + } + + def bomDocumentFile = project.layout.projectDirectory.file('src/en/ref/Versions/Grails BOM.adoc') + def grailsBomConstraintFile = project(':grails-bom').layout.buildDirectory.file('grails-bom-constraints.adoc') + it.doLast { + def bomDocument = bomDocumentFile.asFile + bomDocument.withWriter { writer -> + writer.writeLine '== Grails BOM Dependencies' + writer.writeLine '' + writer.writeLine 'This document provides information about the dependencies defined in the Grails BOM - also known as `org.apache.grails:grails-bom`. It includes the artifact coordinates, where in the hierarchy the coordinate was defined, and the maven property that defines the version.' + writer.writeLine '' + writer.write(grailsBomConstraintFile.get().asFile.text) + writer.writeLine '' + } + + it.logger.lifecycle "BOM Dependency Page generated to: ${bomDocument.absolutePath}" + } +} + +def publishGuideTask = tasks.register('publishGuide', PublishGuideTask) +publishGuideTask.configure { PublishGuideTask publish -> + publish.dependsOn([generateBomDocumentation]) // No language setting because we want the English guide to be // generated with a 'en' in the path, but the source is in 'en' @@ -175,15 +164,15 @@ tasks.register('publishGuide', PublishGuide).configure { PublishGuide publish -> publish.properties = project.provider { def springVersion = getVersion('spring-core') def springBootVersion = getVersion('spring-boot') - + def groovyVersion = getVersion('groovy') as String [ 'api' : '../api', 'safe' : 'UNSAFE', // Make sure any asciidoc security is disabled 'jakartaee' : 'https://jakarta.ee/specifications/platform/10/apidocs/', 'javase' : "https://docs.oracle.com/en/java/javase/${javaVersion}/docs/api/", - 'groovyapi' : "https://docs.groovy-lang.org/${resolveGroovyVersion.resolved}/html/gapi/", - 'groovyjdk' : "https://docs.groovy-lang.org/${resolveGroovyVersion.resolved}/html/groovy-jdk/", - 'groovyVersion' : resolveGroovyVersion.resolved, + 'groovyapi' : "https://docs.groovy-lang.org/${groovyVersion}/html/gapi/", + 'groovyjdk' : "https://docs.groovy-lang.org/${groovyVersion}/html/groovy-jdk/", + 'groovyVersion' : groovyVersion, 'springapi' : "https://docs.spring.io/spring/docs/${springVersion}/javadoc-api/", 'springdocs' : "https://docs.spring.io/spring/docs/${springVersion}/", 'githubBranch' : githubBranch, @@ -216,44 +205,23 @@ tasks.register('publishGuide', PublishGuide).configure { PublishGuide publish -> } } -tasks.register('generateBomDocumentation').configure { Task it -> - it.dependsOn(':grails-bom:extractConstraints') - - it.description = 'Generates an AsciiDoc table listing Group, Artifact, and Version for project dependencies.' - it.group = 'documentation' - - it.inputs.files(project(':grails-bom').layout.projectDirectory.asFileTree) - it.outputs.file(project.layout.projectDirectory.file('src/en/ref/Versions/Grails BOM.adoc')) +def createReleaseDropdownTask = tasks.register('createReleaseDropdown', CreateReleaseDropDownTask) +createReleaseDropdownTask.configure { CreateReleaseDropDownTask it -> + it.dependsOn(publishGuideTask) - def versionsDir = project.layout.projectDirectory.dir('src/en/ref/Versions') - it.doFirst { - versionsDir.asFile.mkdirs() - } - - def bomDocumentFile = project.layout.projectDirectory.file('src/en/ref/Versions/Grails BOM.adoc') - def grailsBomConstraintFile = project(':grails-bom').layout.buildDirectory.file('grails-bom-constraints.adoc') - it.doLast { - def bomDocument = bomDocumentFile.asFile - bomDocument.withWriter { writer -> - writer.writeLine '== Grails BOM Dependencies' - writer.writeLine '' - writer.writeLine 'This document provides information about the dependencies defined in the Grails BOM - also known as `org.apache.grails:grails-bom`. It includes the artifact coordinates, where in the hierarchy the coordinate was defined, and the maven property that defines the version.' - writer.writeLine '' - writer.write(grailsBomConstraintFile.get().asFile.text) - writer.writeLine '' - } - - it.logger.lifecycle "BOM Dependency Page generated to: ${bomDocument.absolutePath}" - } + it.filesToAddDropdowns = project.layout.buildDirectory.files('modified-guide/guide/single.html', 'modified-guide/index.html') + it.sourceDocsDirectory = project.layout.buildDirectory.dir('original-guide') + it.modifiedPagesDirectory = project.layout.buildDirectory.dir('modified-guide') } -tasks.register('generateGuide').configure { Task task -> - task.dependsOn('createReleaseDropdown') - task.group = 'documentation' +def fetchTagsTask = project.tasks.register('fetchTags', FetchTagsTask) +createReleaseDropdownTask.configure { + dependsOn(fetchTagsTask) } -tasks.register('docs', Sync).configure { Sync it -> - it.dependsOn 'aggregateGroovydoc', 'generateGuide', ':grails-data-docs-stage:docs' +def docsTask = tasks.register('docs', Sync) +docsTask.configure { Sync it -> + it.dependsOn(combinedGroovydoc, createReleaseDropdownTask, ':grails-data-docs-stage:docs') it.group = 'documentation' def manualDocsDir = project.layout.buildDirectory.dir('modified-guide') @@ -273,6 +241,11 @@ tasks.register('docs', Sync).configure { Sync it -> it.into mergedDocsDir } +tasks.register('dist', Zip).configure { Zip it -> + it.dependsOn(docsTask) + it.from(outputDir) +} + artifacts { archives dist } diff --git a/grails-doc/src/en/guide/upgrading/upgrading60x.adoc b/grails-doc/src/en/guide/upgrading/upgrading60x.adoc index 9fb5f046683..cfcbc93deb6 100644 --- a/grails-doc/src/en/guide/upgrading/upgrading60x.adoc +++ b/grails-doc/src/en/guide/upgrading/upgrading60x.adoc @@ -146,6 +146,10 @@ The `distributionUrl` in the file should now point to the specified Gradle versi distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip ---- +===== 6.4. Removed Gradle Plugins + +The `grails-doc` plugin has been removed. If you wish to continue to generate the guide documentation, you can use the PublishGuideTask offered by `grails-docs-core`. + ==== 7. Reproducible Builds: The ASF strongly encourages https://reproducible-builds.org/[reproducible builds] as part of it's security requirements. diff --git a/grails-doc/src/en/ref/Command Line/docs.adoc b/grails-doc/src/en/ref/Command Line/docs.adoc deleted file mode 100644 index 5d15a38ba33..00000000000 --- a/grails-doc/src/en/ref/Command Line/docs.adoc +++ /dev/null @@ -1,75 +0,0 @@ -//// -Licensed to the Apache Software Foundation (ASF) under one -or more contributor license agreements. See the NOTICE file -distributed with this work for additional information -regarding copyright ownership. The ASF licenses this file -to you under the Apache License, Version 2.0 (the -"License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at - -https://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, -software distributed under the License is distributed on an -"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -KIND, either express or implied. See the License for the -specific language governing permissions and limitations -under the License. -//// - - -== docs - - - -=== Purpose - - -Generates a user guide for the given Gradle project. - - -=== Examples - - -Add the plugin in your `build.gradle`: - -[source,groovy] ----- -apply plugin: "org.apache.grails.gradle.grails-docs" ----- - -Now run the command - -[source,groovy] ----- -gradle docs ----- - - -=== Description - - -Some projects, particular plugins, benefit from documentation explaining how they work. Grails comes with its own documentation engine based on a wiki syntax that can generate both HTML and PDF versions of a user guide, just like the one you are currently reading. If you have the source for a user guide in `src/docs`, then this command will automatically generate the corresponding HTML and PDF documents. - -It's often useful to have API documentation as well. Since Grails is a mixed source framework, the command also generates both http://www.oracle.com/technetwork/java/javase/documentation/index-137868.html[Javadoc] (the standard format for Java documentation) and Groovydoc API references in HTML form. - -The documentation is generated to the following directories: - -* `docs/guide` - Location of the user guide -* `docs/ref` - Location of the reference section of the user guide - -Usage: -[source,groovy] ----- -grails doc ----- - -Arguments: - -* `\--init` - Create a template project documentation project (optional) -* `\--pdf` - Create PDF output for project documentation (optional) - -Fired Events: - -* `DocStart` - Before documentation generation begins -* `DocEnd` - After documentation generation completes diff --git a/grails-gradle/docs-core/src/main/groovy/grails/doc/dropdown/CreateReleaseDropDownTask.groovy b/grails-gradle/docs-core/src/main/groovy/grails/doc/dropdown/CreateReleaseDropDownTask.groovy index 98e95816500..da0145a27fb 100644 --- a/grails-gradle/docs-core/src/main/groovy/grails/doc/dropdown/CreateReleaseDropDownTask.groovy +++ b/grails-gradle/docs-core/src/main/groovy/grails/doc/dropdown/CreateReleaseDropDownTask.groovy @@ -19,71 +19,198 @@ package grails.doc.dropdown -import groovy.json.JsonSlurper -import groovy.transform.CompileDynamic import groovy.transform.CompileStatic import org.gradle.api.DefaultTask -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.OutputFile -import org.gradle.api.tasks.TaskAction +import org.gradle.api.GradleException +import org.gradle.api.Project +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.Property +import org.gradle.api.tasks.* +import javax.inject.Inject +import java.nio.file.* +import java.nio.file.attribute.BasicFileAttributes + +/** + * Duplicates the documentation and modifies source files to add a release dropdown to the documentation + * @since 6.2.1 + */ @CompileStatic -class CreateReleasesDropdownTask extends DefaultTask { +@CacheableTask +abstract class CreateReleaseDropDownTask extends DefaultTask { - @Input - String slug + private static final String GRAILS_DOC_BASE_URL = "https://docs.grails.org" @Input - String version + final Property githubSlug - @OutputFile - File guide + @InputDirectory + @PathSensitive(PathSensitivity.RELATIVE) + final DirectoryProperty sourceDocsDirectory - @OutputFile - File index + @InputFile + @PathSensitive(PathSensitivity.RELATIVE) + final RegularFileProperty gitTags - @TaskAction - void modifyHtmlAndAddReleasesDropdown() { + @Input + final Property projectVersion - String selectHtml = composeSelectHtml() + @Input + final Property minimumVersion + + @InputFiles + @PathSensitive(PathSensitivity.RELATIVE) + final ConfigurableFileCollection filesToAddDropdowns - String versionHtml = "

Version: ${version}

" - String versionWithSelectHtml = "

Version: ${selectHtml}

" - guide.text = guide.text.replace(versionHtml, versionWithSelectHtml) - index.text = index.text.replace(versionHtml, versionWithSelectHtml) + @OutputDirectory + final DirectoryProperty modifiedPagesDirectory + + @Inject + CreateReleaseDropDownTask(ObjectFactory objects, Project project) { + group = 'documentation' + githubSlug = objects.property(String).convention(project.provider { + project.findProperty('githubSlug') as String ?: 'apache/grails-doc' + }) + sourceDocsDirectory = objects.directoryProperty().convention(project.layout.buildDirectory.dir('manual')) + projectVersion = objects.property(String).convention(project.provider { project.version as String }) + filesToAddDropdowns = objects.fileCollection() + modifiedPagesDirectory = objects.directoryProperty().convention(project.layout.buildDirectory.dir("modified-pages")) + gitTags = objects.fileProperty().convention(project.layout.buildDirectory.file('git-tags.txt')) + minimumVersion = objects.property(SoftwareVersion).convention(new SoftwareVersion(major: 5)) } - String composeSelectHtml() { - String repo = slug.split('/')[1] - String org = slug.split('/')[0] - JsonSlurper slurper = new JsonSlurper() - String json = new URL("https://api.github.com/repos/${slug}/tags").text - def result = slurper.parseText(json) - String selectHtml = "" + options.each { option -> selectHtml += option } selectHtml += '' selectHtml } - @CompileDynamic - List parseSoftwareVersions(Object result) { - result.findAll { it.name.startsWith('v') }.collect { SoftwareVersion.build(it.name.replace('v', '')) }.sort().unique().reverse() + /** + * Generate the option tag + * + * @param value The URL to navigate to + * @param text The version to display + * @param selected Whether the option is selected + * + * @return The option tag + */ + private String option(String value, String text, boolean selected = false) { + if (selected) { + return "" + } else { + return "" + } + } + + /** + * Parse the software versions from the resultant JSON + * + * @param result List of all tags in the repository. + * @param minimumVersion Minimum SoftwareVersion to include in the list. Default version is 0.0.0 + * @return The list of software versions + */ + private List parseSoftwareVersions(String projectVersion, List tags) { + def minimum = minimumVersion.get() + + LinkedHashSet combined = ["v${projectVersion}" as String] + combined.addAll(tags) + + combined.findAll { it?.startsWith('v') } + .collect { it.replace('v', '') } + .collect { SoftwareVersion.build(it) } + .findAll { it >= minimum } + .toSorted() + .unique() + .reverse() } } diff --git a/grails-gradle/docs-core/src/main/groovy/grails/doc/dropdown/SoftwareVersion.groovy b/grails-gradle/docs-core/src/main/groovy/grails/doc/dropdown/SoftwareVersion.groovy index 309c5be75a3..1d246fb7538 100644 --- a/grails-gradle/docs-core/src/main/groovy/grails/doc/dropdown/SoftwareVersion.groovy +++ b/grails-gradle/docs-core/src/main/groovy/grails/doc/dropdown/SoftwareVersion.groovy @@ -19,7 +19,8 @@ package grails.doc.dropdown -class SoftwareVersion implements Comparable { +class SoftwareVersion implements Comparable, Serializable { + private static final long serialVersionUID = 1L; int major int minor @@ -45,7 +46,11 @@ class SoftwareVersion implements Comparable { softVersion.snapshot = new Snapshot(subparts[1..-1].join("-")) return softVersion } - softVersion.patch = parts[2].toInteger() + + // Filter out invalid patches (e.g. 1.0.RC4) + if (parts[2].isInteger()) { + softVersion.patch = parts[2].toInteger() + } } softVersion } diff --git a/grails-gradle/docs-core/src/main/groovy/grails/doc/git/FetchTagsTask.groovy b/grails-gradle/docs-core/src/main/groovy/grails/doc/git/FetchTagsTask.groovy new file mode 100644 index 00000000000..5aca7fde30f --- /dev/null +++ b/grails-gradle/docs-core/src/main/groovy/grails/doc/git/FetchTagsTask.groovy @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package grails.doc.git + +import groovy.transform.CompileStatic +import org.gradle.api.Project +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.Property +import org.gradle.api.tasks.* + +import javax.inject.Inject +import java.nio.charset.StandardCharsets +import java.time.LocalDate +import java.time.LocalDateTime + +@CompileStatic +@CacheableTask +abstract class FetchTagsTask extends Exec { + + @Input + final Property cacheDate // allows for forcing refreshing the tags, defaults to once a day + + @OutputFile + final RegularFileProperty tagsFile + + @Inject + FetchTagsTask(ObjectFactory objectFactory, Project project) { + group = 'documentation' + cacheDate = objectFactory.property(LocalDateTime).convention(LocalDate.now().atStartOfDay()) + tagsFile = objectFactory.fileProperty().convention(project.layout.buildDirectory.file('git-tags.txt')) + + commandLine("git", "tag", "-l", "--sort=-creatordate") + ignoreExitValue = false + + def output = new ByteArrayOutputStream() + standardOutput = output + + doLast { + File file = tagsFile.get().asFile + if (!file.parentFile.exists()) { + file.mkdirs() + } + + file.text = new String(output.toByteArray(), StandardCharsets.UTF_8).trim() + } + } +} diff --git a/grails-gradle/docs-core/src/main/groovy/grails/doc/gradle/PublishGuide.groovy b/grails-gradle/docs-core/src/main/groovy/grails/doc/gradle/PublishGuideTask.groovy similarity index 94% rename from grails-gradle/docs-core/src/main/groovy/grails/doc/gradle/PublishGuide.groovy rename to grails-gradle/docs-core/src/main/groovy/grails/doc/gradle/PublishGuideTask.groovy index b7446cb3ab6..c4d99141c71 100644 --- a/grails-gradle/docs-core/src/main/groovy/grails/doc/gradle/PublishGuide.groovy +++ b/grails-gradle/docs-core/src/main/groovy/grails/doc/gradle/PublishGuideTask.groovy @@ -32,12 +32,13 @@ import org.gradle.api.AntBuilder import org.gradle.api.tasks.* import javax.inject.Inject +import java.nio.file.Files /** * Gradle task for generating a gdoc-based HTML user guide. */ @CacheableTask -class PublishGuide extends DefaultTask { +class PublishGuideTask extends DefaultTask { @Optional @Input @@ -64,11 +65,6 @@ class PublishGuide extends DefaultTask { @PathSensitive(PathSensitivity.RELATIVE) final DirectoryProperty sourceDir - @Optional - @InputDirectory - @PathSensitive(PathSensitivity.RELATIVE) - final DirectoryProperty workDir - @Optional @InputDirectory @PathSensitive(PathSensitivity.RELATIVE) @@ -84,7 +80,7 @@ class PublishGuide extends DefaultTask { private final AntBuilder ant @Inject - PublishGuide(ObjectFactory objects, Project project) { + PublishGuideTask(ObjectFactory objects, Project project) { this.ant = project.ant language = objects.property(String).convention(null as String) sourceRepo = objects.property(String) @@ -92,7 +88,6 @@ class PublishGuide extends DefaultTask { asciidoc = objects.property(Boolean).convention(true) propertiesFiles = objects.fileCollection() sourceDir = objects.directoryProperty().convention(project.layout.projectDirectory.dir("src")) - workDir = objects.directoryProperty().convention(project.layout.buildDirectory) resourcesDir = objects.directoryProperty().convention(project.layout.projectDirectory.dir("resources")) macros = objects.listProperty(Object).convention([]) targetDir = objects.directoryProperty().convention(project.layout.buildDirectory.dir("docs")) @@ -103,6 +98,8 @@ class PublishGuide extends DefaultTask { def publishGuide() { Properties combinedProperties = new Properties() + File workingDir = Files.createTempDirectory('grails-doc-publish-guide').toFile() + File resources = resourcesDir.get().asFile File docProperties = new File(resources, 'doc.properties') if(docProperties.exists()) { @@ -126,7 +123,7 @@ class PublishGuide extends DefaultTask { def publisher = new DocPublisher(sourceDir.get().asFile, apiDir) publisher.ant = ant publisher.asciidoc = asciidoc - publisher.workDir = workDir.get().asFile + publisher.workDir = workingDir publisher.apiDir = apiDir publisher.language = language.getOrElse('') publisher.sourceRepo = sourceRepo.getOrElse('') @@ -170,6 +167,8 @@ class PublishGuide extends DefaultTask { // Restore the old context class loader. Thread.currentThread().contextClassLoader = oldClassLoader + + workingDir.deleteDir() } } diff --git a/grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/doc/GrailsDocGradlePlugin.groovy b/grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/doc/GrailsDocGradlePlugin.groovy deleted file mode 100644 index cc4555820cc..00000000000 --- a/grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/doc/GrailsDocGradlePlugin.groovy +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.grails.gradle.plugin.doc - -import grails.util.BuildSettings -import groovy.transform.CompileStatic -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.Task -import org.gradle.api.artifacts.Configuration -import org.gradle.api.tasks.javadoc.Groovydoc -import org.gradle.api.tasks.javadoc.Javadoc - -/** - * Adds Grails doc publishing support - * - * @author Graeme Rocher - * @since 3.0 - */ -@CompileStatic -class GrailsDocGradlePlugin implements Plugin { - - public static final String DOC_CONFIGURATION = 'docs' - - @Override - void apply(Project project) { - Configuration docConfiguration = project.configurations.create(DOC_CONFIGURATION) - project.dependencies.add(DOC_CONFIGURATION, "org.apache.grails:grails-docs-core:$BuildSettings.grailsVersion") - - Groovydoc groovydocTask = (Groovydoc)project.tasks.findByName('groovydoc') - Javadoc javadocTask = (Javadoc)project.tasks.findByName('javadoc') - - if(groovydocTask && javadocTask) { - - Task docsTask = project.tasks.create('docs', PublishGuideTask) - - docsTask.classpath = docConfiguration - - File applicationYml = project.file("${project.projectDir}/grails-app/conf/application.yml") - if(applicationYml.exists()) { - docsTask.propertiesFile = applicationYml - } - docsTask.destinationDir = project.file("${project.buildDir}/docs/manual") - docsTask.source = project.file("${project.projectDir}/src/docs") - docsTask.resourcesDir = project.file("${project.projectDir}/src/docs") - docsTask.groovydocDir = groovydocTask.destinationDir - docsTask.javadocDir = javadocTask.destinationDir - docsTask.dependsOn(groovydocTask) - docsTask.dependsOn(javadocTask) - } - } -} diff --git a/grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/doc/PublishGuideTask.groovy b/grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/doc/PublishGuideTask.groovy deleted file mode 100644 index 9231e813d03..00000000000 --- a/grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/doc/PublishGuideTask.groovy +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.grails.gradle.plugin.doc - -import groovy.transform.CompileDynamic -import groovy.transform.CompileStatic -import org.gradle.api.tasks.InputDirectory -import org.gradle.api.tasks.InputFile -import org.gradle.api.tasks.Optional -import org.gradle.api.tasks.TaskAction -import org.gradle.api.tasks.compile.AbstractCompile - -/** - * A task used to publish the user guide if a publin that is in GDoc format - * - * @author Graeme Rocher - * @since 3.0 - */ -@CompileStatic -class PublishGuideTask extends AbstractCompile { - - @InputDirectory - @Optional - File resourcesDir - - @InputFile - @Optional - File propertiesFile - - @InputDirectory - @Optional - File groovydocDir - - @InputDirectory - @Optional - File javadocDir - - @InputDirectory - File srcDir - - @Override - void setSource(Object source) { - try { - srcDir = project.file(source) - if (srcDir.exists() && !srcDir.isDirectory()) { - throw new IllegalArgumentException("The source for GSP compilation must be a single directory, but was $source") - } - super.setSource(source) - } catch (e) { - throw new IllegalArgumentException("The source for GSP compilation must be a single directory, but was $source") - } - } - - @CompileDynamic - @TaskAction - void compile() { - def urls = getClasspath().files.collect() { File f -> f.toURI().toURL() } - - URLClassLoader classLoader = new URLClassLoader(urls as URL[], (ClassLoader) null) - def docPublisher = classLoader.loadClass("grails.doc.DocPublisher").newInstance(srcDir, destinationDir, project.logger) - if (groovydocDir?.exists()) { - project.copy { - from groovydocDir - into "$destinationDir/gapi" - } - } - if (javadocDir?.exists()) { - project.copy { - from javadocDir - into "$destinationDir/api" - } - } - docPublisher.title = project.name - docPublisher.version = project.version - docPublisher.src = srcDir - docPublisher.target = destinationDir - docPublisher.workDir = new File(project.buildDir, "doc-tmp") - docPublisher.apiDir = destinationDir - if (resourcesDir) { - docPublisher.images = new File(resourcesDir, "img") - docPublisher.css = new File(resourcesDir, "css") - docPublisher.js = new File(resourcesDir, "js") - docPublisher.style = new File(resourcesDir, "style") - } - if (propertiesFile) { - docPublisher.propertiesFile = propertiesFile - } - - - docPublisher.publish() - } - -}