diff --git a/build.gradle b/build.gradle index e12f61ea50..85b29caf85 100644 --- a/build.gradle +++ b/build.gradle @@ -28,8 +28,6 @@ buildscript { plugins { alias(libs.plugins.artifactory) alias(libs.plugins.shadow) - alias(libs.plugins.asciidoctor.convert) apply false - alias(libs.plugins.asciidoctor.pdf) apply false alias(libs.plugins.japicmp) alias(libs.plugins.download) // note: build scan plugin now must be applied in settings.gradle @@ -42,7 +40,6 @@ plugins { } apply plugin: "io.reactor.gradle.detect-ci" -apply from: "gradle/asciidoc.gradle" // asciidoc (which is generated from root dir) apply from: "gradle/releaser.gradle" apply from: "gradle/dependencies.gradle" apply from: "gradle/toolchains.gradle" @@ -89,7 +86,8 @@ ext { } nohttp { - source.exclude "docs/asciidoc/highlight/**" + source.exclude "docs/modules/ROOT/assets/highlight/**" + source.exclude "docs/.gradle/**" source.exclude "**/build/reports/tests/**/*.html" allowlistFile = project.file('codequality/nohttp/allowlist.lines') } @@ -201,8 +199,6 @@ configure(subprojects) { p -> } } -assemble.dependsOn docsZip - configure(subprojects) { p -> // these apply once the above configure is done, but before project-specific build.gradle have applied apply plugin: "io.reactor.gradle.java-conventions" diff --git a/docs/build.gradle b/docs/build.gradle new file mode 100644 index 0000000000..7d4fc6dc0e --- /dev/null +++ b/docs/build.gradle @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2024 VMware, Inc. or its affiliates, All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +plugins { + alias(libs.plugins.antora) + alias(libs.plugins.antora.yml) +} + +def isCommandAvailable(String command) { + def result = exec { + commandLine 'which', command + ignoreExitValue true + standardOutput = new ByteArrayOutputStream() + errorOutput = new ByteArrayOutputStream() + } + return result.exitValue == 0 +} + +antora { + //version = "$antoraVersion" + version = libs.versions.antora.version + playbook = findProperty('antora.playbook') ?: "antora-playbook.yml" + options = ['--clean', '--stacktrace'] + + def version = project.version + def forcePdf = project.hasProperty('forcePdf') + + if (!version.endsWith("-SNAPSHOT") || forcePdf) { + if (isCommandAvailable('asciidoctor-pdf')) { + logger.log(LogLevel.DEBUG, "enabling antora pdf-extension") + options.add('--extension=pdf-extension') + } else { + logger.lifecycle("PDF not generated, asciidoctor-pdf not found from the PATH.") + } + } + + environment = [ + 'ALGOLIA_API_KEY' : 'd4bf9918bfc7d63ae68fbf92d69c2f49', + 'ALGOLIA_APP_ID' : '82SNR5M8HE', + 'ALGOLIA_INDEX_NAME': 'projectreactor' + ] + + dependencies = [ + '@antora/atlas-extension' : "${libs.versions.antora.atlas.extension.get()}", + '@antora/pdf-extension' : "${libs.versions.antora.pdf.extension.get()}", + '@antora/collector-extension' : "${libs.versions.antora.collector.extension.get()}", + '@asciidoctor/tabs' : "${libs.versions.antora.tabs.extension.get()}", + '@springio/antora-extensions' : "${libs.versions.antora.springio.antora.extension.get()}", + '@springio/asciidoctor-extensions': "${libs.versions.antora.asciidoctor.extension.get()}" + ] +} + +jar { + enabled = false +} + +javadoc { + enabled = false +} + +tasks.withType(AbstractPublishToMaven).configureEach { + enabled = false +} + +configurations { + adoc +} + +dependencies { + adoc(libs.micrometer.docsGenerator) +} + +task generateObservabilityDocs(dependsOn: [ + "generateMeterListenerDocs", + "generateTimedSchedulerDocs", + "generateObservationDocs", + "polishGeneratedMetricsDocs"]) { + outputs.dir(project.layout.buildDirectory.dir("documentedMetrics/").get().asFile.absolutePath) +} + +task generateMeterListenerDocs(type: JavaExec) { + def outputDir = project.layout.buildDirectory.dir("generatedMetricsDocs/meterListener").get().asFile.absolutePath + outputs.dir(outputDir) + mainClass.set("io.micrometer.docs.DocsGeneratorCommand") + classpath configurations.adoc + args project.rootDir.getAbsolutePath(), + ".*MicrometerMeterListenerDocumentation.*.java", + outputDir +} + +task generateTimedSchedulerDocs(type: JavaExec) { + def outputDir = project.layout.buildDirectory.dir("generatedMetricsDocs/timedScheduler").get().asFile.absolutePath + outputs.dir(outputDir) + mainClass.set("io.micrometer.docs.DocsGeneratorCommand") + classpath configurations.adoc + args project.rootDir.getAbsolutePath(), ".*TimedSchedulerMeterDocumentation.*.java", + outputDir +} + +task generateObservationDocs(type: JavaExec) { + def outputDir = project.layout.buildDirectory.dir("generatedMetricsDocs/observation").get().asFile.absolutePath + outputs.dir(outputDir) + mainClass.set("io.micrometer.docs.DocsGeneratorCommand") + classpath configurations.adoc + args project.rootDir.getAbsolutePath(), + ".*MicrometerObservationListenerDocumentation.*.java", + outputDir +} + +task polishGeneratedMetricsDocs(type: Copy) { + mustRunAfter "generateMeterListenerDocs" + mustRunAfter "generateTimedSchedulerDocs" + mustRunAfter "generateObservationDocs" + outputs.dir(project.layout.buildDirectory.dir("documentedMetrics").get().asFile.absolutePath) + + from(project.layout.buildDirectory.get().asFile.toString() + "/generatedMetricsDocs/meterListener/") { + include "_*.adoc" + rename '_(.*).adoc', 'meterListener_$1.adoc' + } + from(project.layout.buildDirectory.get().asFile.toString() + "/generatedMetricsDocs/timedScheduler/") { + include "_*.adoc" + rename '_(.*).adoc', 'timedScheduler_$1.adoc' + } + from(project.layout.buildDirectory.get().asFile.toString() + "/generatedMetricsDocs/observation/") { + include "_*.adoc" + rename '_(.*).adoc', 'observation_$1.adoc' + } + into project.layout.buildDirectory.get().asFile.toString() + "/documentedMetrics" + filter { String line -> + line.startsWith('[[observability-metrics]]') || + line.startsWith('=== Observability - Metrics') || + line.startsWith('Below you can find a list of all ') || + line.startsWith("Fully qualified name of the enclosing class ") + ? null : line + } + filter { String line -> line.startsWith("====") ? line.replaceFirst("====", "=") : line } + doLast { + //since these are the files that get explicitly included in asciidoc, smoke test they exist + assert file(project.layout.buildDirectory.get().asFile.toString() + "/documentedMetrics/meterListener_metrics.adoc").exists() + assert file(project.layout.buildDirectory.get().asFile.toString() + "/documentedMetrics/timedScheduler_metrics.adoc").exists() + assert file(project.layout.buildDirectory.get().asFile.toString() + "/documentedMetrics/observation_metrics.adoc").exists() + } +} + +tasks.create(name: 'createAntoraPartials', type: Sync) { + from { project(":docs").tasks.generateObservabilityDocs.outputs } + + // Download and include SUPPORT.adoc + doLast { + def url = 'https://raw.githubusercontent.com/reactor/.github/main/SUPPORT.adoc' + def outputFile = file("${buildDir}/generated-antora-resources/modules/ROOT/partials/SUPPORT.adoc") + ant.get(src: url, dest: outputFile) + } + into layout.buildDirectory.dir('generated-antora-resources/modules/ROOT/partials') +} + +tasks.named("generateAntoraYml") { + asciidocAttributes = project.provider({ generateAttributes() }) + baseAntoraYmlFile = file("antora.yml") +} + +tasks.create('generateAntoraResources') { + dependsOn 'createAntoraPartials' + dependsOn 'generateAntoraYml' +} + +def generateAttributes() { + return ['is-snapshot-version': project.version.endsWith("-SNAPSHOT"), + 'project-version' : project.version, + 'reactorReleaseTrain': bomVersion + ] +} + +task docsZip(type: Zip, dependsOn: ':docs:antora') { + archiveBaseName.set("reactor-core") + archiveClassifier.set('docs') + + def isSnapshot = project.version.endsWith('-SNAPSHOT') + def version = isSnapshot ? project.version.takeWhile { it != '-' } : project.version + boolean forcePdf = project.hasProperty('forcePdf') + + from('build/site') { + into 'docs' + } + + if (!isSnapshot || forcePdf) { + def pdfFile = file("build/assembler/reactor/${version}/reactor-3-reference-guide.pdf") + logger.lifecycle("${pdfFile} will be included in docs zip") + from(pdfFile) { + rename { fileName -> + "docs/reactor-core-reference-guide-${project.version}.pdf" + } + } + } +} + +description = "Reactor 3 Antora Docs" + +assemble.dependsOn docsZip + +// docsZip is added to publication in gradle/setup.gradle, see publications -> mavenJava -> afterEvaluate diff --git a/gradle/asciidoc.gradle b/gradle/asciidoc.gradle deleted file mode 100644 index 148497df49..0000000000 --- a/gradle/asciidoc.gradle +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (c) 2011-2021 VMware Inc. or its affiliates, All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -configure(rootProject) { - apply plugin: 'org.asciidoctor.jvm.convert' - apply plugin: 'org.asciidoctor.jvm.pdf' - - repositories { - maven { url 'https://repo.spring.io/snapshot' } - maven { url 'https://repo.spring.io/milestone' } - } - - // This configuration applies both to the asciidoctor & asciidoctorPdf tasks - asciidoctorj { - options = [doctype: 'book'] - attributes 'allow-uri-read': '', - 'attribute-missing': 'warn', - 'project-version': "${project.version}", - 'reactorReleaseTrain': "${bomVersion}" - } - - asciidoctor { - dependsOn "generateObservabilityDocs" - inputs.dir(layout.buildDirectory.dir("generatedMetricsDocs/").get().asFile) // force the task to consider changes in this folder, making it not UP-TO-DATE - sourceDir "docs/asciidoc/" - sources { - include "index.asciidoc" - } - baseDirFollowsSourceDir() - resources { - from(sourceDir) { - include 'images/**' - include 'highlight/**/*' - } - - } - outputDir layout.buildDirectory.dir("docs/asciidoc/html").get().asFile - logDocuments = true - attributes stylesdir: "stylesheets/", - stylesheet: 'reactor.css', - 'source-highlighter': 'highlightjs', - 'highlightjsdir': "./highlight", - 'highlightjs-theme': 'railscasts', - 'reactorReleaseTrain': "$bomVersion" - } - - asciidoctorPdf { - onlyIf { isCiServer || !rootProject.version.toString().endsWith("-SNAPSHOT") || rootProject.hasProperty("forcePdf") } - dependsOn "generateObservabilityDocs" - sourceDir "docs/asciidoc/" - sources { - include "index.asciidoc" - } - baseDirFollowsSourceDir() - outputDir layout.buildDirectory.dir("docs/asciidoc/pdf").get().asFile - logDocuments = true - attributes 'source-highlighter': 'rouge' - } - - task asciidocs(dependsOn: [asciidoctor, asciidoctorPdf], group: "documentation") { } - - task docsZip(type: Zip, dependsOn: asciidocs) { - archiveBaseName.set("reactor-core") - archiveClassifier.set('docs') - afterEvaluate() { - //we configure the pdf copy late, when a potential customVersion has been applied to rootProject - from(asciidoctorPdf) { - into ("docs/") - rename("index.pdf", "reactor-core-reference-guide-${rootProject.version}.pdf") - } - } - from(asciidoctor) { into("docs/") } - } - - configurations { - adoc - } - - dependencies { - adoc libs.micrometer.docsGenerator - } - - task generateObservabilityDocs(dependsOn: [ - "generateMeterListenerDocs", - "generateTimedSchedulerDocs", - "generateObservationDocs", - "polishGeneratedMetricsDocs"]) { - } - - task generateMeterListenerDocs(type: JavaExec) { - mainClass.set("io.micrometer.docs.DocsGeneratorCommand") - classpath configurations.adoc - args project.rootDir.getAbsolutePath(), - ".*MicrometerMeterListenerDocumentation.*.java", - project.rootProject.layout.buildDirectory.dir("generatedMetricsDocs/meterListener").get().asFile.absolutePath - } - - task generateTimedSchedulerDocs(type: JavaExec) { - mainClass.set("io.micrometer.docs.DocsGeneratorCommand") - classpath configurations.adoc - args project.rootDir.getAbsolutePath(), ".*TimedSchedulerMeterDocumentation.*.java", - project.rootProject.layout.buildDirectory.dir("generatedMetricsDocs/timedScheduler").get().asFile.absolutePath - } - - task generateObservationDocs(type: JavaExec) { - mainClass.set("io.micrometer.docs.DocsGeneratorCommand") - classpath configurations.adoc - args project.rootDir.getAbsolutePath(), - ".*MicrometerObservationListenerDocumentation.*.java", - project.rootProject.layout.buildDirectory.dir("generatedMetricsDocs/observation").get().asFile.absolutePath - } - - task polishGeneratedMetricsDocs(type: Copy) { - mustRunAfter "generateMeterListenerDocs" - mustRunAfter "generateTimedSchedulerDocs" - mustRunAfter "generateObservationDocs" - from(project.rootProject.layout.buildDirectory.get().asFile.toString() + "/generatedMetricsDocs/meterListener/") { - include "_*.adoc" - rename '_(.*).adoc', 'meterListener_$1.adoc' - } - from(project.rootProject.layout.buildDirectory.get().asFile.toString() + "/generatedMetricsDocs/timedScheduler/") { - include "_*.adoc" - rename '_(.*).adoc', 'timedScheduler_$1.adoc' - } - from(project.rootProject.layout.buildDirectory.get().asFile.toString() + "/generatedMetricsDocs/observation/") { - include "_*.adoc" - rename '_(.*).adoc', 'observation_$1.adoc' - } - into project.rootProject.layout.buildDirectory.get().asFile.toString() + "/documentedMetrics" - filter { String line -> - line.startsWith('[[observability-metrics]]') || - line.startsWith('=== Observability - Metrics') || - line.startsWith('Below you can find a list of all ') || - line.startsWith("Fully qualified name of the enclosing class ") - ? null : line - } - filter { String line -> line.startsWith("====") ? line.replaceFirst("====", "=") : line } - doLast { - //since these are the files that get explicitly included in asciidoc, smoke test they exist - assert file(project.rootProject.layout.buildDirectory.get().asFile.toString() + "/documentedMetrics/meterListener_metrics.adoc").exists() - assert file(project.rootProject.layout.buildDirectory.get().asFile.toString() + "/documentedMetrics/timedScheduler_metrics.adoc").exists() - assert file(project.rootProject.layout.buildDirectory.get().asFile.toString() + "/documentedMetrics/observation_metrics.adoc").exists() - } - } - -} - diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 42779a6489..d072810db7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,7 +12,6 @@ baselinePerfCore = "3.6.5" baselinePerfExtra = "3.5.1" # Other shared versions -asciidoctor = "4.0.2" #note that some micrometer artifacts like context-propagation has a different version directly set in libraries below micrometer = "1.12.5" micrometerDocsGenerator = "1.0.2" @@ -20,6 +19,15 @@ micrometerTracingTest="1.2.5" contextPropagation="1.1.1" kotlin = "1.8.22" reactiveStreams = "1.0.4" +antora = "1.0.0" +antora-yml = "0.0.1" +antora-version = "3.2.0-alpha.4" +antora-atlas-extension = "1.0.0-alpha.1" +antora-pdf-extension = "1.0.0-alpha.7" +antora-collector-extension = "1.0.0-alpha.3" +antora-tabs-extension = "1.0.0-beta.6" +antora-springio-antora-extension = "1.8.2" +antora-asciidoctor-extension = "1.0.0-alpha.9" [libraries] jsr166backport = "io.projectreactor:jsr166:1.0.0.RELEASE" @@ -42,8 +50,6 @@ reactor-perfBaseline-extra = { module = "io.projectreactor.addons:reactor-extra" [plugins] artifactory = { id = "com.jfrog.artifactory", version = "4.31.0" } -asciidoctor-convert = { id = "org.asciidoctor.jvm.convert", version.ref = "asciidoctor" } -asciidoctor-pdf = { id = "org.asciidoctor.jvm.pdf", version.ref = "asciidoctor" } bnd = { id = "biz.aQute.bnd.builder", version = "6.4.0" } download = { id = "de.undercouch.download", version = "5.6.0" } japicmp = { id = "me.champeau.gradle.japicmp", version = "0.4.2" } @@ -52,3 +58,5 @@ nohttp = { id = "io.spring.nohttp", version = "0.0.11" } shadow = { id = "com.github.johnrengelman.shadow", version = "8.1.1" } spotless = { id = "com.diffplug.spotless", version = "6.13.0" } mrjar = { id = "me.champeau.mrjar", version = "0.1.1" } +antora = { id = "org.antora", version.ref = "antora" } +antora-yml = { id = "io.spring.antora.generate-antora-yml", version.ref = "antora-yml" } diff --git a/gradle/setup.gradle b/gradle/setup.gradle index 5b6f72584f..2f4311aa53 100644 --- a/gradle/setup.gradle +++ b/gradle/setup.gradle @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2021 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2011-2024 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -112,10 +112,28 @@ publishing { artifact javadocJar //consider adding extra artifacts here, conditionally on submodule's name and perhaps in an afterEvaluate block afterEvaluate { - if (project.name == 'reactor-core') { - artifact rootProject.tasks.docsZip + // Find the docs project (it's not a java project, so we need to lookup it) + def docsProject = project.findProject(':docs') + if (docsProject) { + // Access the docsZip task from the docs project + def docsZipTask = docsProject.tasks.findByName('docsZip') + if (docsZipTask) { + // Add the docsZip task as an artifact + artifact docsZipTask + } + } + else { + // If the current JDK version is JDK8, the docs project is not loaded (see settings.gradle), so + // docsTask is not available. In this case, include the docsZip file path directly as an artifact, if it exists. + // (it may exist in case the docs have been previously built using a JDK17 compatible JDK). + def docsZipFile = file("${rootDir}/docs/build/distributions/reactor-core-${project.version}-docs.zip") + if (docsZipFile.exists()) { + artifact(docsZipFile) { + classifier 'docs' + } + } } - // note that reactor-tools has more involved stuff, so we kept it in the submodule's build: + // Note that reactor-tools has more involved stuff, so we kept it in the submodule's build: // (it replaces the original jar with shadow jar and adds the former as -original.jar) } diff --git a/settings.gradle b/settings.gradle index e9650f3a9d..370844064a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2023 VMware Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2011-2024 VMware Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,10 @@ rootProject.name = 'reactor' include 'benchmarks', 'reactor-core', 'reactor-test', 'reactor-tools', 'reactor-core-micrometer' +if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_17)) { + include 'docs' +} + dependencyResolutionManagement { versionCatalogs { libs {