From 6157479f06b6ffc54021ab814b8ce0ef82598710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Schalk=20W=2E=20Cronj=C3=A9?= Date: Sat, 3 Jun 2023 22:36:41 +0200 Subject: [PATCH] Update plugins to work with Grolifant 2.x This prepares 4.0.0 final release. It fully integrates with Grolifant 2.2 and adopts the newer JVM execution models too, whichs allows for better integration with Gradle workers. This has been so successful that the default execution model is no a worker with classpath isolation as previously it always has been javaexec. JRuby: jruby-gradle plugin has been replaced with jruby-simple-plugin. This eliminates a number of issues with the previous plugin in terms of functionality and compatibiliy. It also restores plugin compatibility with JDK8 whereas the older plugin requires JDK11. GEMs: The issues with load errors for external GEMs has been resolved, but eliminating the use of GEM_PATH and rather compiling a custom GEM Jar on the fly and placing it on the classpath. Disabled plugins: Some plugins were disabled for this release and will probably be fixed for future releases. Due to their limited usage, it was thought better to get a release out now and fix these later. The plugins are: - Leanpub - Slides - Slides export Other: - `asciidoctorEditorConfig` is now lazy-created - Update to NodeJs plugin 2.0.1 - Remove most deprecated Grolifant 2.0 APIs Known issues: - Supplying an extension via a configuration rather than via `docExtensions` does not work. - GEM resolver test on Windows is failing. - Asciidotor.convertFile. Curretnly the Asciidoctor API does not allow for inspection of the Option object and the old Map methods are all deprecated. Closes: #628, #626, #655, #658, #664, #671, #673, #684, i#692, #693 --- .github/workflows/build.yml | 12 +- .gitignore | 2 +- .sdkmanrc | 3 + README.adoc | 6 +- asciidoctoreditorconfig/build.gradle | 19 +- ...iidoctorEditorConfigIntegrationSpec.groovy | 14 +- .../internal/FunctionalSpecification.groovy | 45 +- .../AsciidoctorEditorConfigGenerator.groovy | 48 +- .../AsciidoctorEditorConfigPlugin.groovy | 18 +- .../org.asciidoctor.editorconfig.properties | 17 - .../AsciidoctorEditorConfigSpec.groovy | 2 +- base/build.gradle | 19 +- .../gradle/base/ApplyPluginSpec.groovy | 2 +- .../internal/FunctionalSpecification.groovy | 29 +- .../base/AbstractAsciidoctorBaseTask.groovy | 504 ++++++------ .../base/AbstractDownloadableComponent.groovy | 181 ----- .../base/AbstractDownloadableComponent.java | 192 +++++ ...stractImplementationEngineExtension.groovy | 36 +- ...Archive.groovy => AscGitHubArchive.groovy} | 18 +- ...Archive.groovy => AscGitLabArchive.groovy} | 18 +- .../base/AsciidoctorAttributeProvider.java | 2 +- .../gradle/base/AsciidoctorBasePlugin.groovy | 19 +- .../base/AsciidoctorExecutionException.groovy | 2 +- .../base/AsciidoctorModuleDefinition.java | 20 +- .../AsciidoctorMultiLanguageException.groovy | 2 +- .../base/AsciidoctorTaskAttributes.java | 85 ++ .../AsciidoctorTaskBaseDirConfiguration.java | 113 +++ .../base/AsciidoctorTaskFileOperations.java | 363 +++++++++ .../gradle/base/AsciidoctorTaskMethods.java | 27 + .../base/AsciidoctorTaskOutputOptions.java | 121 +++ ...ciidoctorTaskReportableConfigurations.java | 31 + .../base/AsciidoctorTaskTreeOperations.java | 99 +++ .../AsciidoctorTaskWorkspacePreparation.java | 59 ++ .../gradle/base/AsciidoctorUtils.groovy | 58 +- .../gradle/base/BaseDirStrategy.groovy | 2 +- .../base/ModuleNotFoundException.groovy | 2 +- .../gradle/base/ModuleVersionLoader.groovy | 5 +- .../gradle/base/OutputOptions.groovy | 2 +- .../asciidoctor/gradle/base/SafeMode.groovy | 2 +- .../asciidoctor/gradle/base/Transform.groovy | 2 +- .../base/basedir/BaseDirFollowsProject.groovy | 20 +- .../basedir/BaseDirFollowsRootProject.groovy | 4 +- .../base/basedir/BaseDirIsFixedPath.groovy | 2 +- .../gradle/base/basedir/BaseDirIsNull.groovy | 4 +- .../internal/AsciidoctorAttributes.groovy | 89 ++- .../base/internal/ConfigurationUtils.groovy | 64 -- ...aultAsciidoctorBaseDirConfiguration.groovy | 158 ++++ .../DefaultAsciidoctorFileOperations.groovy | 740 ++++++++++++++++++ .../DefaultAsciidoctorOutputOptions.groovy | 242 ++++++ ...aultAsciidoctorWorkspacePreparation.groovy | 144 ++++ .../base/internal/DeprecatedFeatures.groovy | 25 +- .../gradle/base/internal/Workspace.groovy | 2 +- .../base/internal/slides/ProfileUtils.groovy | 2 +- .../gradle/base/log/Severity.groovy | 2 +- .../gradle/base/process/ProcessMode.groovy | 22 +- .../gradle/base/slides/Profile.java | 2 +- .../base/slides/SlidesToExportAware.java | 2 +- .../org.asciidoctor.base.properties | 17 - ...ctImplementationEngineExtensionSpec.groovy | 4 +- .../base/AsciidoctorBasePluginSpec.groovy | 28 +- .../gradle/base/AsciidoctorUtilsSpec.groovy | 2 +- .../gradle/base/BaseTaskPatternSpec.groovy | 86 +- .../gradle/base/SafeModeSpec.groovy | 2 +- .../base/process/ProcessModeSpec.groovy | 2 +- .../gradle/base/slides/ProfileSpec.groovy | 2 +- ...rg.asciidoctor.gradle.base.test.properties | 2 +- build.gradle | 56 +- buildSrc/build.gradle | 2 + .../AsciidoctorGradleGroovyProject.groovy | 8 +- .../AsciidoctorGradlePluginProject.groovy | 54 +- .../AsciidoctorGradleProjectExtension.groovy | 89 +++ config/codenarc/codenarc.groovy | 10 +- docs/src/docs/asciidoc/index.adoc | 2 +- .../parts/common-task-configuration.adoc | 4 +- gems/build.gradle | 29 +- .../src/gradleTest/external-gems/build.gradle | 8 +- ...GemPrepareTaskCachingFunctionalSpec.groovy | 28 +- .../internal/FunctionalSpecification.groovy | 53 +- .../jvm/gems/AsciidoctorGemPrepare.groovy | 48 +- .../gems/AsciidoctorGemSupportPlugin.groovy | 49 +- .../org.asciidoctor.jvm.gems.properties | 17 - .../AsciidoctorGemSupportPluginSpec.groovy | 5 +- gradle.properties | 17 +- gradle/compatibility-tests.gradle | 7 +- gradle/gradle-plugin-dependencies.gradle | 17 - gradle/integration-tests.gradle | 20 +- gradle/publishing.gradle | 136 +--- gradle/remote-tests.gradle | 39 + gradle/wrapper/gradle-wrapper.jar | Bin 58910 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 257 +++--- gradlew.bat | 21 +- js/build.gradle | 56 +- .../AsciidoctorTaskFunctionalSpec.groovy | 26 +- .../internal/FunctionalSpecification.groovy | 49 +- .../AbstractAsciidoctorJSExtension.groovy | 10 +- .../js/base/AbstractAsciidoctorTask.groovy | 2 +- .../js/base/AsciidoctorJSModules.groovy | 27 +- .../base/internal/AsciidoctorJSModule.groovy | 54 +- .../internal/BaseAsciidoctorJSModules.groovy | 61 +- .../base/internal/VersionUpdateTrigger.java | 32 + .../AbstractAsciidoctorNodeJSTask.groovy | 41 +- .../js/nodejs/AsciidoctorJSExtension.groovy | 151 +++- .../js/nodejs/AsciidoctorNodeJSPlugin.groovy | 27 +- .../gradle/js/nodejs/AsciidoctorTask.groovy | 5 +- .../core/AsciidoctorJSNodeExtension.groovy | 52 ++ .../AsciidoctorJSNpmExtension.groovy | 27 +- .../AsciidoctorNodeJSBasePlugin.groovy | 22 +- .../js/nodejs/core/NodeJSBasePlugin.groovy | 34 - .../core/NodeJSDependencyFactory.groovy | 32 +- .../gradle/js/nodejs/core/NodeJSUtils.groovy | 30 +- .../internal/AsciidoctorJSRunner.groovy | 64 +- .../internal/AsciidoctorNodeJSModules.groovy | 64 +- .../nodejs/internal/PackageDescriptor.groovy | 2 +- .../org.asciidoctor.js.base.properties | 17 - .../org.asciidoctor.js.convert.properties | 17 - .../org.asciidoctor.js.nodejs-base.properties | 17 - .../nodejs/AsciidoctorJSExtensionSpec.groovy | 2 +- .../js/nodejs/AsciidoctorTaskSpec.groovy | 2 +- jvm-epub/build.gradle | 25 +- jvm-epub/src/gradleTest/epub3/build.gradle | 2 +- .../src/docs/asciidoc/chapter1.adoc | 6 - .../src/docs/asciidoc/chapter2.adoc | 9 - .../src/docs/asciidoc/epub3.adoc | 6 - ...doctorEpubTaskCachingFunctionalSpec.groovy | 22 +- .../AsciidoctorEpubTaskFunctionalSpec.groovy | 21 +- .../epub/LinkedChaptersFunctionalSpec.groovy | 16 +- .../internal/FunctionalSpecification.groovy | 37 +- .../jvm/epub/AsciidoctorEpubTask.groovy | 76 +- .../jvm/epub/AsciidoctorJEpubPlugin.groovy | 30 +- .../org.asciidoctor.jvm.epub.properties | 17 - jvm-leanpub/build.gradle | 35 +- ...sciidoctorLeanpubTaskFunctionalSpec.groovy | 47 +- .../internal/FunctionalSpecification.groovy | 64 +- ...sciidoctorJLeanpubDropboxCopyPlugin.groovy | 31 +- .../leanpub/AsciidoctorJLeanpubPlugin.groovy | 32 +- .../jvm/leanpub/AsciidoctorLeanpubTask.groovy | 22 +- .../gradle/jvm/leanpub/DropboxCopyTask.groovy | 22 +- ...doctor.jvm.leanpub.dropbox-copy.properties | 17 - .../org.asciidoctor.jvm.leanpub.properties | 17 - .../AsciidoctorJLeanpubPluginSpec.groovy | 2 +- .../jvm/leanpub/DropboxCopyTaskSpec.groovy | 2 +- jvm-pdf/build.gradle | 30 +- .../gradleTest/complex-jvm-setup/build.gradle | 2 +- .../complex-jvm-setup/build.gradle.kts | 5 +- ...idoctorPdfTaskCachingFunctionalSpec.groovy | 30 +- .../AsciidoctorPdfTaskFunctionalSpec.groovy | 47 +- .../internal/FunctionalSpecification.groovy | 61 +- .../jvm/pdf/AsciidoctorJPdfPlugin.groovy | 2 +- .../gradle/jvm/pdf/AsciidoctorPdfTask.groovy | 133 +--- .../pdf/AsciidoctorPdfThemesExtension.groovy | 34 +- .../gradle/jvm/pdf/PdfFontDirException.groovy | 2 +- .../org.asciidoctor.jvm.pdf.properties | 17 - .../AsciidoctorPdfThemeExtensionSpec.groovy | 10 +- jvm/build.gradle | 116 +-- .../complex-jvm-setup/build.gradle.kts | 2 +- .../extension-in-subproject/build.gradle | 17 +- .../build.gradle | 23 +- .../docs-using-configuration/build.gradle | 11 +- .../docs-using-project-direct/build.gradle | 6 +- .../internal/FunctionalSpecification.groovy | 44 +- ...sciidoctorTaskCachingFunctionalSpec.groovy | 74 +- .../jvm/AsciidoctorTaskFunctionalSpec.groovy | 22 +- .../jvm/ExtensionsFunctionalSpec.groovy | 58 +- .../jvm/MultiLanguageFunctionalSpec.groovy | 64 +- .../jvm/RelativeIncludeFunctionalSpec.groovy | 4 +- .../gradle/jvm/RequiresFunctionalSpec.groovy | 10 +- .../gradle/jvm/ResourcesFunctionalSpec.groovy | 21 +- .../jvm/WarningsAsErrorsFunctionalSpec.groovy | 6 +- .../AsciidoctorExecutorFactory.groovy | 39 + .../AsciidoctorWorkerParameterFactory.groovy | 50 ++ .../AsciidoctorWorkerParameters.groovy | 41 + .../DefaultAsciidoctorJModules.groovy} | 83 +- .../internal/ExecutorConfiguration.groovy | 4 +- .../ExecutorConfigurationContainer.groovy | 6 +- .../gradle/internal/ExecutorLogLevel.groovy | 2 +- .../gradle/internal/ExecutorUtils.groovy | 2 +- .../gradle/internal/JavaExecUtils.groovy | 93 ++- .../gradle/jvm/AbstractAsciidoctorTask.groovy | 589 +++++++------- .../gradle/jvm/AsciidoctorJBasePlugin.groovy | 2 +- .../gradle/jvm/AsciidoctorJExtension.groovy | 552 +++++++------ .../gradle/jvm/AsciidoctorJModules.java | 131 ++++ .../gradle/jvm/AsciidoctorJPlugin.groovy | 6 +- .../gradle/jvm/AsciidoctorJvmExecSpec.groovy | 28 +- .../gradle/jvm/AsciidoctorTask.groovy | 32 +- .../gradle/remote/AsciidoctorJExecuter.groovy | 221 ------ .../remote/AsciidoctorJLogProcessor.groovy | 167 ++++ .../gradle/remote/AsciidoctorJSetup.groovy | 122 +++ .../gradle/remote/AsciidoctorJavaExec.groovy | 24 +- ...AsciidoctorRemoteExecutionException.groovy | 2 +- .../remote/AsciidoctorWorkerExecutor.groovy | 116 +++ .../gradle/remote/ExecutorBase.groovy | 66 +- .../gradle/remote/LogSeverityMapper.groovy | 8 +- .../org.asciidoctor.jvm.base.properties | 17 - .../org.asciidoctor.jvm.convert.properties | 17 - .../remote/AsciidoctorJExecutorSpec.groovy | 53 -- .../remote/AsciidoctorJavaExecSpec.groovy | 12 +- ...idoctorRemoteExecutionExceptionSpec.groovy | 2 +- .../remote/LogSeverityMapperSpec.groovy | 2 +- .../internal/RemoteSpecification.groovy | 59 +- .../internal/ExecutorConfigurationSpec.groovy | 3 +- .../gradle/internal/JavaExecUtilsSpec.groovy | 27 +- .../jvm/AsciidoctorJBasePluginSpec.groovy | 2 +- .../jvm/AsciidoctorJExtensionSpec.groovy | 81 -- .../gradle/jvm/AsciidoctorTaskSpec.groovy | 202 ++--- .../resources/src/asciidoc/docinfo-footer.xml | 2 +- .../test/resources/src/asciidoc/docinfo.xml | 2 +- .../src/asciidoc/sample-docinfo-footer.xml | 2 +- .../resources/src/asciidoc/sample-docinfo.xml | 2 +- .../test/resources/src/asciidoc/secondary.txt | 2 +- .../src/asciidocextensions/blockMacro.groovy | 2 +- module-versions.properties | 6 +- settings.gradle | 33 +- .../export/base/AbstractExportBaseTask.groovy | 19 +- .../export/decktape/DeckTapeExtension.groovy | 15 +- .../export/decktape/DeckTapeTask.groovy | 28 +- testfixtures/jvm/build.gradle | 7 +- .../AsciidoctorjTestVersions.groovy | 2 +- .../AsciidoctorjsTestVersions.groovy | 2 +- .../testfixtures/BuildScanFixture.groovy | 46 ++ ...gTest.groovy => CachingTestFixture.groovy} | 66 +- .../gradle/testfixtures/DslType.groovy | 2 +- .../testfixtures/FunctionalTestFixture.groovy | 107 +++ .../testfixtures/FunctionalTestSetup.groovy | 2 +- .../testfixtures/GradleTestVersions.groovy | 8 +- .../testfixtures/JRubyTestVersions.groovy | 2 +- ...idoctorjVersionProcessModeGenerator.groovy | 2 +- .../AsciidoctorjsVersionGenerator.groovy | 2 +- ...ubyAsciidoctorJCombinationGenerator.groovy | 2 +- .../generators/ProcessGenerator.groovy | 2 +- .../internal/TestFixtureVersionLoader.groovy | 5 +- testfixtures/offline-repo/build.gradle | 10 +- 232 files changed, 6537 insertions(+), 3886 deletions(-) create mode 100644 .sdkmanrc delete mode 100644 asciidoctoreditorconfig/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.editorconfig.properties delete mode 100644 base/src/main/groovy/org/asciidoctor/gradle/base/AbstractDownloadableComponent.groovy create mode 100644 base/src/main/groovy/org/asciidoctor/gradle/base/AbstractDownloadableComponent.java rename base/src/main/groovy/org/asciidoctor/gradle/base/{GitHubArchive.groovy => AscGitHubArchive.groovy} (64%) rename base/src/main/groovy/org/asciidoctor/gradle/base/{GitLabArchive.groovy => AscGitLabArchive.groovy} (64%) create mode 100644 base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorTaskAttributes.java create mode 100644 base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorTaskBaseDirConfiguration.java create mode 100644 base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorTaskFileOperations.java create mode 100644 base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorTaskMethods.java create mode 100644 base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorTaskOutputOptions.java create mode 100644 base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorTaskReportableConfigurations.java create mode 100644 base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorTaskTreeOperations.java create mode 100644 base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorTaskWorkspacePreparation.java delete mode 100644 base/src/main/groovy/org/asciidoctor/gradle/base/internal/ConfigurationUtils.groovy create mode 100644 base/src/main/groovy/org/asciidoctor/gradle/base/internal/DefaultAsciidoctorBaseDirConfiguration.groovy create mode 100644 base/src/main/groovy/org/asciidoctor/gradle/base/internal/DefaultAsciidoctorFileOperations.groovy create mode 100644 base/src/main/groovy/org/asciidoctor/gradle/base/internal/DefaultAsciidoctorOutputOptions.groovy create mode 100644 base/src/main/groovy/org/asciidoctor/gradle/base/internal/DefaultAsciidoctorWorkspacePreparation.groovy rename base/src/main/{java => groovy}/org/asciidoctor/gradle/base/slides/Profile.java (97%) rename base/src/main/{java => groovy}/org/asciidoctor/gradle/base/slides/SlidesToExportAware.java (94%) delete mode 100644 base/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.base.properties create mode 100644 buildSrc/src/main/groovy/AsciidoctorGradleProjectExtension.groovy delete mode 100644 gems/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.jvm.gems.properties delete mode 100644 gradle/gradle-plugin-dependencies.gradle create mode 100644 gradle/remote-tests.gradle create mode 100644 js/src/main/groovy/org/asciidoctor/gradle/js/base/internal/VersionUpdateTrigger.java create mode 100644 js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/core/AsciidoctorJSNodeExtension.groovy rename js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/{ => core}/AsciidoctorJSNpmExtension.groovy (50%) rename js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/{ => core}/AsciidoctorNodeJSBasePlugin.groovy (54%) delete mode 100644 js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/core/NodeJSBasePlugin.groovy delete mode 100644 js/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.js.base.properties delete mode 100644 js/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.js.convert.properties delete mode 100644 js/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.js.nodejs-base.properties delete mode 100644 jvm-epub/src/gradleTest/issue-409-link-regression/src/docs/asciidoc/chapter1.adoc delete mode 100644 jvm-epub/src/gradleTest/issue-409-link-regression/src/docs/asciidoc/chapter2.adoc delete mode 100644 jvm-epub/src/gradleTest/issue-409-link-regression/src/docs/asciidoc/epub3.adoc delete mode 100644 jvm-epub/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.jvm.epub.properties delete mode 100644 jvm-leanpub/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.jvm.leanpub.dropbox-copy.properties delete mode 100644 jvm-leanpub/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.jvm.leanpub.properties delete mode 100644 jvm-pdf/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.jvm.pdf.properties create mode 100644 jvm/src/main/groovy/org/asciidoctor/gradle/internal/AsciidoctorExecutorFactory.groovy create mode 100644 jvm/src/main/groovy/org/asciidoctor/gradle/internal/AsciidoctorWorkerParameterFactory.groovy create mode 100644 jvm/src/main/groovy/org/asciidoctor/gradle/internal/AsciidoctorWorkerParameters.groovy rename jvm/src/main/groovy/org/asciidoctor/gradle/{jvm/AsciidoctorJModules.groovy => internal/DefaultAsciidoctorJModules.groovy} (57%) create mode 100644 jvm/src/main/groovy/org/asciidoctor/gradle/jvm/AsciidoctorJModules.java rename js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/AsciidoctorJSNodeExtension.groovy => jvm/src/main/groovy/org/asciidoctor/gradle/jvm/AsciidoctorJvmExecSpec.groovy (54%) delete mode 100644 jvm/src/main/groovy/org/asciidoctor/gradle/remote/AsciidoctorJExecuter.groovy create mode 100644 jvm/src/main/groovy/org/asciidoctor/gradle/remote/AsciidoctorJLogProcessor.groovy create mode 100644 jvm/src/main/groovy/org/asciidoctor/gradle/remote/AsciidoctorJSetup.groovy create mode 100644 jvm/src/main/groovy/org/asciidoctor/gradle/remote/AsciidoctorWorkerExecutor.groovy delete mode 100644 jvm/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.jvm.base.properties delete mode 100644 jvm/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.jvm.convert.properties delete mode 100644 jvm/src/remoteTest/groovy/org/asciidoctor/gradle/remote/AsciidoctorJExecutorSpec.groovy delete mode 100644 jvm/src/test/groovy/org/asciidoctor/gradle/jvm/AsciidoctorJExtensionSpec.groovy create mode 100644 testfixtures/jvm/src/main/groovy/org/asciidoctor/gradle/testfixtures/BuildScanFixture.groovy rename testfixtures/jvm/src/main/groovy/org/asciidoctor/gradle/testfixtures/{CachingTest.groovy => CachingTestFixture.groovy} (68%) create mode 100644 testfixtures/jvm/src/main/groovy/org/asciidoctor/gradle/testfixtures/FunctionalTestFixture.groovy diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ea1fff6c1..008252632 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -46,10 +46,11 @@ jobs: with: arguments: --console=plain --warning-mode=all -s clean assemble # Test + # TODO: Resolve the gem integration test issue. See https://github.com/asciidoctor/asciidoctor-gradle-plugin/issues/694 - name: Test uses: gradle/gradle-build-action@v2 with: - arguments: --console=plain --warning-mode=all -s check --no-parallel -Djava.net.preferIPv4Stack=true -x gradleTest --scan + arguments: --console=plain --warning-mode=all -s check --no-parallel -Djava.net.preferIPv4Stack=true -x gradleTest -x :asciidoctor-gradle-jvm-gems:IntTest --scan # Stop gradlew to avoid locking issues - name: Cleanup uses: gradle/gradle-build-action@v2 @@ -88,13 +89,16 @@ jobs: - name: Integration tests (without slides) uses: gradle/gradle-build-action@v2 with: - arguments: -i -s --console=plain --no-build-cache test intTest remoteTest -x asciidoctor-gradle-slides-export:intTest -x asciidoctor-gradle-jvm-slides:intTest + arguments: -i -s --console=plain --no-build-cache test intTest remoteTest --scan +# arguments: -i -s --console=plain --no-build-cache test intTest remoteTest -x asciidoctor-gradle-jvm-slides:intTest +# arguments: -i -s --console=plain --no-build-cache test intTest remoteTest -x asciidoctor-gradle-slides-export:intTest -x asciidoctor-gradle-jvm-slides:intTest +# TODO: See https://github.com/asciidoctor/asciidoctor-gradle-plugin/issues/695 # - name: Integration tests (slides only) -# uses: eskatos/gradle-command-action@v1 +# uses: eskatos/gradle-command-action@v2 # with: # arguments: -i -s --console=plain --no-build-cache test asciidoctor-gradle-jvm-slides:intTest asciidoctor-gradle-slides-export:intTest # Gradle tests - name: Gradle tests uses: gradle/gradle-build-action@v2 with: - arguments: -i -s --console=plain --no-build-cache gradleTest + arguments: -i -s --console=plain --no-build-cache gradleTest --scan diff --git a/.gitignore b/.gitignore index 8a1d97113..4c2c5bc53 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,7 @@ out buildSrc/gradle/wrapper buildSrc/gradlew* .asciidoctor-module-versions.generated - +.generated-src/ # Because we auto-generate this from the main project # and it is only needed for IntelliJ docs/gradle/wrapper diff --git a/.sdkmanrc b/.sdkmanrc new file mode 100644 index 000000000..81a24fadd --- /dev/null +++ b/.sdkmanrc @@ -0,0 +1,3 @@ + # Enable auto-env through the sdkman_auto_env config +# Add key=value pairs of SDKs to use below +java=8.0.302-open diff --git a/README.adoc b/README.adoc index 14288d6bd..12ec96d94 100644 --- a/README.adoc +++ b/README.adoc @@ -1,7 +1,7 @@ = Asciidoctor Gradle Plugin Andres Almiray -:version: 3.3.2 -:version-published: 3.3.2 +:version: 4.0.0 +:version-published: 4.0.0-alpha.1 :asciidoc-url: http://asciidoc.org :asciidoctor-url: http://asciidoctor.org :issues: https://github.com/asciidoctor/asciidoctor-maven-plugin/issues @@ -20,7 +20,7 @@ Andres Almiray :plugin-name: Asciidoctor Gradle plugin :project-name: asciidoctor-gradle-plugin :project-full-path: asciidoctor/asciidoctor-gradle-plugin -:github-branch: development-3.x +:github-branch: development-4.x :linkattrs: ifndef::env-github[:icons: font] ifdef::env-github,env-browser[] diff --git a/asciidoctoreditorconfig/build.gradle b/asciidoctoreditorconfig/build.gradle index 82a5b86d6..a5442dc69 100644 --- a/asciidoctoreditorconfig/build.gradle +++ b/asciidoctoreditorconfig/build.gradle @@ -1,5 +1,13 @@ -configurations { - additionalPluginClasspath +agProject { + withAdditionalPluginClasspath() + + configurePlugin( + 'org.asciidoctor.editorconfig', + 'Asciidoctor Editor Config Plugin', + 'Generate .asciidoctorconfig files for use by supported IDEs', + 'org.asciidoctor.gradle.editorconfig.AsciidoctorEditorConfigPlugin', + ['intellij', 'idea'] + ) } dependencies { @@ -7,12 +15,5 @@ dependencies { additionalPluginClasspath project(':asciidoctor-gradle-jvm') } -pluginUnderTestMetadata { - pluginClasspath.from configurations.additionalPluginClasspath -} -configurePlugin 'org.asciidoctor.editorconfig', - 'Asciidoctor Editor Config Plugin', - 'Generate .asciidoctorconfig files for use by supported IDEs', - ['asciidoctor', 'intellij', 'idea'] diff --git a/asciidoctoreditorconfig/src/intTest/groovy/org/asciidoctor/gradle/editorconfig/AsciidoctorEditorConfigIntegrationSpec.groovy b/asciidoctoreditorconfig/src/intTest/groovy/org/asciidoctor/gradle/editorconfig/AsciidoctorEditorConfigIntegrationSpec.groovy index a9e9e880c..4711b45dc 100644 --- a/asciidoctoreditorconfig/src/intTest/groovy/org/asciidoctor/gradle/editorconfig/AsciidoctorEditorConfigIntegrationSpec.groovy +++ b/asciidoctoreditorconfig/src/intTest/groovy/org/asciidoctor/gradle/editorconfig/AsciidoctorEditorConfigIntegrationSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,7 @@ class AsciidoctorEditorConfigIntegrationSpec extends FunctionalSpecification { String groupName = 'the.group' String projVer = '1.0.0' - File attrFile = new File(testProjectDir.root, 'inputs.adoc') + File attrFile = new File(projectDir, 'inputs.adoc') attrFile.text = ":${key3}: ${value3}\n" getGroovyBuildFile(""" @@ -51,18 +51,18 @@ class AsciidoctorEditorConfigIntegrationSpec extends FunctionalSpecification { } """) - File outputFile = new File(testProjectDir.root, '.asciidoctorconfig') - new File(testProjectDir.root, 'settings.gradle').text = "rootProject.name='${projName}'" + File outputFile = new File(projectDir, '.asciidoctorconfig') + settingsFile.text = "rootProject.name='${projName}'" when: getGradleRunner(['asciidoctorEditorConfig']).build() then: normalisedLineEndings(outputFile.text) == """:${key1}: ${value1} -:gradle-project-name: ${projName} -:gradle-project-group: ${groupName} :gradle-project-version: ${projVer} +:gradle-project-name: ${projName} :${key2}: ${value2} +:gradle-project-group: ${groupName} :${key3}: ${value3} """ } @@ -70,4 +70,4 @@ class AsciidoctorEditorConfigIntegrationSpec extends FunctionalSpecification { String normalisedLineEndings(String text) { text.replaceAll('\\r', '') } -} \ No newline at end of file +} diff --git a/asciidoctoreditorconfig/src/intTest/groovy/org/asciidoctor/gradle/editorconfig/internal/FunctionalSpecification.groovy b/asciidoctoreditorconfig/src/intTest/groovy/org/asciidoctor/gradle/editorconfig/internal/FunctionalSpecification.groovy index d8299fdb8..e3878f43c 100644 --- a/asciidoctoreditorconfig/src/intTest/groovy/org/asciidoctor/gradle/editorconfig/internal/FunctionalSpecification.groovy +++ b/asciidoctoreditorconfig/src/intTest/groovy/org/asciidoctor/gradle/editorconfig/internal/FunctionalSpecification.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,56 +17,50 @@ package org.asciidoctor.gradle.editorconfig.internal import groovy.transform.CompileStatic import org.apache.commons.io.FileUtils -import org.asciidoctor.gradle.testfixtures.DslType +import org.asciidoctor.gradle.testfixtures.FunctionalTestFixture import org.asciidoctor.gradle.testfixtures.FunctionalTestSetup import org.gradle.testkit.runner.GradleRunner -import org.junit.Rule -import org.junit.rules.TemporaryFolder import spock.lang.Specification +import spock.lang.TempDir import static org.asciidoctor.gradle.testfixtures.DslType.GROOVY_DSL import static org.asciidoctor.gradle.testfixtures.DslType.KOTLIN_DSL -import static org.asciidoctor.gradle.testfixtures.FunctionalTestSetup.getOfflineRepositoriesGroovyDsl -import static org.asciidoctor.gradle.testfixtures.FunctionalTestSetup.getOfflineRepositoriesKotlinDsl -class FunctionalSpecification extends Specification { +class FunctionalSpecification extends Specification implements FunctionalTestFixture { public static final String TEST_PROJECTS_DIR = System.getProperty( - 'TEST_PROJECTS_DIR', - './asciidoctoreditorconfig/src/intTest/projects' + 'TEST_PROJECTS_DIR', + './asciidoctoreditorconfig/src/intTest/projects' ) public static final String TEST_REPO_DIR = System.getProperty( - 'OFFLINE_REPO', - './testfixtures/offline-repo/build/repo' + 'OFFLINE_REPO', + './testfixtures/offline-repo/build/repo' ) - @Rule - TemporaryFolder testProjectDir + @TempDir + File testProjectDir + + void setup() { + projectDir.mkdirs() + } @CompileStatic GradleRunner getGradleRunner(List taskNames = ['tasks']) { - FunctionalTestSetup.getGradleRunner(GROOVY_DSL, testProjectDir.root, taskNames) + FunctionalTestSetup.getGradleRunner(GROOVY_DSL, projectDir, taskNames) } @CompileStatic GradleRunner getGradleRunnerForKotlin(List taskNames = ['tasks']) { - FunctionalTestSetup.getGradleRunner(KOTLIN_DSL, testProjectDir.root, taskNames) + FunctionalTestSetup.getGradleRunner(KOTLIN_DSL, projectDir, taskNames) } @SuppressWarnings(['BuilderMethodWithSideEffects']) void createTestProject(String docGroup = 'normal') { - FileUtils.copyDirectory(new File(TEST_PROJECTS_DIR, docGroup), testProjectDir.root) - } - - @CompileStatic - String getOfflineRepositories(DslType dslType = GROOVY_DSL) { - dslType == GROOVY_DSL ? getOfflineRepositoriesGroovyDsl(new File(TEST_REPO_DIR)) : - getOfflineRepositoriesKotlinDsl(new File(TEST_REPO_DIR)) + FileUtils.copyDirectory(new File(TEST_PROJECTS_DIR, docGroup), projectDir) } File getGroovyBuildFile(String extraContent, String plugin = 'org.asciidoctor.editorconfig') { - File buildFile = testProjectDir.newFile('build.gradle') buildFile << """ plugins { id '${plugin}' @@ -80,8 +74,7 @@ class FunctionalSpecification extends Specification { } File getKotlinBuildFile(String extraContent, String plugin = 'org.asciidoctor.editorconfig') { - File buildFile = testProjectDir.newFile('build.gradle.kts') - buildFile << """ + buildFileKts << """ plugins { id("${plugin}") } @@ -90,7 +83,7 @@ class FunctionalSpecification extends Specification { ${extraContent} """ - buildFile + buildFileKts } } \ No newline at end of file diff --git a/asciidoctoreditorconfig/src/main/groovy/org/asciidoctor/gradle/editorconfig/AsciidoctorEditorConfigGenerator.groovy b/asciidoctoreditorconfig/src/main/groovy/org/asciidoctor/gradle/editorconfig/AsciidoctorEditorConfigGenerator.groovy index 5ba3e502b..a36f20911 100644 --- a/asciidoctoreditorconfig/src/main/groovy/org/asciidoctor/gradle/editorconfig/AsciidoctorEditorConfigGenerator.groovy +++ b/asciidoctoreditorconfig/src/main/groovy/org/asciidoctor/gradle/editorconfig/AsciidoctorEditorConfigGenerator.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,12 +26,12 @@ import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.PathSensitive import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.TaskAction +import org.ysb33r.grolifant.api.core.ProjectOperations import java.util.concurrent.Callable -import static org.ysb33r.grolifant.api.v4.MapUtils.stringizeValues - -/** Generates {@code .asciidoctorconfig} file. +/** + * Generates {@code .asciidoctorconfig} file. * * When the file is generated attributes are applied in the following order. *
    @@ -51,16 +51,19 @@ class AsciidoctorEditorConfigGenerator extends DefaultTask { private final List> fileProviders = [] private final List>> attributeProviders = [] private final Provider outputFile + private final ProjectOperations projectOperations private Object outputDir AsciidoctorEditorConfigGenerator() { + this.projectOperations = ProjectOperations.find(project) this.outputDir = project.projectDir this.outputFile = project.provider({ new File(destinationDir, '.asciidoctorconfig') } as Callable) } - /** Replace existing attributes with a new set. + /** + * Replace existing attributes with a new set. * * @param attrs Replacement attributes */ @@ -69,7 +72,8 @@ class AsciidoctorEditorConfigGenerator extends DefaultTask { this.attributes.putAll(attrs) } - /** Add more attributes to the existing set + /** + * Add more attributes to the existing set * * @param attrs Additional attributes. */ @@ -83,23 +87,23 @@ class AsciidoctorEditorConfigGenerator extends DefaultTask { */ @Input Map getAttributes() { - stringizeValues(this.attributes) + projectOperations.stringTools.stringizeValues(this.attributes) } - /** Add an additional attribute provider. + /** + * Add an additional attribute provider. * * A provider can be a file of something that implements {@link AsciidoctorAttributeProvider} (such as * an {@code asciidoctorj} or {@code asciidoctorjs extension}). * - * * @param attrs Anything convertible to a file using {@code project.file} or that implements * {@link AsciidoctorAttributeProvider}. */ void additionalAttributes(Object attrs) { switch (attrs) { case AsciidoctorAttributeProvider: - this.attributeProviders.add(project.provider({ - stringizeValues(((AsciidoctorAttributeProvider) attrs).attributes) + this.attributeProviders.add(projectOperations.provider({ + projectOperations.stringTools.stringizeValues(((AsciidoctorAttributeProvider) attrs).attributes) } as Callable>)) break default: @@ -109,7 +113,8 @@ class AsciidoctorEditorConfigGenerator extends DefaultTask { } } - /** Returns list of file providers. + /** + * Returns list of file providers. * * Content of these files will simply be appended to the genrated content. * @@ -121,7 +126,8 @@ class AsciidoctorEditorConfigGenerator extends DefaultTask { this.fileProviders } - /** Returns list of attribute providers. THese providers will return attributes as key-value pairs. + /** + * Returns list of attribute providers. THese providers will return attributes as key-value pairs. * * @return List of attribute providers */ @@ -130,16 +136,18 @@ class AsciidoctorEditorConfigGenerator extends DefaultTask { this.attributeProviders } - /** Destination directory. Defaults to the project directory. + /** + * Destination directory. Defaults to the project directory. * * @return Directory */ @Internal File getDestinationDir() { - project.file(this.outputDir) + projectOperations.fsOperations.file(this.outputDir) } - /** Sets destination directory. + /** + * Sets destination directory. * * @param dir Anything convertible to a directory using {@code project.file}. */ @@ -147,7 +155,8 @@ class AsciidoctorEditorConfigGenerator extends DefaultTask { this.outputDir = dir } - /** Location of generated {@code .asciidoctorconfig} file. + /** + * Location of generated {@code .asciidoctorconfig} file. * * @return File location. */ @@ -159,8 +168,9 @@ class AsciidoctorEditorConfigGenerator extends DefaultTask { @TaskAction void exec() { outputFile.get().withWriter { w -> - getAttributes().each { k, v -> - w.println ":${k}: ${v}" + Map attrs = getAttributes() + attrs.keySet().sort().each { String k -> + w.println ":${k}: ${attrs[k]}" } additionalAttributeProviders.each { prov -> diff --git a/asciidoctoreditorconfig/src/main/groovy/org/asciidoctor/gradle/editorconfig/AsciidoctorEditorConfigPlugin.groovy b/asciidoctoreditorconfig/src/main/groovy/org/asciidoctor/gradle/editorconfig/AsciidoctorEditorConfigPlugin.groovy index c63f055ec..a64686184 100644 --- a/asciidoctoreditorconfig/src/main/groovy/org/asciidoctor/gradle/editorconfig/AsciidoctorEditorConfigPlugin.groovy +++ b/asciidoctoreditorconfig/src/main/groovy/org/asciidoctor/gradle/editorconfig/AsciidoctorEditorConfigPlugin.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ package org.asciidoctor.gradle.editorconfig import groovy.transform.CompileStatic import org.gradle.api.Plugin import org.gradle.api.Project +import org.ysb33r.grolifant.api.core.ProjectOperations /** Asciidoctor editorConfig plugin. * @@ -31,17 +32,10 @@ class AsciidoctorEditorConfigPlugin implements Plugin { @Override void apply(Project project) { - AsciidoctorEditorConfigGenerator task = project.tasks.create( - DEFAULT_TASK_NAME, - AsciidoctorEditorConfigGenerator + ProjectOperations.maybeCreateExtension(project) + project.tasks.register( + DEFAULT_TASK_NAME, + AsciidoctorEditorConfigGenerator ) - configureIdea(task) - } - - void configureIdea(AsciidoctorEditorConfigGenerator aecg) { - Project project = aecg.project - project.pluginManager.withPlugin('idea') { - project.tasks.getByName('ideaModule').dependsOn aecg - } } } diff --git a/asciidoctoreditorconfig/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.editorconfig.properties b/asciidoctoreditorconfig/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.editorconfig.properties deleted file mode 100644 index 250fa096c..000000000 --- a/asciidoctoreditorconfig/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.editorconfig.properties +++ /dev/null @@ -1,17 +0,0 @@ -# -# Copyright 2013-2023 the original author or authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://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. -# - -implementation-class=org.asciidoctor.gradle.editorconfig.AsciidoctorEditorConfigPlugin \ No newline at end of file diff --git a/asciidoctoreditorconfig/src/test/groovy/org/asciidoctor/gradle/editorconfig/AsciidoctorEditorConfigSpec.groovy b/asciidoctoreditorconfig/src/test/groovy/org/asciidoctor/gradle/editorconfig/AsciidoctorEditorConfigSpec.groovy index 1c450db8d..4ef6d69f5 100644 --- a/asciidoctoreditorconfig/src/test/groovy/org/asciidoctor/gradle/editorconfig/AsciidoctorEditorConfigSpec.groovy +++ b/asciidoctoreditorconfig/src/test/groovy/org/asciidoctor/gradle/editorconfig/AsciidoctorEditorConfigSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/base/build.gradle b/base/build.gradle index d6ccb9f9c..8671a13a6 100644 --- a/base/build.gradle +++ b/base/build.gradle @@ -15,11 +15,15 @@ * limitations under the License. */ -configurePlugin 'org.asciidoctor.base', - 'Asciidoctor Base Plugin', - 'Base plugin for all asciidoctor document conversion plugins (AsciidoctorJ & AsciidoctorJS)', - [ ] - +agProject { + configurePlugin( + 'org.asciidoctor.base', + 'Asciidoctor Base Plugin', + 'Base plugin for all asciidoctor document conversion plugins (AsciidoctorJ & AsciidoctorJS)', + 'org.asciidoctor.gradle.base.AsciidoctorBasePlugin', + [] + ) +} pluginManager.withPlugin('jacoco') { jacocoTestReport { @@ -29,6 +33,7 @@ pluginManager.withPlugin('jacoco') { } } -configurations.gradleTestCompile.transitive = false +configurations { + gradleTestCompile.transitive = false +} -validateTaskProperties.enabled = false \ No newline at end of file diff --git a/base/src/intTest/groovy/org/asciidoctor/gradle/base/ApplyPluginSpec.groovy b/base/src/intTest/groovy/org/asciidoctor/gradle/base/ApplyPluginSpec.groovy index 4dc528fe9..0a9e649a3 100644 --- a/base/src/intTest/groovy/org/asciidoctor/gradle/base/ApplyPluginSpec.groovy +++ b/base/src/intTest/groovy/org/asciidoctor/gradle/base/ApplyPluginSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/base/src/intTest/groovy/org/asciidoctor/gradle/base/internal/FunctionalSpecification.groovy b/base/src/intTest/groovy/org/asciidoctor/gradle/base/internal/FunctionalSpecification.groovy index 452b91c85..c27477697 100644 --- a/base/src/intTest/groovy/org/asciidoctor/gradle/base/internal/FunctionalSpecification.groovy +++ b/base/src/intTest/groovy/org/asciidoctor/gradle/base/internal/FunctionalSpecification.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,9 +20,8 @@ import org.apache.commons.io.FileUtils import org.asciidoctor.gradle.testfixtures.DslType import org.asciidoctor.gradle.testfixtures.FunctionalTestSetup import org.gradle.testkit.runner.GradleRunner -import org.junit.Rule -import org.junit.rules.TemporaryFolder import spock.lang.Specification +import spock.lang.TempDir import static org.asciidoctor.gradle.testfixtures.DslType.GROOVY_DSL import static org.asciidoctor.gradle.testfixtures.DslType.KOTLIN_DSL @@ -32,41 +31,41 @@ import static org.asciidoctor.gradle.testfixtures.FunctionalTestSetup.getOffline class FunctionalSpecification extends Specification { public static final String TEST_PROJECTS_DIR = System.getProperty( - 'TEST_PROJECTS_DIR', - './asciidoctor-gradle-base/src/intTest/projects' + 'TEST_PROJECTS_DIR', + './asciidoctor-gradle-base/src/intTest/projects' ) public static final String TEST_REPO_DIR = System.getProperty( - 'OFFLINE_REPO', - './testfixtures/offline-repo/build/repo' + 'OFFLINE_REPO', + './testfixtures/offline-repo/build/repo' ) - @Rule - TemporaryFolder testProjectDir + @TempDir + File testProjectDir @CompileStatic GradleRunner getGradleRunner(List taskNames = ['tasks']) { - FunctionalTestSetup.getGradleRunner(GROOVY_DSL, testProjectDir.root, taskNames) + FunctionalTestSetup.getGradleRunner(GROOVY_DSL, testProjectDir, taskNames) } @CompileStatic GradleRunner getGradleRunnerForKotlin(List taskNames = ['tasks']) { - FunctionalTestSetup.getGradleRunner(KOTLIN_DSL, testProjectDir.root, taskNames) + FunctionalTestSetup.getGradleRunner(KOTLIN_DSL, testProjectDir, taskNames) } @SuppressWarnings(['BuilderMethodWithSideEffects']) void createTestProject(String docGroup = 'normal') { - FileUtils.copyDirectory(new File(TEST_PROJECTS_DIR, docGroup), testProjectDir.root) + FileUtils.copyDirectory(new File(TEST_PROJECTS_DIR, docGroup), testProjectDir) } @CompileStatic String getOfflineRepositories(DslType dslType = GROOVY_DSL) { dslType == GROOVY_DSL ? getOfflineRepositoriesGroovyDsl(new File(TEST_REPO_DIR)) : - getOfflineRepositoriesKotlinDsl(new File(TEST_REPO_DIR)) + getOfflineRepositoriesKotlinDsl(new File(TEST_REPO_DIR)) } File getGroovyBuildFile(String extraContent, String plugin = 'org.asciidoctor.base') { - File buildFile = testProjectDir.newFile('build.gradle') + File buildFile = new File(testProjectDir, 'build.gradle') buildFile << """ plugins { id '${plugin}' @@ -80,7 +79,7 @@ class FunctionalSpecification extends Specification { } File getKotlinBuildFile(String extraContent, String plugin = 'org.asciidoctor.base') { - File buildFile = testProjectDir.newFile('build.gradle.kts') + File buildFile = new File(testProjectDir, 'build.gradle.kts') buildFile << """ plugins { id("${plugin}") diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/AbstractAsciidoctorBaseTask.groovy b/base/src/main/groovy/org/asciidoctor/gradle/base/AbstractAsciidoctorBaseTask.groovy index 4663e0818..0839d1181 100644 --- a/base/src/main/groovy/org/asciidoctor/gradle/base/AbstractAsciidoctorBaseTask.groovy +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/AbstractAsciidoctorBaseTask.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,10 +17,7 @@ package org.asciidoctor.gradle.base import groovy.transform.CompileDynamic import groovy.transform.CompileStatic -import org.asciidoctor.gradle.base.basedir.BaseDirFollowsProject -import org.asciidoctor.gradle.base.basedir.BaseDirFollowsRootProject -import org.asciidoctor.gradle.base.basedir.BaseDirIsFixedPath -import org.asciidoctor.gradle.base.basedir.BaseDirIsNull +import org.asciidoctor.gradle.base.internal.DefaultAsciidoctorBaseDirConfiguration import org.asciidoctor.gradle.base.internal.Workspace import org.gradle.api.Action import org.gradle.api.DefaultTask @@ -32,27 +29,20 @@ import org.gradle.api.file.CopySpec import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.FileTree import org.gradle.api.file.FileTreeElement +import org.gradle.api.provider.Property import org.gradle.api.provider.Provider import org.gradle.api.specs.Spec import org.gradle.api.tasks.Console -import org.gradle.api.tasks.IgnoreEmptyDirectories import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.Internal import org.gradle.api.tasks.Nested import org.gradle.api.tasks.OutputDirectories import org.gradle.api.tasks.OutputDirectory -import org.gradle.api.tasks.PathSensitive -import org.gradle.api.tasks.SkipWhenEmpty import org.gradle.api.tasks.util.PatternFilterable import org.gradle.api.tasks.util.PatternSet -import org.gradle.util.GradleVersion import org.ysb33r.grolifant.api.core.ProjectOperations -import org.ysb33r.grolifant.api.v4.FileUtils -import org.ysb33r.grolifant.api.v4.StringUtils import java.nio.file.Path -import java.util.concurrent.Callable import static org.asciidoctor.gradle.base.AsciidoctorUtils.UNDERSCORE_LED_FILES import static org.asciidoctor.gradle.base.AsciidoctorUtils.createDirectoryProperty @@ -60,8 +50,10 @@ import static org.asciidoctor.gradle.base.AsciidoctorUtils.executeDelegatingClos import static org.asciidoctor.gradle.base.AsciidoctorUtils.mapToDirectoryProvider import static org.gradle.api.tasks.PathSensitivity.RELATIVE import static org.ysb33r.grolifant.api.core.TaskInputFileOptions.IGNORE_EMPTY_DIRECTORIES +import static org.ysb33r.grolifant.api.core.TaskInputFileOptions.SKIP_WHEN_EMPTY -/** Abstract base task for Asciidoctor that can be shared between AsciidoctorJ and Asciidoctor.js. +/** + * Abstract base task for Asciidoctor that can be shared between AsciidoctorJ and Asciidoctor.js. * * @author Schalk W. Cronjé * @author Lari Hotari @@ -71,24 +63,25 @@ import static org.ysb33r.grolifant.api.core.TaskInputFileOptions.IGNORE_EMPTY_DI */ @CompileStatic @SuppressWarnings(['MethodCount', 'ClassSize']) -abstract class AbstractAsciidoctorBaseTask extends DefaultTask { +abstract class AbstractAsciidoctorBaseTask extends DefaultTask implements AsciidoctorTaskMethods { - private static final boolean GRADLE_LT_4_3 = GradleVersion.current() < GradleVersion.version('4.3') + @Delegate + private final AsciidoctorTaskBaseDirConfiguration baseDirConfiguration private final DirectoryProperty srcDir private final DirectoryProperty outDir private final ProjectOperations projectOperations - private BaseDirStrategy baseDir private PatternSet sourceDocumentPattern private PatternSet secondarySourceDocumentPattern private CopySpec resourceCopy private List copyResourcesForBackends = [] private boolean withIntermediateWorkDir = false - private PatternSet intermediateArtifactPattern private final List languages = [] private final Map languageResources = [:] private final OutputOptions configuredOutputOptions = new OutputOptions() private final Provider defaultRevNumber + private final Provider intermediateWorkDirProvider + private final Property intermediateArtifactPattern /** Logs documents as they are converted * @@ -153,100 +146,6 @@ abstract class AbstractAsciidoctorBaseTask extends DefaultTask { this.outDir } - /** Base directory (current working directory) for a conversion. - * - * @return Base directory. - */ - // IMPORTANT: Do not change this to @InputDirectory as it can lead to file locking issues on - // Windows. In reality we do not need to track contents of the directory - // simply the value change - we achieve that via a normal property. - @Internal - File getBaseDir() { - if (!languages.empty) { - throw new AsciidoctorMultiLanguageException('Use getBaseDir(lang) instead') - } - this.baseDir ? this.baseDir.baseDir : project.projectDir - } - - /** Base directory (current working directory) for a conversion. - * - * Depending on the strateggy in use, the source language used in the conversion - * may change the final base directory relative to the value returned by {@link #getBaseDir}. - * - * @param lang Language in use - * @return Language-dependent base directory - */ - File getBaseDir(String lang) { - this.baseDir ? this.baseDir.getBaseDir(lang) : project.projectDir - } - - /** Sets the base directory for a conversion. - * - * The base directory is used by AsciidoctorJ to set a current working directory for - * a conversion. - * - * If never set, then {@code project.projectDir} will be assumed to be the base directory. - * - * @param f Base directory - */ - void setBaseDir(Object f) { - switch (f) { - case BaseDirStrategy: - this.baseDir = (BaseDirStrategy) f - break - case null: - this.baseDir = BaseDirIsNull.INSTANCE - break - default: - this.baseDir = new BaseDirIsFixedPath(project.providers.provider({ - project.file(f) - } as Callable)) - } - } - - /** Sets the basedir to be the same directory as the root project directory. - * - * @return A strategy that allows the basedir to be locked to the root project. - * - * @since 2.2.0 - */ - void baseDirIsRootProjectDir() { - this.baseDir = new BaseDirFollowsRootProject(project) - } - - /** Sets the basedir to be the same directory as the current project directory. - * - * @return A strategy that allows the basedir to be locked to the current project. - * - * @since 2.2.0 - */ - void baseDirIsProjectDir() { - this.baseDir = new BaseDirFollowsProject(project) - } - - /** The base dir will be the same as the source directory. - * - * If an intermediate working directory is used, the the base dir will be where the - * source directory is located within the temporary working directory. - * - * @return A strategy that allows the basedir to be locked to the current project. - * - * @since 2.2.0 - */ - void baseDirFollowsSourceDir() { - this.baseDir = new BaseDirIsFixedPath(project.providers.provider({ AbstractAsciidoctorBaseTask task -> - task.withIntermediateWorkDir ? task.intermediateWorkDir : task.sourceDir - }.curry(this) as Callable)) - } - - /** Sets the basedir to be the same directory as each individual source file. - * - * @since 3.0.0 - */ - void baseDirFollowsSourceFile() { - this.baseDir = BaseDirIsNull.INSTANCE - } - /** Configures sources. * * @param cfg Configuration closure. Is passed a {@link PatternSet}. @@ -332,10 +231,7 @@ abstract class AbstractAsciidoctorBaseTask extends DefaultTask { * * @since 1.5.1 */ - @InputFiles - @SkipWhenEmpty - @IgnoreEmptyDirectories - @PathSensitive(RELATIVE) + @Internal FileTree getSourceFileTree() { if (languages.empty) { getSourceFileTreeFrom(sourceDir) @@ -355,9 +251,7 @@ abstract class AbstractAsciidoctorBaseTask extends DefaultTask { * @return Collection of secondary source files * */ - @InputFiles - @IgnoreEmptyDirectories - @PathSensitive(RELATIVE) + @Internal FileTree getSecondarySourceFileTree() { if (languages.empty) { getSecondarySourceFileTreeFrom(sourceDir) @@ -471,6 +365,28 @@ abstract class AbstractAsciidoctorBaseTask extends DefaultTask { Optional.ofNullable(this.copyResourcesForBackends) } + /** + * A provider of patterns identifying intermediate artifacts. + * + * @return Provider to a {@link PatternSet}. Can be empty. + */ + @Override + Provider getIntermediateArtifactPatternProvider() { + this.intermediateArtifactPattern + } + + /** + * Returns the copy specification for the resources of a specific language. + * + * @param lang Language + * + * @return Copy specification. Can be {@code null}. + */ + @Override + CopySpec getLanguageResourceCopySpec(String lang) { + languageResources[lang] + } + /** Some extensions such as {@code ditaa} creates images in the source directory. * * Use this setting to copy all sources and resources to an intermediate work directory @@ -493,10 +409,10 @@ abstract class AbstractAsciidoctorBaseTask extends DefaultTask { */ void withIntermediateArtifacts(@DelegatesTo(PatternSet) Closure cfg) { useIntermediateWorkDir() - if (this.intermediateArtifactPattern == null) { - this.intermediateArtifactPattern = new PatternSet() + if (!this.intermediateArtifactPattern.present) { + this.intermediateArtifactPattern.set(new PatternSet()) } - executeDelegatingClosure(this.intermediateArtifactPattern, cfg) + executeDelegatingClosure(this.intermediateArtifactPattern.get(), cfg) } /** Additional artifacts created by Asciidoctor that might require copying. @@ -507,10 +423,25 @@ abstract class AbstractAsciidoctorBaseTask extends DefaultTask { */ void withIntermediateArtifacts(final Action cfg) { useIntermediateWorkDir() - if (this.intermediateArtifactPattern == null) { - this.intermediateArtifactPattern = new PatternSet() + if (!this.intermediateArtifactPattern.present) { + this.intermediateArtifactPattern.set(new PatternSet()) } - cfg.execute(this.intermediateArtifactPattern) + cfg.execute(this.intermediateArtifactPattern.get()) + } + + /** + * Checks whether an intermediate workdir is required. + * + * @return {@code true} is there is an intermediate working directory. + */ + @Override + boolean hasIntermediateWorkDir() { + this.intermediateWorkDirProvider.present + } + + @Override + Provider getIntermediateWorkDirProvider() { + this.intermediateWorkDirProvider } /** The directory that will be the intermediate directory should one be required. @@ -521,7 +452,7 @@ abstract class AbstractAsciidoctorBaseTask extends DefaultTask { */ @Internal File getIntermediateWorkDir() { - project.file("${project.buildDir}/tmp/${FileUtils.toSafeFileName(this.name)}.intermediate") + this.intermediateWorkDirProvider.get() } /** Returns a list of all output directories by backend @@ -585,6 +516,105 @@ abstract class AbstractAsciidoctorBaseTask extends DefaultTask { this.languages.addAll(langs) } + /** Gets the CopySpec for additional resources. + * + * If {@code resources} was never called, it will return a default CopySpec otherwise it will return the + * one built up via successive calls to {@code resources} + * + * @param lang Language to to apply to or empty for no-language support. + * @return A{@link CopySpec}. Never {@code null}. + */ + @Override + CopySpec getResourceCopySpec(Optional lang) { + this.resourceCopy ?: getDefaultResourceCopySpec(lang) + } + + /** The default CopySpec that will be used if {@code resources} was never called + * + * By default anything below {@code $sourceDir/images} will be included. + * + * @param lang Language to use. Can be empty (not {@code null}) when not to use a language. + * @return A{@link CopySpec}. Never {@code null}. + */ + @CompileDynamic + CopySpec getDefaultResourceCopySpec(Optional lang) { + project.copySpec { + from(lang.present ? new File(sourceDir, lang.get()) : sourceDir) { + include 'images/**' + } + } + } + + /** + * A task may add some default attributes. + * + * If the user specifies any of these attributes, then those attributes will not be utilised. + * + * The default implementation will add {@code includedir}, {@code revnumber}, {@code gradle-project-group}, + * {@code gradle-project-name} + * + * @param workingSourceDir Directory where source files are located. + * + * @return A collection of default attributes. + */ + Map getTaskSpecificDefaultAttributes(File workingSourceDir) { + Map attrs = [ + includedir: (Object) workingSourceDir.absolutePath + ] + + String revNumber = defaultRevNumber.get() + if (!revNumber.empty && revNumber != Project.DEFAULT_VERSION) { + attrs.put('revnumber', revNumber) + } + + attrs + } + + /** Prepares a workspace prior to conversion. + * + * @return A presentation of the working source directory and the source tree. + */ + Workspace prepareWorkspace() { + if (!this.languages.empty) { + throw new AsciidoctorMultiLanguageException('Use prepareWorkspace(lang) instead.') + } + if (this.withIntermediateWorkDir) { + File tmpDir = intermediateWorkDir + prepareTempWorkspace(tmpDir) + Workspace.builder().workingSourceDir(tmpDir).sourceTree(getSourceFileTreeFrom(tmpDir)).build() + } else { + Workspace.builder().workingSourceDir(sourceDir).sourceTree(sourceFileTree).build() + } + } + + /** Prepares a workspace for a specific language prior to conversion. + * + * @param language Language to prepare workspace for. + * @return A presentation of the working source directory and the source tree. + */ + Workspace prepareWorkspace(String language) { + if (this.withIntermediateWorkDir) { + File tmpDir = new File(intermediateWorkDir, language) + prepareTempWorkspace(tmpDir, language) + Workspace.builder().workingSourceDir(tmpDir).sourceTree(getSourceFileTreeFrom(tmpDir)).build() + } else { + File srcDir = new File(sourceDir, language) + Workspace.builder() + .workingSourceDir(srcDir) + .sourceTree(getSourceFileTreeFrom(srcDir)) + .build() + } + } + + /** Checks whether an explicit strategy has been set for base directory. + * + * @return {@code true} if a strategy has been configured. + */ + @Internal + boolean isBaseDirConfigured() { + this.baseDir != null + } + /** Shortcut method for obtaining attributes. * * In most implementations this will just access the {@code getAttributes} method @@ -632,18 +662,35 @@ abstract class AbstractAsciidoctorBaseTask extends DefaultTask { @Internal abstract Set getReportableConfigurations() + @SuppressWarnings('ThisReferenceEscapesConstructor') protected AbstractAsciidoctorBaseTask() { super() this.projectOperations = ProjectOperations.find(project) + this.intermediateArtifactPattern = project.objects.property(PatternSet) + this.srcDir = createDirectoryProperty(project) + this.outDir = createDirectoryProperty(project) + this.defaultRevNumber = projectOperations.projectTools.versionProvider.orElse(Project.DEFAULT_VERSION) + this.intermediateWorkDirProvider = projectOperations.buildDirDescendant( + "/tmp/${projectOperations.fsOperations.toSafeFileName(this.name)}.intermediate" + ) + projectOperations.tasks.inputFiles( inputs, { projectOperations.fsOperations.resolveFilesFromCopySpec(getResourceCopySpec(Optional.empty())) }, RELATIVE, IGNORE_EMPTY_DIRECTORIES ) - this.srcDir = createDirectoryProperty(project) - this.outDir = createDirectoryProperty(project) - this.defaultRevNumber = projectOperations.projectTools.versionProvider.orElse(Project.DEFAULT_VERSION) + projectOperations.tasks.inputFiles( + inputs, + { sourceFileTree }, + RELATIVE, IGNORE_EMPTY_DIRECTORIES, SKIP_WHEN_EMPTY + ) + projectOperations.tasks.inputFiles( + inputs, + { secondarySourceFileTree }, + RELATIVE, IGNORE_EMPTY_DIRECTORIES + ) + this.baseDirConfiguration = new DefaultAsciidoctorBaseDirConfiguration(project, this) } @Nested @@ -651,32 +698,16 @@ abstract class AbstractAsciidoctorBaseTask extends DefaultTask { configuredOutputOptions } - /** Gets the CopySpec for additional resources. - * - * If {@code resources} was never called, it will return a default CopySpec otherwise it will return the - * one built up via successive calls to {@code resources} - * - * @param lang Language to to apply to or empty for no-language support. - * @return A{@link CopySpec}. Never {@code null}. - */ - protected CopySpec getResourceCopySpec(Optional lang) { - this.resourceCopy ?: getDefaultResourceCopySpec(lang) - } - - /** The default CopySpec that will be used if {@code resources} was never called + /** + * Access tp the object that handles base directory configuration * - * By default anything below {@code $sourceDir/images} will be included. + * @return Implementation of {@link AsciidoctorTaskBaseDirConfiguration} * - * @param lang Language to use. Can be empty (not {@code null}) when not to use a language. - * @return A{@link CopySpec}. Never {@code null}. + * @since 4.0 */ - @CompileDynamic - protected CopySpec getDefaultResourceCopySpec(Optional lang) { - project.copySpec { - from(lang.present ? new File(sourceDir, lang.get()) : sourceDir) { - include 'images/**' - } - } + @Nested + protected AsciidoctorTaskBaseDirConfiguration getBaseDirDelegate() { + this.baseDirConfiguration } /** @@ -725,7 +756,11 @@ abstract class AbstractAsciidoctorBaseTask extends DefaultTask { * @return Source tree based upon configured pattern. */ protected FileTree getSourceFileTreeFrom(File dir) { - AsciidoctorUtils.getSourceFileTree(project, dir, this.sourceDocumentPattern ?: defaultSourceDocumentPattern) + AsciidoctorUtils.getSourceFileTree( + projectOperations, + dir, + this.sourceDocumentPattern ?: defaultSourceDocumentPattern + ) } /** Obtains a secondary source tree based on patterns. @@ -763,68 +798,6 @@ abstract class AbstractAsciidoctorBaseTask extends DefaultTask { asciidocPatterns } - /** - * A task may add some default attributes. - * - * If the user specifies any of these attributes, then those attributes will not be utilised. - * - * The default implementation will add {@code includedir}, {@code revnumber}, {@code gradle-project-group}, - * {@code gradle-project-name} - * - * @param workingSourceDir Directory where source files are located. - * - * @return A collection of default attributes. - */ - protected Map getTaskSpecificDefaultAttributes(File workingSourceDir) { - Map attrs = [ - includedir: (Object) workingSourceDir.absolutePath -// 'gradle-project-name': (Object) project.name - ] - - String revNumber = defaultRevNumber.get() - if (!revNumber.empty && revNumber != Project.DEFAULT_VERSION) { - attrs.put('revnumber', revNumber) - } - -// if (project.group != null) { -// attrs.put('gradle-project-group', (Object) project.group) -// } - - attrs - } - - /** Get the output directory for a specific backend. - * - * @param backendName Name of backend - * @return Output directory. - */ - protected File getOutputDirFor(final String backendName) { - if (outputDir == null) { - throw new GradleException("outputDir has not been defined for task '${name}'") - } - if (!this.languages.empty) { - throw new AsciidoctorMultiLanguageException('Use getOutputDir(backendname,language) instead.') - } - configuredOutputOptions.separateOutputDirs ? new File(outputDir, backendName) : outputDir - } - - /** Get the output directory for a specific backend. - * - * @param backendName Name of backend - * @param language Language for which sources are being generated. - * @return Output directory. - * - * @since 3.0.0 - */ - protected File getOutputDirFor(final String backendName, final String language) { - if (outputDir == null) { - throw new GradleException("outputDir has not been defined for task '${name}'") - } - configuredOutputOptions.separateOutputDirs ? - new File(outputDir, "${language}/${backendName}") : - new File(outputDir, language) - } - /** Adds an input property. * * Serves as a proxy method in order to deal with the API differences between Gradle 4.0-4.2 and 4.3 @@ -846,47 +819,7 @@ abstract class AbstractAsciidoctorBaseTask extends DefaultTask { */ @CompileDynamic protected void addOptionalInputProperty(String propName, Object value) { - if (GRADLE_LT_4_3) { - inputs.property propName, { -> StringUtils.stringize(value) ?: '' } - } else { - inputs.property(propName, value).optional(true) - } - } - - /** Prepares a workspace prior to conversion. - * - * @return A presentation of the working source directory and the source tree. - */ - protected Workspace prepareWorkspace() { - if (!this.languages.empty) { - throw new AsciidoctorMultiLanguageException('Use prepareWorkspace(lang) instead.') - } - if (this.withIntermediateWorkDir) { - File tmpDir = intermediateWorkDir - prepareTempWorkspace(tmpDir) - Workspace.builder().workingSourceDir(tmpDir).sourceTree(getSourceFileTreeFrom(tmpDir)).build() - } else { - Workspace.builder().workingSourceDir(sourceDir).sourceTree(sourceFileTree).build() - } - } - - /** Prepares a workspace for a specific language prior to conversion. - * - * @param language Language to prepare workspace for. - * @return A presentation of the working source directory and the source tree. - */ - protected Workspace prepareWorkspace(String language) { - if (this.withIntermediateWorkDir) { - File tmpDir = new File(intermediateWorkDir, language) - prepareTempWorkspace(tmpDir, language) - Workspace.builder().workingSourceDir(tmpDir).sourceTree(getSourceFileTreeFrom(tmpDir)).build() - } else { - File srcDir = new File(sourceDir, language) - Workspace.builder() - .workingSourceDir(srcDir) - .sourceTree(getSourceFileTreeFrom(srcDir)) - .build() - } + inputs.property(propName, value).optional(true) } /** Copy resources for a backend name. @@ -905,8 +838,8 @@ abstract class AbstractAsciidoctorBaseTask extends DefaultTask { CopySpec rcs = getResourceCopySpec(includeLang) logger.info "Copy resources for '${backendName}' to ${outputDir}" - FileTree ps = this.intermediateArtifactPattern ? - project.fileTree(sourceDir).matching(this.intermediateArtifactPattern) : + FileTree ps = this.intermediateArtifactPattern.present ? + projectOperations.fileTree(sourceDir).matching(this.intermediateArtifactPattern.get()) : null CopySpec langSpec = includeLang.present ? languageResources[includeLang.get()] : null @@ -980,15 +913,6 @@ abstract class AbstractAsciidoctorBaseTask extends DefaultTask { } } - /** Checks whether an explicit strategy has been set for base directory. - * - * @return {@code true} if a strategy has been configured. - */ - @Internal - protected boolean isBaseDirConfigured() { - this.baseDir != null - } - /** Validates all preconditions prior to starting to run the conversion process. * */ @@ -997,7 +921,40 @@ abstract class AbstractAsciidoctorBaseTask extends DefaultTask { checkForIncompatiblePathRoots() } - /** Prepare attributes to be serialisable + /** Get the output directory for a specific backend. + * + * @param backendName Name of backend + * @return Output directory. + */ + protected File getOutputDirFor(final String backendName) { + if (outputDir == null) { + throw new GradleException("outputDir has not been defined for task '${name}'") + } + if (!this.languages.empty) { + throw new AsciidoctorMultiLanguageException('Use getOutputDir(backendname,language) instead.') + } + configuredOutputOptions.separateOutputDirs ? new File(outputDir, backendName) : outputDir + } + + /** Get the output directory for a specific backend. + * + * @param backendName Name of backend + * @param language Language for which sources are being generated. + * @return Output directory. + * + * @since 3.0.0 + */ + protected File getOutputDirFor(final String backendName, final String language) { + if (outputDir == null) { + throw new GradleException("outputDir has not been defined for task '${name}'") + } + configuredOutputOptions.separateOutputDirs ? + new File(outputDir, "${language}/${backendName}") : + new File(outputDir, language) + } + + /** + * Prepare attributes to be serialisable * * @param workingSourceDir Working source directory from which source documents will be made available. * @param seedAttributes Initial attributes set on the task. @@ -1041,7 +998,7 @@ abstract class AbstractAsciidoctorBaseTask extends DefaultTask { Map defaultAttrs = defaultAttributes.findAll { k, v -> !userDefinedAttrKeys.contains(k) }.collectEntries { k, v -> - ["${k}@".toString(), v instanceof Serializable ? v : StringUtils.stringize(v)] + ["${k}@".toString(), v instanceof Serializable ? v : projectOperations.stringTools.stringize(v)] } as Map if (lang.present) { @@ -1074,13 +1031,6 @@ abstract class AbstractAsciidoctorBaseTask extends DefaultTask { } as Map } - /** Name of the implementation engine. - * - * @return Name of the Asciidoctor implementation engine. - */ - @Internal - abstract protected String getEngineName() - private void checkForInvalidSourceDocuments() { if (!sourceFileTree.filter { File f -> f.name.startsWith('_') @@ -1094,7 +1044,7 @@ abstract class AbstractAsciidoctorBaseTask extends DefaultTask { throw new GradleException("outputDir has not been defined for task '${name}'") } - Path baseRoot = languages.empty ? getBaseDir()?.toPath()?.root : getBaseDir(languages[0])?.toPath()?.root + Path baseRoot = languages.empty ? baseDir?.toPath()?.root : getBaseDir(languages[0])?.toPath()?.root if (baseRoot != null) { Path sourceRoot = sourceDir.toPath().root Path outputRoot = outputDir.toPath().root diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/AbstractDownloadableComponent.groovy b/base/src/main/groovy/org/asciidoctor/gradle/base/AbstractDownloadableComponent.groovy deleted file mode 100644 index d1487ac4b..000000000 --- a/base/src/main/groovy/org/asciidoctor/gradle/base/AbstractDownloadableComponent.groovy +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright 2013-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://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.asciidoctor.gradle.base - -import groovy.transform.CompileDynamic -import groovy.transform.CompileStatic -import org.gradle.api.Action -import org.gradle.api.Project -import org.gradle.api.UnknownDomainObjectException -import org.ysb33r.grolifant.api.v4.git.AbstractCloudGit -import org.ysb33r.grolifant.api.v4.git.GitRepoArchiveDownloader - -import static org.asciidoctor.gradle.base.AsciidoctorUtils.executeDelegatingClosure - -/** Base class for building extension which can allows styles and themes to be added to various - * Asciidoctor backends that may require them. - * - * @since 2.0 - */ -@CompileStatic -abstract class AbstractDownloadableComponent { - - private final Map components = [:] - protected final Project project - - /** Adds a component source that is available on the local filesystem. - * - * By default the {@code styleName} will match that of the theme. - * - * @param name Name of theme - * @param localConfig Configures an instance of {@link ComponentSrc}. - */ - void local(final String name, @DelegatesTo(ComponentSrc) Closure localConfig) { - ComponentSrc component = instantiateComponentSource(name) - executeDelegatingClosure(component, localConfig) - this.components[name] = convertible(component) - } - - /** Adds a component source that is available on the local filesystem. - * - * By default the {@code styleName} will match that of the theme. - * If the name ends with {@code .yml}, it’s assumed to be the complete name of a file. - * Otherwise, {@code -theme.yml} is appended to the name to make the file name (i.e., {@code -theme.yml}). - * - * @param name Name of theme - * @param localConfig Configures an instance of {@link ComponentSrc}. - */ - void local(final String name, Action localConfig) { - ComponentSrc component = instantiateComponentSource(name) - localConfig.execute(component) - this.components[name] = convertible(component) - } - - /** Use a GitHub repository as a theme. - * - * @param name Name of theme - * @param githubConfig Closure to configure a {@link GitHubArchive}. - */ - void github(final String name, @DelegatesTo(GitHubArchive) Closure githubConfig) { - addCloudGitArchive(name, new GitHubArchive(), githubConfig) - } - - /** Use a GitHub repository as a theme. - * - * @param name Name of theme - * @param githubConfig Action to configure a {@link GitHubArchive}. - */ - void github(final String name, Action githubConfig) { - addCloudGitArchive(name, new GitHubArchive(), githubConfig as Action) - } - - /** Use a GitLab repository as a theme. - * - * @param name Name of theme - * @param githubConfig Closure to configure a {@link GitLabArchive}. - */ - void gitlab(final String name, @DelegatesTo(GitLabArchive) Closure gitlabConfig) { - addCloudGitArchive(name, new GitLabArchive(), gitlabConfig) - } - - /** Use a GitLab repository as a theme. - * - * @param name Name of theme - * @param githubConfig Action to configure a {@link GitLabArchive}. - */ - void gitlab(final String name, Action gitlabConfig) { - addCloudGitArchive(name, new GitLabArchive(), gitlabConfig as Action) - } - - /** Retrieve a component by name. - * - * If the component has not already been resolved, it will be resolved by this call. - * - * @param name Name of the component. - * @return Resolved component. - * @throw {@link org.gradle.api.UnknownDomainObjectException} is the component has not been registered. - */ - ResolvedComponent getByName(final String name) { - if (components.containsKey(name)) { - components[name].call() - } else { - throw new UnknownDomainObjectException("Theme with name '${name}' was not registered") - } - } - - protected AbstractDownloadableComponent(Project project) { - this.project = project - } - - /** Creates a closure that can convert from a GitLab/GitHub repository to a local cached file. - * - * @param name Name or componenet - * @param component Details of component in remote repository. - * @return Closure that will resolve an archive from a remote reposiotry and store it locally. - */ - protected Closure convertible(final String name, AbstractCloudGit component) { - final GitRepoArchiveDownloader downloader = new GitRepoArchiveDownloader(component, project) - final String relativePath = getRelativePathInsideArchive(component) - return { -> - downloader.downloadRoot = project.buildDir - File root = downloader.archiveRoot - - instantiateResolvedComponent(name, relativePath ? new File(root, relativePath) : root) - } - } - - /** Instantiates a component of type {@code ComponentSrc}. - * - * @param name Name of componenet - * @return New component source description - */ - abstract protected ComponentSrc instantiateComponentSource(final String name) - - /** Create a closure that will convert an instance of a {@code ComponentSrc} into a {@code ResolvedComponent}. - * - * @param component Component to be converted. - * @return Converting closure. - */ - abstract protected Closure convertible(@DelegatesTo.Target ComponentSrc component) - - /** Instantiates a resolved component. - * - * @param name Name of component - * @param path Path to where compoenet is located on local filesystem. - * @return Instantiated component. - */ - abstract protected ResolvedComponent instantiateResolvedComponent(final String name, final File path) - - private void addCloudGitArchive( - final String name, final AbstractCloudGit archive, @DelegatesTo(AbstractCloudGit) Closure config - ) { - executeDelegatingClosure(archive, config) - this.components[name] = convertible(name, archive) - } - - private void addCloudGitArchive( - final String name, final AbstractCloudGit archive, Action config - ) { - config.execute(archive) - this.components[name] = convertible(name, archive) - } - - @CompileDynamic - private String getRelativePathInsideArchive(AbstractCloudGit theme) { - theme.relativePath - } - -} diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/AbstractDownloadableComponent.java b/base/src/main/groovy/org/asciidoctor/gradle/base/AbstractDownloadableComponent.java new file mode 100644 index 000000000..8d06beec3 --- /dev/null +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/AbstractDownloadableComponent.java @@ -0,0 +1,192 @@ +/* + * Copyright 2013-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://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.asciidoctor.gradle.base; + +import groovy.lang.Closure; +import groovy.lang.DelegatesTo; +import org.gradle.api.Action; +import org.gradle.api.Project; +import org.gradle.api.UnknownDomainObjectException; +import org.gradle.api.provider.Provider; +import org.ysb33r.grolifant.api.core.ClosureUtils; +import org.ysb33r.grolifant.api.core.ProjectOperations; +import org.ysb33r.grolifant.api.core.git.AbstractCloudGit; +import org.ysb33r.grolifant.api.core.git.GitLabArchive; +import org.ysb33r.grolifant.api.core.git.GitRepoArchiveDownloader; + +import java.io.File; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.Callable; + +/** + * Base class for building extension which can allows styles and themes to be added to various + * Asciidoctor backends that may require them. + * + * @param Source component type + * @param Final comnponent type + * @since 2.0 + */ +public abstract class AbstractDownloadableComponent { + + protected final ProjectOperations projectOperations; + private final Map> components = + new LinkedHashMap<>(); + + private final Provider downloadRootProvider; + + /** + * Adds a component source that is available on the local filesystem. + *

    + * By default, the {@code styleName} will match that of the theme. + * If the name ends with {@code .yml}, it’s assumed to be the complete name of a file. + * Otherwise, {@code -theme.yml} is appended to the name to make the file name (i.e., {@code -theme.yml}). + * + * @param name Name of theme + * @param localConfig Configures an instance of {@link ComponentSrc}. + */ + public void local(final String name, Action localConfig) { + ComponentSrc component = instantiateComponentSource(name); + localConfig.execute(component); + this.components.put(name, convertible(component)); + } + + /** + * Use a GitHub repository as a theme. + * + * @param name Name of theme + * @param githubConfig Action to configure a {@link AscGitHubArchive}. + */ + public void github(final String name, Action githubConfig) { + AscGitHubArchive archive = new AscGitHubArchive(projectOperations); + githubConfig.execute(archive); + this.components.put(name, convertible(name, archive)); + } + + /** + * Use a GitLab repository as a theme. + * + * @param name Name of theme + * @param gitlabConfig Closure to configure a {@link AscGitLabArchive}. + */ + public void gitlab(final String name, @DelegatesTo(GitLabArchive.class) Closure gitlabConfig) { + AscGitLabArchive archive = new AscGitLabArchive(projectOperations); + ClosureUtils.configureItem(archive,gitlabConfig); + this.components.put(name, convertible(name, archive)); + } + + /** + * Use a GitLab repository as a theme. + * + * @param name Name of theme + * @param gitlabConfig Action to configure a {@link AscGitLabArchive}. + */ + public void gitlab(final String name, Action gitlabConfig) { + AscGitLabArchive archive = new AscGitLabArchive(projectOperations); + gitlabConfig.execute(archive); + this.components.put(name, convertible(name, archive)); + } + + /** + * Retrieve a component by name. + *

    + * If the component has not already been resolved, it will be resolved by this call. + * + * @param name Name of the component. + * @return Resolved component. + * @throw {@link UnknownDomainObjectException} is the component has not been registered. + */ + public ResolvedComponent getByName(final String name) { + try { + if (components.containsKey(name)) { + return components.get(name).call(); + } + } catch (Exception e) { + throw new UnknownDomainObjectException("Unexpected error when tryying to retrieve " + name, e); + } + + throw new UnknownDomainObjectException("Theme with name '" + name + "' was not registered"); + } + + protected AbstractDownloadableComponent(Project project) { + this.projectOperations = ProjectOperations.find(project); + this.downloadRootProvider = projectOperations.buildDirDescendant("cloud-archives"); + } + + /** + * Creates a closure that can convert from a GitLab/GitHub repository to a local cached file. + * + * @param name Name or componenet + * @param component Details of component in remote repository. + * @return Closure that will resolve an archive from a remote repository and store it locally. + */ + protected Callable convertible(final String name, AbstractCloudGit component) { + final GitRepoArchiveDownloader downloader = new GitRepoArchiveDownloader(component, projectOperations); + final String relativePath = getRelativePathInsideArchive(component); + return () -> { + downloader.setDownloadRoot(downloadRootProvider.get()); + File root = downloader.getArchiveRoot(); + return instantiateResolvedComponent(name, relativePath != null ? new File(root, relativePath) : root); + }; + } + + /** + * Instantiates a component of type {@code ComponentSrc}. + * + * @param name Name of componenet + * @return New component source description + */ + protected abstract ComponentSrc instantiateComponentSource(final String name); + + /** + * Create a closure that will convert an instance of a {@code ComponentSrc} into a {@code ResolvedComponent}. + * + * @param component Component to be converted. + * @return Converting closure. + */ + protected abstract Callable convertible(ComponentSrc component); + + /** + * Instantiates a resolved component. + * + * @param name Name of component + * @param path Path to where compoenet is located on local filesystem. + * @return Instantiated component. + */ + protected abstract ResolvedComponent instantiateResolvedComponent(final String name, final File path); + +// private void addCloudGitArchive( +// final String name, +// final AbstractCloudGit archive, +// @DelegatesTo(AbstractCloudGit.class) Closure config +// ) { +// AsciidoctorUtils.executeDelegatingClosure(archive, config); +// this.components.put(name, convertible(name, archive)); +// } + + private void addCloudGitArchive( + final String name, + final AbstractCloudGit archive, + Action config + ) { + config.execute((AbstractCloudGit) archive); + this.components.put(name, convertible(name, archive)); + } + + private String getRelativePathInsideArchive(AbstractCloudGit theme) { + return ((String) (theme.getProperty("relativePath"))); + } +} diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/AbstractImplementationEngineExtension.groovy b/base/src/main/groovy/org/asciidoctor/gradle/base/AbstractImplementationEngineExtension.groovy index 84f363e1d..00761fcb8 100644 --- a/base/src/main/groovy/org/asciidoctor/gradle/base/AbstractImplementationEngineExtension.groovy +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/AbstractImplementationEngineExtension.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,11 +19,11 @@ import groovy.transform.CompileStatic import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.provider.Provider -import org.ysb33r.grolifant.api.v4.AbstractCombinedProjectTaskExtension +import org.ysb33r.grolifant.api.core.ProjectOperations +import org.ysb33r.grolifant.api.core.runnable.CombinedProjectTaskExtensionBase import java.util.concurrent.Callable - -import static org.ysb33r.grolifant.api.v4.StringUtils.stringize +import java.util.function.Function /** Base class for implementing extensions in the Asciidoctor Gradle suite. * @@ -32,8 +32,8 @@ import static org.ysb33r.grolifant.api.v4.StringUtils.stringize * @since 3.0 */ @CompileStatic -abstract class AbstractImplementationEngineExtension - extends AbstractCombinedProjectTaskExtension +class AbstractImplementationEngineExtension + extends CombinedProjectTaskExtensionBase implements AsciidoctorAttributeProvider { private SafeMode safeMode @@ -251,7 +251,7 @@ abstract class AbstractImplementationEngineExtension } protected AbstractImplementationEngineExtension(Project project, String moduleResourceName) { - super(project) + super(ProjectOperations.find(project)) this.safeMode = SafeMode.UNSAFE this.attributes['gradle-project-name'] = project.name this.attributes['gradle-project-group'] = projectOperations.projectTools.groupProvider.orElse('') @@ -260,7 +260,11 @@ abstract class AbstractImplementationEngineExtension } protected AbstractImplementationEngineExtension(Task task, final String name) { - super(task, name) + super( + task, + ProjectOperations.find(task.project), + (AbstractImplementationEngineExtension) task.project.extensions.getByName(name) + ) } protected Map getDefaultVersionMap() { @@ -291,15 +295,15 @@ abstract class AbstractImplementationEngineExtension protected Collection stringizeList( Collection list, boolean fromTaskOnly, - Closure> other + Function> other ) { if (!task || fromTaskOnly) { stringize(list) - } else if (list.isEmpty()) { - other.call(extFromProject) + } else if (list.empty) { + other.apply(extFromProject) } else { List newOptions = [] - newOptions.addAll(other.call(extFromProject)) + newOptions.addAll(other.apply(extFromProject)) newOptions.addAll(list) stringize(newOptions) } @@ -321,7 +325,7 @@ abstract class AbstractImplementationEngineExtension case Callable: return item default: - return { -> stringize(item) } as Callable + return { -> projectOperations.stringTools.stringize(item) } as Callable } } } @@ -342,7 +346,7 @@ abstract class AbstractImplementationEngineExtension case Callable: return [key, item] default: - return [key, { -> stringize(item) } as Callable] + return [key, { -> projectOperations.stringTools.stringize(item) } as Callable] } } as Map } @@ -350,4 +354,8 @@ abstract class AbstractImplementationEngineExtension private AbstractImplementationEngineExtension getExtFromProject() { task ? (AbstractImplementationEngineExtension) projectExtension : this } + + private List stringize(Collection stringyThings) { + projectOperations.stringTools.stringize(stringyThings) + } } diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/GitHubArchive.groovy b/base/src/main/groovy/org/asciidoctor/gradle/base/AscGitHubArchive.groovy similarity index 64% rename from base/src/main/groovy/org/asciidoctor/gradle/base/GitHubArchive.groovy rename to base/src/main/groovy/org/asciidoctor/gradle/base/AscGitHubArchive.groovy index 86205b1fe..e864a6251 100644 --- a/base/src/main/groovy/org/asciidoctor/gradle/base/GitHubArchive.groovy +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/AscGitHubArchive.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,15 +15,29 @@ */ package org.asciidoctor.gradle.base +import org.ysb33r.grolifant.api.core.ProjectOperations +import org.ysb33r.grolifant.api.core.git.GitHubArchive + /** Represents a remote GitHub Archive. * * @since 2.0 */ @SuppressWarnings('ClassNameSameAsSuperclass') -class GitHubArchive extends org.ysb33r.grolifant.api.v4.git.GitHubArchive { +class AscGitHubArchive extends GitHubArchive { /** Relative path to locate the them inside the GitHub archive. * */ Object relativePath + + /** + * Create a downloadable Github reference. + * + * @param po {@link ProjectOperations} that is linked to the project. + * + * @since 4.0 + */ + AscGitHubArchive(ProjectOperations po) { + super(po) + } } diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/GitLabArchive.groovy b/base/src/main/groovy/org/asciidoctor/gradle/base/AscGitLabArchive.groovy similarity index 64% rename from base/src/main/groovy/org/asciidoctor/gradle/base/GitLabArchive.groovy rename to base/src/main/groovy/org/asciidoctor/gradle/base/AscGitLabArchive.groovy index 36dbb4800..98f705105 100644 --- a/base/src/main/groovy/org/asciidoctor/gradle/base/GitLabArchive.groovy +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/AscGitLabArchive.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,15 +15,29 @@ */ package org.asciidoctor.gradle.base +import org.ysb33r.grolifant.api.core.ProjectOperations +import org.ysb33r.grolifant.api.core.git.GitLabArchive + /** Represents a remote GitLab Archive. * * @since 2.0 */ @SuppressWarnings('ClassNameSameAsSuperclass') -class GitLabArchive extends org.ysb33r.grolifant.api.v4.git.GitLabArchive { +class AscGitLabArchive extends GitLabArchive { /** Relative path to locate the them inside the GitLab archive. * */ Object relativePath + + /** + * Create a downloadable Gitlab reference. + * + * @param po {@link ProjectOperations} that is linked to the project. + * + * @since 4.0 + */ + AscGitLabArchive(ProjectOperations po) { + super(po) + } } diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorAttributeProvider.java b/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorAttributeProvider.java index 694cd75a8..74ba42929 100644 --- a/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorAttributeProvider.java +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorAttributeProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorBasePlugin.groovy b/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorBasePlugin.groovy index 7966f3c0e..85a545087 100644 --- a/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorBasePlugin.groovy +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorBasePlugin.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,13 +24,14 @@ import org.gradle.api.Task import org.gradle.api.UnknownTaskException import org.gradle.api.tasks.TaskContainer import org.gradle.api.tasks.diagnostics.DependencyReportTask +import org.gradle.language.base.plugins.LifecycleBasePlugin import org.ysb33r.grolifant.api.core.ProjectOperations -import org.ysb33r.grolifant.api.v4.TaskProvider import java.util.regex.Matcher import java.util.regex.Pattern -/** Base plugin for all Asciidoctor plugins (J & JS). +/** + * Base plugin for all Asciidoctor plugins (J & JS). * * @author Schalk W. Cronjé * @@ -41,7 +42,7 @@ class AsciidoctorBasePlugin implements Plugin { private static final Pattern DEPS_TASK_PATTERN = ~/^(.+)Dependencies$/ void apply(Project project) { - project.apply plugin: 'base' + project.pluginManager.apply LifecycleBasePlugin ProjectOperations.maybeCreateExtension(project) registerDependencyReportRules(project) } @@ -49,15 +50,14 @@ class AsciidoctorBasePlugin implements Plugin { private void registerDependencyReportRules(Project project) { TaskContainer tasks = project.tasks tasks.addRule( - 'Dependencies: Report dependencies for AsciidoctorJ tasks' + 'Dependencies: Report dependencies for AsciidoctorJ(S) tasks' ) { String targetTaskName -> Matcher matcher = targetTaskName =~ DEPS_TASK_PATTERN if (matcher.matches()) { try { - TaskProvider associate = TaskProvider.taskByTypeAndName( - project, - AbstractAsciidoctorBaseTask, - taskBaseName(matcher) + final associate = project.tasks.named( + taskBaseName(matcher), + AbstractAsciidoctorBaseTask ) tasks.create(targetTaskName, DependencyReportTask, new Action() { @@ -79,5 +79,4 @@ class AsciidoctorBasePlugin implements Plugin { private String taskBaseName(Matcher matcher) { matcher[0][1] } - } \ No newline at end of file diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorExecutionException.groovy b/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorExecutionException.groovy index 7781ebb92..6ddb7f2e9 100644 --- a/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorExecutionException.groovy +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorExecutionException.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorModuleDefinition.java b/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorModuleDefinition.java index 618f9ad0b..836b73c88 100644 --- a/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorModuleDefinition.java +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorModuleDefinition.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,38 +17,44 @@ import org.gradle.api.Named; -/** Describes a converter. +/** + * Describes a converter. * * @since 2.2.0 */ public interface AsciidoctorModuleDefinition extends Named { - /** Use the default version of this component + /** + * Use the default version of this component * */ void use(); - /** Set the version of the component to use. + /** + * Set the version of the component to use. * * @param o Any object can can be converted to a String using * {@code org.ysb33r.grolifant.api.StringUtils.stringize} */ void setVersion(Object o); - /** Declarative way in DSL to set the version of the component to use. + /** + * Declarative way in DSL to set the version of the component to use. * * @param o Any object can can be converted to a String using * {@code org.ysb33r.grolifant.api.StringUtils.stringize} */ void version(Object o); - /** Version of the component + /** + * Version of the component * * @return Returns version to use, or {@code null} if no version was set. * The latter usually implies that the specific component is not needed. */ String getVersion(); - /** Whether the component has been allocated a version. + /** + * Whether the component has been allocated a version. * * @return {@code true} if the component has been defined */ diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorMultiLanguageException.groovy b/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorMultiLanguageException.groovy index d4f1f3cf3..bcf0d8bb6 100644 --- a/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorMultiLanguageException.groovy +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorMultiLanguageException.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorTaskAttributes.java b/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorTaskAttributes.java new file mode 100644 index 000000000..fd92a904f --- /dev/null +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorTaskAttributes.java @@ -0,0 +1,85 @@ +/* + * Copyright 2013-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://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.asciidoctor.gradle.base; + +import groovy.lang.Closure; +import groovy.lang.DelegatesTo; +import org.asciidoctor.gradle.base.basedir.BaseDirIsNull; +import org.gradle.api.Action; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.file.CopySpec; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.FileTree; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.*; +import org.gradle.api.tasks.util.PatternSet; + +import java.io.File; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +/** + * The attribute methods all Asciidoctor conversion tasks will offer. + * + * @since 4.0 + * + * @author Schalk W. Cronjé + */ +public interface AsciidoctorTaskAttributes { + /** + * Shortcut method for obtaining attributes. + * + * In most implementations this will just access the {@code getAttributes} method + * on the appropriate task extension derived from {@link AbstractImplementationEngineExtension} + * + * @return Access to attributes hashmap + */ + @Input + Map getAttributes(); + + /** + * Shortcut method to apply a new set of Asciidoctor attributes, clearing any attributes previously set. + * + * In most implementations this will just access the {@code setAttributes} method + * on the appropriate task extension derived from {@link AbstractImplementationEngineExtension} + * + * @param m Map with new options + */ + void setAttributes(Map m); + + /** + * Shortcut method to add additional asciidoctor attributes. + * + * In most implementations this will just access the {@code attributes} method + * on the appropriate task extension derived from {@link AbstractImplementationEngineExtension} + * + * @param m Map with new options + */ + void attributes(Map m); + + /** + * Shortcut method to access additional providers of attributes. + * + * In most implementations this will just access the {@code getAttributeProviders} method + * on the appropriate task extension derived from {@link AbstractImplementationEngineExtension} + * + * @return List of attribute providers. + */ + @Internal + List getAttributeProviders(); +} diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorTaskBaseDirConfiguration.java b/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorTaskBaseDirConfiguration.java new file mode 100644 index 000000000..6bf675480 --- /dev/null +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorTaskBaseDirConfiguration.java @@ -0,0 +1,113 @@ +/* + * Copyright 2013-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://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.asciidoctor.gradle.base; + +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.Internal; + +import javax.annotation.Nullable; +import java.io.File; + +/** + * Asciidoctor base directories need special care and those methods are specified by this interface. + * + *

    + * Getters in this interface are all annotated with {@code Internal} as they should be participate in + * task properties for up to date checks. + *

    + * @author Schalk W. Cronjé + * + * @since 4.0 + */ +public interface AsciidoctorTaskBaseDirConfiguration { + + /** + * The base dir will be the same as the source directory. + *

    + * If an intermediate working directory is used, the the base dir will be where the + * source directory is located within the temporary working directory. + *

    + */ + void baseDirFollowsSourceDir(); + + /** + * Sets the basedir to be the same directory as each individual source file. + */ + void baseDirFollowsSourceFile(); + + /** + * Sets the basedir to be the same directory as the current project directory. + * + * @since 2.2.0 + */ + void baseDirIsProjectDir(); + + /** + * Sets the basedir to be the same directory as the root project directory. + */ + void baseDirIsRootProjectDir(); + + /** Base directory (current working directory) for a conversion. + * + * @return Base directory. Can be {@code null} + */ + @Internal + @Nullable + default File getBaseDir() { + return getBaseDirProvider().getOrNull(); + } + + /** + * Base directory (current working directory) for a conversion. + * + * Depending on the strateggy in use, the source language used in the conversion + * may change the final base directory relative to the value returned by {@link #getBaseDir}. + * + * @param lang Language in use + * @return Language-dependent base directory + */ + File getBaseDir(String lang); + + @Internal + Provider getBaseDirProvider(); + + /** + * Returns the current basedir strategy if it has been configured. + * + * @return Strategy or @{code null}. + */ + @Nullable + @Internal + BaseDirStrategy getBaseDirStrategy(); + + /** + * Checks whether an explicit strategy has been set for base directory. + * + * @return {@code true} if a strategy has been configured. + */ + @Internal + boolean isBaseDirConfigured(); + + /** + * Sets the base directory for a conversion. + *

    + * The base directory is used by AsciidoctorJ to set a current working directory for + * a conversion. If never set, then {@code project.projectDir} will be assumed to be the base directory. + *

    + * @param f Base directory + */ + void setBaseDir(Object f); +} diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorTaskFileOperations.java b/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorTaskFileOperations.java new file mode 100644 index 000000000..f7b4e8148 --- /dev/null +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorTaskFileOperations.java @@ -0,0 +1,363 @@ +/* + * Copyright 2013-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://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.asciidoctor.gradle.base; + +import groovy.lang.Closure; +import groovy.lang.DelegatesTo; +import groovy.transform.CompileDynamic; +import org.asciidoctor.gradle.base.basedir.BaseDirIsNull; +import org.gradle.api.Action; +import org.gradle.api.file.CopySpec; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.FileTree; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.*; +import org.gradle.api.tasks.util.PatternSet; + +import java.io.File; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +/** + * The standard methods all Asciidoctor conversion tasks will offer. + * + * @since 4.0 + * + * @author Schalk W. Cronjé + */ +public interface AsciidoctorTaskFileOperations { + + /** + * The name of the Asciidoctor engine implementation. + * + * @return Engine name. + */ + @Internal + String getEngineName(); + + /** + * Logs documents as they are converted + * + */ + @Console + boolean getLogDocuments(); + + /** + * Whether to log documents as they are being converted. + * + * @param mode Set {@code true} in order to log documents. + */ + void setLogDocuments(boolean mode); + + + /** + * Sets the new Asciidoctor parent source directory. + * + * @param f Any object convertible with {@code project.file}. + */ + void setSourceDir(Object f); + + /** + * Sets the new Asciidoctor parent source directory in a declarative style. + * + * @param f Any object convertible with {@code project.file}. + */ + void sourceDir(Object f); + + /** + * Returns the parent directory for Asciidoctor source. + * + * @return Location of source directory. + */ + @Internal + default File getSourceDir() { + return getSourceDirProperty().getAsFile().get(); + } + + /** + * Returns the parent directory for Asciidoctor source as a property object. + * + * @return Source directory as a property. + */ + @Internal + DirectoryProperty getSourceDirProperty(); + + /** + * Returns the current toplevel output directory + * + * @return Output directory. + */ + @OutputDirectory + default File getOutputDir() { + return getOutputDirProperty().getAsFile().get(); + } + + /** + * Sets the new Asciidoctor parent output directory. + * + * @param f An object convertible via {@code project.file}. + */ + void setOutputDir(Object f); + + /** + * Returns the current toplevel output directory as a property object. + * + * @return Output directory as a property. + */ + @Internal + DirectoryProperty getOutputDirProperty(); + + /** Configures sources. + * + * @param cfg Configuration closure. Is passed a {@link PatternSet}. + */ + void sources(@DelegatesTo(PatternSet.class) Closure cfg); + + /** Configures sources. + * + * @param cfg Configuration {@link org.gradle.api.Action}. Is passed a {@link PatternSet}. + */ + void sources(final Action cfg); + + /** + * Include source patterns. + * + * @param includePatterns ANT-style patterns for sources to include + */ + void sources(String... includePatterns); + + /** + * Clears existing sources patterns. + */ + void clearSources(); + + /** + * Clears any of the existing secondary soruces patterns. + * + * This should be used if none of the default patterns should be monitored. + */ + void clearSecondarySources(); + + /** Configures secondary sources. + * + * @param cfg Configuration closure. Is passed a {@link PatternSet}. + */ + void secondarySources(@DelegatesTo(PatternSet.class) Closure cfg); + + /** Configures sources. + * + * @param cfg Configuration {@link Action}. Is passed a {@link PatternSet}. + */ + void secondarySources(final Action cfg); + + /** + * A provider of patterns identifying intermediate artifacts. + * + * @return Provider to a {@link PatternSet}. Can be empty. + */ + @Internal + Provider getIntermediateArtifactPatternProvider(); + + /** + * Returns the copy specification for the resources of a specific language. + * + * @param lang Language + * + * @return Copy specification. Can be {@code null}. + */ + CopySpec getLanguageResourceCopySpec(String lang); + + /** + * Gets the CopySpec for additional resources. + * + * If {@code resources} was never called, it will return a default CopySpec otherwise it will return the + * one built up via successive calls to {@code resources} + * + * @param lang Language to to apply to or empty for no-language support. + * @return A{@link CopySpec}. Never {@code null}. + */ + CopySpec getResourceCopySpec(Optional lang); + + /** + * The default CopySpec that will be used if {@code resources} was never called + * + * By default, anything below {@code $sourceDir/images} will be included. + * + * @param lang Language to use. Can be empty (not {@code null}) when not to use a language. + * @return A{@link CopySpec}. Never {@code null}. + */ + CopySpec getDefaultResourceCopySpec(Optional lang); + + /** + * Returns a FileTree containing all source documents + * + * If a filter with {@link #sources} was never set then all asciidoc source files + * below {@link #setSourceDir} will be included. If multiple languages are used, all + * * language secondary source sets are included. + * @return Applicable source trees. + */ + @Internal + FileTree getSourceFileTree(); + + /** + * Returns a FileTree containing all secondary source documents. + * + * If a filter with {@link #secondarySources} was never set then all asciidoc source files + * below {@link #setSourceDir} will be included. If multiple languages are used, all + * language secondary source sets are included. + * + * @return Collection of secondary source files + * + */ + @Internal + FileTree getSecondarySourceFileTree(); + + /** + * Add to the CopySpec for extra files. The destination of these files will always have a parent directory + * of {@code outputDir} or {@code outputDir + backend} + * + * @param cfg {@link CopySpec} runConfiguration closure + */ + void resources(Closure cfg); + + /** + * Add to the CopySpec for extra files. The destination of these files will always have a parent directory + * of {@code outputDir} or {@code outputDir + backend} + * + * @param cfg {@link CopySpec} runConfiguration {@link Action} + */ + void resources(Action cfg); + + /** + * Add to the CopySpec for extra files. The destination of these files will always have a parent directory + * of {@code outputDir} or {@code outputDir + backend} + * + * If not languages are set. these resources will be ignored. + * + * @param cfg {@link CopySpec} runConfiguration closure + * @param lang Language to which these resources will be applied to. + */ + void resources(final String lang, Closure cfg); + + /** Add to the CopySpec for extra files. The destination of these files will always have a parent directory + * of {@code outputDir} or {@code outputDir + backend} + * + * If not languages are set. these resources will be ignored. + * + * @param cfg {@link CopySpec} runConfiguration {@link Action} + * @param lang Language to which these resources will be applied to. + * @since 3.0.0 + */ + void resources(final String lang, Action cfg); + + /** + * Some extensions such as {@code ditaa} creates images in the source directory. + * + * Use this setting to copy all sources and resources to an intermediate work directory + * before processing starts. This will keep the source directory pristine + */ + void useIntermediateWorkDir(); + + /** + * Checks whether an intermediate workdir is required. + * + * @return {@code true} is there is an intermediate working directory. + */ + boolean hasIntermediateWorkDir(); + + /** + * The document conversion might generate additional artifacts that could + * require copying to the final destination. + * + * An example is use of {@code ditaa} diagram blocks. These artifacts can be specified + * in this block. Use of the option implies {@link #useIntermediateWorkDir}. + * + * @param cfg Configures a {@link PatternSet} with a base directory of the intermediate working + * directory. + */ + void withIntermediateArtifacts(@DelegatesTo(PatternSet.class) Closure cfg); + + /** + * Additional artifacts created by Asciidoctor that might require copying. + * + * @param cfg Action that configures a {@link PatternSet}. + * + * @see {@link #withIntermediateArtifacts(Closure cfg)} + */ + void withIntermediateArtifacts(final Action cfg); + + /** + * The directory that will be the intermediate directory should one be required. + * + * @return Intermediate working directory + */ + @Internal + default File getIntermediateWorkDir() { + return getIntermediateWorkDirProvider().get(); + } + + @Internal + Provider getIntermediateWorkDirProvider(); + + /** + * Returns a list of all output directories by backend + */ + @OutputDirectories + Set getBackendOutputDirectories(); + + /** + * Obtain List of languages the sources documents are written in. + * + * @return List of languages. Can be empty, but never {@code null}. + */ + @Input + List getLanguages(); + + /** + * Reset current list of languages and replace with a new set. + * + * @param langs List of new languages + */ + void setLanguages(Iterable langs); + + /** + * Add to list of languages to process. + * + * @param langs List of additional languages + */ + void languages(Iterable langs); + + /** + * Add to list of languages to process. + * + * @param langs List of additional languages + */ + void languages(String... langs); + + /** + * A task may add some default attributes. + * If the user specifies any of these attributes, then those attributes will not be utilised. + * The default implementation will add {@code includedir}, {@code revnumber}, {@code gradle-project-group}, + * {@code gradle-project-name} + * + * @param workingSourceDir Directory where source files are located. + * + * @return A collection of default attributes. + */ + Map getTaskSpecificDefaultAttributes(File workingSourceDir); +} diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorTaskMethods.java b/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorTaskMethods.java new file mode 100644 index 000000000..f4b878c75 --- /dev/null +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorTaskMethods.java @@ -0,0 +1,27 @@ +/* + * Copyright 2013-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://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.asciidoctor.gradle.base; + +/** + * Methods that an ASciidoctor conversion task should implement. + * + * @author Schalk W. Cronjé + * @since 4.0 + */ +public interface AsciidoctorTaskMethods extends AsciidoctorTaskFileOperations, AsciidoctorTaskAttributes, + AsciidoctorTaskReportableConfigurations, AsciidoctorTaskWorkspacePreparation, + AsciidoctorTaskBaseDirConfiguration { +} diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorTaskOutputOptions.java b/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorTaskOutputOptions.java new file mode 100644 index 000000000..4de1102b6 --- /dev/null +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorTaskOutputOptions.java @@ -0,0 +1,121 @@ +/* + * Copyright 2013-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://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.asciidoctor.gradle.base; + +import org.gradle.api.tasks.Internal; +import org.gradle.api.tasks.Nested; +import org.gradle.api.tasks.OutputDirectories; + +import java.io.File; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +/** + * Asciidoctor conversion output options. + * + * @authpr Schalk W. Cronje + * @since 4.0 + */ +public interface AsciidoctorTaskOutputOptions { + + /** + * The current set of configured Asciidoctor backends. + * + * @return Backends. Can be empty. Never null. + */ + Set backends(); + + /** + * Copies all resources to the output directory. + * + * Some backends (such as {@code html5}) require all resources to be copied to the output directory. + * This is the default behaviour for this task. + */ + void copyAllResources(); + + /** Do not copy any resources to the output directory. + * + * Some backends (such as {@code pdf}) process all resources in place. + * + */ + void copyNoResources(); + + /** + * Copy resources for a backend name. + * + * @param backendName Name of backend for which resources are copied + * @param sourceDir Source directory of resources + * @param outputDir Final output directory. + * @param includeLang If set also copy resources for this specified language + */ + void copyResourcesByBackend( + final String backendName, + final File sourceDir, + final File outputDir, + Optional includeLang + ); + + /** + * Copy resources to the output directory only if the backend names matches any of the specified + * names. + * + * @param backendNames List of names for which resources should be copied. + * + */ + void copyResourcesOnlyIf(String... backendNames); + + /** + * Returns a list of all output directories by backend + * + * @return Output directories + */ + @OutputDirectories + Set getBackendOutputDirectories(); + + /** List of backends for which to copy resources. + * + * @return List of backends. Can be {@code null}. + */ + @Internal + Optional> getCopyResourcesForBackends(); + + /** + * + * Get the output directory for a specific backend. + * + * @param backendName Name of backend + * @return Output directory. + */ + File getOutputDirForBackend(final String backendName); + + /** + * Get the output directory for a specific backend. + * + * @param backendName Name of backend + * @param language Language for which sources are being generated. + * @return Output directory. + */ + File getOutputDirForBackend(final String backendName, final String language); + + /** + * Access to the underlying {@link OutputOptions}. + * + * @return {@link OutputOptions} instance. + */ + @Nested + OutputOptions getOutputOptions(); +} diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorTaskReportableConfigurations.java b/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorTaskReportableConfigurations.java new file mode 100644 index 000000000..c4454a444 --- /dev/null +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorTaskReportableConfigurations.java @@ -0,0 +1,31 @@ +/* + * Copyright 2013-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://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.asciidoctor.gradle.base; + +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.tasks.Internal; + +import java.util.Set; + +public interface AsciidoctorTaskReportableConfigurations { + /** + * Configurations for which dependencies should be reported. + * + * @return Set of configurations. Can be empty, but never {@code null}. + */ + @Internal + Set getReportableConfigurations(); +} diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorTaskTreeOperations.java b/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorTaskTreeOperations.java new file mode 100644 index 000000000..33d572a8c --- /dev/null +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorTaskTreeOperations.java @@ -0,0 +1,99 @@ +/* + * Copyright 2013-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://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.asciidoctor.gradle.base; + +import org.gradle.api.InvalidUserDataException; +import org.gradle.api.file.FileTree; +import org.gradle.api.tasks.Internal; +import org.gradle.api.tasks.util.PatternSet; + +import javax.annotation.Nullable; +import java.io.File; + +/** + * Additional operations on the source and destinations trees for Asciidoctor conversions. + * + * @authro Schalk W. Cronjé + * + * @since 4.0 + */ +public interface AsciidoctorTaskTreeOperations { + + /** + * Validates that the path roots are sane. + * + * @param baseDir Base directory strategy. Can be {@code null} + */ + void checkForIncompatiblePathRoots(@Nullable BaseDirStrategy baseDir); + + /** + * Validates the source tree + */ + void checkForInvalidSourceDocuments() throws InvalidUserDataException; + + /** + * The default pattern set for secondary sources. + * + * @return By default all *.adoc,*.ad,*.asc,*.asciidoc is included. + */ + @Internal + PatternSet getDefaultSecondarySourceDocumentPattern() throws InvalidUserDataException; + + /** + * The default {@link PatternSet} that will be used if {@code sources} was never called + * + * @return By default all *.adoc,*.ad,*.asc,*.asciidoc is included. + * Files beginning with underscore are excluded + */ + @Internal + PatternSet getDefaultSourceDocumentPattern(); + + /** + * Gets the language-specific secondary source tree. + * + * @param lang Language + * @return Language-specific source tree. + */ + @Internal + FileTree getLanguageSecondarySourceFileTree(final String lang); + + /** + * Gets the language-specific source tree + * + * @param lang Language + * @return Language-specific source tree. + */ + @Internal + FileTree getLanguageSourceFileTree(final String lang); + + /** + * Obtains a secondary source tree based on patterns. + * + * @param dir Toplevel source directory. + * @return Source tree based upon configured pattern. + */ + @Internal + FileTree getSecondarySourceFileTreeFrom(File dir); + + /** + * Obtains a source tree based on patterns. + * + * @param dir Toplevel source directory. + * @return Source tree based upon configured pattern. + */ + @Internal + FileTree getSourceFileTreeFrom(File dir); +} diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorTaskWorkspacePreparation.java b/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorTaskWorkspacePreparation.java new file mode 100644 index 000000000..891f87757 --- /dev/null +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorTaskWorkspacePreparation.java @@ -0,0 +1,59 @@ +/* + * Copyright 2013-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://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.asciidoctor.gradle.base; + +import org.asciidoctor.gradle.base.internal.Workspace; + +import java.util.Optional; + +/** + * Methods for preparing an Asciidoctor worksapce prior to conversion. + * + * @author Schalk W> Cronjé + * @since 4.0 + */ +public interface AsciidoctorTaskWorkspacePreparation { + /** + * Prepares a workspace prior to conversion. + * + * @return A presentation of the working source directory and the source tree. + */ + Workspace prepareWorkspace(); + + /** + * Prepares a workspace for a specific language prior to conversion. + * + * @param language Language to prepare workspace for. + * @return A presentation of the working source directory and the source tree. + */ + Workspace prepareWorkspace(String language); + + + /** + * Prepares a workspace for a specific language prior to conversion. + * If the language is not present, it behaves like {@link #prepareWorkspace()}. + * + * @param language Language to prepare workspace for. + * @return A presentation of the working source directory and the source tree. + */ + default Workspace prepareWorkspace(Optional language) { + if(language.isPresent()) { + return prepareWorkspace(language.get()); + } else { + return prepareWorkspace(); + } + } +} diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorUtils.groovy b/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorUtils.groovy index 0f03e24b2..20f36750a 100644 --- a/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorUtils.groovy +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/AsciidoctorUtils.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ import org.gradle.api.specs.Spec import org.gradle.api.tasks.util.PatternSet import org.gradle.util.GradleVersion import org.ysb33r.grolifant.api.core.OperatingSystem +import org.ysb33r.grolifant.api.core.ProjectOperations import java.nio.file.Path @@ -53,7 +54,7 @@ class AsciidoctorUtils { static final Spec ACCEPT_ONLY_FILES = new Spec() { @Override boolean isSatisfiedBy(File element) { - element.isFile() + element.file } } @@ -67,20 +68,40 @@ class AsciidoctorUtils { * @param filePatterns Patterns to use to identify suitable sources. * @return A collection of suitable files. * @throw {@link GradleException} is files starting with undersocres are detected. + * + * @deprecated */ + @Deprecated static FileTree getSourceFileTree(final Project project, final File sourceDir, final PatternSet filePatterns) { - FileTree ft = project.fileTree(sourceDir). - matching(filePatterns).filter(ACCEPT_ONLY_FILES).asFileTree + getSourceFileTree( + ProjectOperations.find(project), + sourceDir, + filePatterns + ) + } + + /** Gets a fileTree that described an Asciidoctor set of source files. + * + * @param po {@link ProjectOperations} instance to applu + * @param sourceDir Base directory for the sourcs. + * @param filePatterns Patterns to use to identify suitable sources. + * @return A collection of suitable files. + * @throw {@link GradleException} is files starting with undersocres are detected. + */ + static FileTree getSourceFileTree(final ProjectOperations po, final File sourceDir, final PatternSet filePatterns) { + FileTree ft = po.fileTree(sourceDir). + matching(filePatterns).filter(ACCEPT_ONLY_FILES).asFileTree ft.visit { FileVisitDetails it -> if (it.name.startsWith('_')) { throw new GradleException("Sources starting with '_' found. This is not allowed. " + - "Current sources are: ${ft.files}") + "Current sources are: ${ft.files}") } } ft } + /* */ /** Normalises slashes in a path. @@ -168,11 +189,7 @@ class AsciidoctorUtils { * @since 3.0 */ static void setConvention(Property property, Provider value) { - if (GRADLE_LT_5_1) { - doSetPropertyConventionPre51(property, value) - } else { - doSetPropertyConvention(property, value) - } + doSetPropertyConvention(property, value) } /** Maps a file object to a directory provider @@ -202,30 +219,9 @@ class AsciidoctorUtils { * @since 3.0 */ static DirectoryProperty createDirectoryProperty(Project project) { - if (GRADLE_LT_5_0) { - doCreateDirectoryPropertyPre50(project) - } else { - doCreateDirectoryProperty(project) - } - } - - @CompileDynamic - private static DirectoryProperty doCreateDirectoryPropertyPre50(Project project) { - project.layout.directoryProperty() - } - - @CompileDynamic - private static DirectoryProperty doCreateDirectoryProperty(Project project) { project.objects.directoryProperty() } - @CompileDynamic - private static void doSetPropertyConventionPre51(Property property, Provider value) { - if (!property.isPresent()) { - property.set(value) - } - } - @CompileDynamic private static void doSetPropertyConvention(Property property, Provider value) { property.convention(value) diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/BaseDirStrategy.groovy b/base/src/main/groovy/org/asciidoctor/gradle/base/BaseDirStrategy.groovy index f8e67c036..dce89ac81 100644 --- a/base/src/main/groovy/org/asciidoctor/gradle/base/BaseDirStrategy.groovy +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/BaseDirStrategy.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/ModuleNotFoundException.groovy b/base/src/main/groovy/org/asciidoctor/gradle/base/ModuleNotFoundException.groovy index b0cfb5d19..976c90fff 100644 --- a/base/src/main/groovy/org/asciidoctor/gradle/base/ModuleNotFoundException.groovy +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/ModuleNotFoundException.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/ModuleVersionLoader.groovy b/base/src/main/groovy/org/asciidoctor/gradle/base/ModuleVersionLoader.groovy index f06699e87..a2acfad4e 100644 --- a/base/src/main/groovy/org/asciidoctor/gradle/base/ModuleVersionLoader.groovy +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/ModuleVersionLoader.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,6 @@ package org.asciidoctor.gradle.base import groovy.transform.CompileStatic -import org.ysb33r.grolifant.api.v4.MapUtils /** Loads the versions of Asciidoctor modules that are required for a specific plugin. * @@ -43,7 +42,7 @@ class ModuleVersionLoader { try { Properties props = new Properties() props.load(stream) - MapUtils.stringizeValues(props as Map).asImmutable() + props.collectEntries { k, v -> [k, v.toString()] }.asImmutable() as Map } finally { stream.close() } diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/OutputOptions.groovy b/base/src/main/groovy/org/asciidoctor/gradle/base/OutputOptions.groovy index 115a27fca..da4bf2adc 100644 --- a/base/src/main/groovy/org/asciidoctor/gradle/base/OutputOptions.groovy +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/OutputOptions.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/SafeMode.groovy b/base/src/main/groovy/org/asciidoctor/gradle/base/SafeMode.groovy index f899c579b..ec1a8d59f 100644 --- a/base/src/main/groovy/org/asciidoctor/gradle/base/SafeMode.groovy +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/SafeMode.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/Transform.groovy b/base/src/main/groovy/org/asciidoctor/gradle/base/Transform.groovy index 36a13e353..6d61ee308 100644 --- a/base/src/main/groovy/org/asciidoctor/gradle/base/Transform.groovy +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/Transform.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/basedir/BaseDirFollowsProject.groovy b/base/src/main/groovy/org/asciidoctor/gradle/base/basedir/BaseDirFollowsProject.groovy index 1cd15694e..13a5dd52c 100644 --- a/base/src/main/groovy/org/asciidoctor/gradle/base/basedir/BaseDirFollowsProject.groovy +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/basedir/BaseDirFollowsProject.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,10 +29,22 @@ import org.gradle.api.Project @CompileStatic class BaseDirFollowsProject implements BaseDirStrategy { - private final Project project + private final File projectDir + @Deprecated BaseDirFollowsProject(Project project) { - this.project = project + this.projectDir = project.projectDir + } + + /** + * Sets the project by the project directory. + * + * @param projectDir Project directory. + * + * @since 4.0 + */ + BaseDirFollowsProject(final File projectDir) { + this.projectDir = projectDir } /** Base directory location. @@ -41,7 +53,7 @@ class BaseDirFollowsProject implements BaseDirStrategy { */ @Override File getBaseDir() { - project.projectDir + this.projectDir } /** Base directory location. diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/basedir/BaseDirFollowsRootProject.groovy b/base/src/main/groovy/org/asciidoctor/gradle/base/basedir/BaseDirFollowsRootProject.groovy index 09dc4aa80..cb2acc3ad 100644 --- a/base/src/main/groovy/org/asciidoctor/gradle/base/basedir/BaseDirFollowsRootProject.groovy +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/basedir/BaseDirFollowsRootProject.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,10 +27,12 @@ import org.gradle.api.Project * @since 2.2.0 */ @CompileStatic +@Deprecated class BaseDirFollowsRootProject implements BaseDirStrategy { private final Project project + @Deprecated BaseDirFollowsRootProject(Project project) { this.project = project } diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/basedir/BaseDirIsFixedPath.groovy b/base/src/main/groovy/org/asciidoctor/gradle/base/basedir/BaseDirIsFixedPath.groovy index 228274666..e3c295cb6 100644 --- a/base/src/main/groovy/org/asciidoctor/gradle/base/basedir/BaseDirIsFixedPath.groovy +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/basedir/BaseDirIsFixedPath.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/basedir/BaseDirIsNull.groovy b/base/src/main/groovy/org/asciidoctor/gradle/base/basedir/BaseDirIsNull.groovy index 30fe71199..0b730f29c 100644 --- a/base/src/main/groovy/org/asciidoctor/gradle/base/basedir/BaseDirIsNull.groovy +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/basedir/BaseDirIsNull.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,7 @@ import org.asciidoctor.gradle.base.BaseDirStrategy */ @CompileStatic class BaseDirIsNull implements BaseDirStrategy { - static final BaseDirIsNull INSTANCE = new BaseDirIsNull() + static public final BaseDirIsNull INSTANCE = new BaseDirIsNull() final File baseDir = null diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/internal/AsciidoctorAttributes.groovy b/base/src/main/groovy/org/asciidoctor/gradle/base/internal/AsciidoctorAttributes.groovy index a69962ed2..3472cbe97 100644 --- a/base/src/main/groovy/org/asciidoctor/gradle/base/internal/AsciidoctorAttributes.groovy +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/internal/AsciidoctorAttributes.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package org.asciidoctor.gradle.base.internal import groovy.transform.CompileStatic +import org.asciidoctor.gradle.base.AsciidoctorAttributeProvider +import org.asciidoctor.gradle.base.Transform import org.gradle.api.provider.Provider import org.ysb33r.grolifant.api.core.ProjectOperations import org.ysb33r.grolifant.api.core.StringTools @@ -63,6 +65,91 @@ class AsciidoctorAttributes { resolvedAs } + /** + * Resolves all provider into the underlying type. + * + * @param initialMap Map possible containing Providers as values. + * @return Map with top-layer of providers stripped out. + * + * @since 4.0 + */ + static Map evaluateProviders(final Map initialMap) { + initialMap.collectEntries { String k, Object v -> + if (v instanceof Provider) { + [k, v.get()] + } else { + [k, v] + } + } as Map + } + + /** + * Prepare attributes to be serialisable + * + * @param stringTools {@link StringTools} instance to use for stringification. + * @param workingSourceDir Working source directory from which source documents will be made available. + * @param seedAttributes Initial attributes set on the task. + * @param langAttributes Any language specific attributes. + * @param tdsAttributes Task-specific default attributes. + * @param attributeProviders Additional attribute providers. + * @param lang Language being processed. Can be unset if multi-language feature is not used. + * @return Attributes ready for serialisation. + * + * @since 3.0.0 + */ + @SuppressWarnings('ParameterCount') + static Map prepareAttributes( + final StringTools stringTools, + Map seedAttributes, + Map langAttributes, + Map tsdAttributes, + List attributeProviders, + Optional lang + ) { + Map attrs = [:] + attrs.putAll(seedAttributes) + attrs.putAll(langAttributes) + attributeProviders.each { + attrs.putAll(it.attributes) + } + + Map defaultAttrs = prepareDefaultAttributes( + stringTools, + attrs, + tsdAttributes, + lang + ) + attrs.putAll(defaultAttrs) + evaluateProviders(attrs) + } + + private static Map prepareDefaultAttributes( + final StringTools stringTools, + Map seedAttributes, + Map defaultAttributes, + Optional lang + ) { + Set userDefinedAttrKeys = trimOverridableAttributeNotation(seedAttributes.keySet()) + + Map defaultAttrs = defaultAttributes.findAll { k, v -> + !userDefinedAttrKeys.contains(k) + }.collectEntries { k, v -> + ["${k}@".toString(), v instanceof Serializable ? v : stringTools.stringize(v)] + } as Map + + if (lang.present) { + defaultAttrs.put('lang@', lang.get()) + } + + defaultAttrs + } + + private static Set trimOverridableAttributeNotation(Set attributeKeys) { + // remove possible trailing '@' character that is used to encode that the attribute can be overridden + // in the document itself + Transform.toSet(attributeKeys) { k -> k - ~/@$/ } + } + private static Object resolveItemAsBasicType(Object value, StringTools stringTools) { switch (value) { case URI: diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/internal/ConfigurationUtils.groovy b/base/src/main/groovy/org/asciidoctor/gradle/base/internal/ConfigurationUtils.groovy deleted file mode 100644 index 48f75ce0a..000000000 --- a/base/src/main/groovy/org/asciidoctor/gradle/base/internal/ConfigurationUtils.groovy +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2013-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://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.asciidoctor.gradle.base.internal - -import groovy.transform.CompileStatic -import org.asciidoctor.gradle.base.Transform -import org.gradle.api.Project -import org.gradle.api.artifacts.Configuration -import org.gradle.api.provider.Provider -import org.ysb33r.grolifant.api.v4.StringUtils - -/** Utilities for dealing with Configurations - * - * @since 3.0.0 - */ -@CompileStatic -class ConfigurationUtils { - - /** Extracts a {@link Configuration} if possible. - * - * @param project Associated project - * @param sourceConfig Item that could be a source of a configuration - * @return {@link Configuration} instance - * - * @throw {@code UnknownConfigurationException} - */ - static Configuration asConfiguration(Project project, Object sourceConfig) { - switch (sourceConfig.class) { - case Configuration: - return (Configuration) sourceConfig - case Provider: - return asConfiguration(project, ((Provider) sourceConfig).get()) - default: - project.configurations.getByName(StringUtils.stringize(sourceConfig)) - } - } - - /** Extracts a list of {@link Configuration} instances. - * - * @param project Associated project. - * @param sourceConfigs Collection of items that could be sources of configurations. - * @return List of {@link Configuration} instances. - * - * @throw {@code UnknownConfigurationException} - */ - static List asConfigurations(Project project, Collection sourceConfigs) { - Transform.toList(sourceConfigs) { - asConfiguration(project, it) - } - } -} diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/internal/DefaultAsciidoctorBaseDirConfiguration.groovy b/base/src/main/groovy/org/asciidoctor/gradle/base/internal/DefaultAsciidoctorBaseDirConfiguration.groovy new file mode 100644 index 000000000..ba4766cf9 --- /dev/null +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/internal/DefaultAsciidoctorBaseDirConfiguration.groovy @@ -0,0 +1,158 @@ +/* + * Copyright 2013-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://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.asciidoctor.gradle.base.internal + +import groovy.transform.CompileStatic +import org.asciidoctor.gradle.base.AsciidoctorTaskBaseDirConfiguration +import org.asciidoctor.gradle.base.AsciidoctorTaskFileOperations +import org.asciidoctor.gradle.base.BaseDirStrategy +import org.asciidoctor.gradle.base.basedir.BaseDirFollowsProject +import org.asciidoctor.gradle.base.basedir.BaseDirIsFixedPath +import org.asciidoctor.gradle.base.basedir.BaseDirIsNull +import org.gradle.api.Project +import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider +import org.ysb33r.grolifant.api.core.ProjectOperations + +import javax.annotation.Nullable +import java.util.concurrent.Callable + +/** + * The default implementation for base directory copnfiguration. + * + * @author Schalk W. Cronjé + * + * @since 4.0 + */ +@CompileStatic +class DefaultAsciidoctorBaseDirConfiguration implements AsciidoctorTaskBaseDirConfiguration { + private BaseDirStrategy baseDirStrategy + private final Property baseDirProperty + private final ProjectOperations po + private final AsciidoctorTaskFileOperations fileOperations + + DefaultAsciidoctorBaseDirConfiguration(Project project, AsciidoctorTaskFileOperations atfo) { + this.po = ProjectOperations.find(project) + this.fileOperations = atfo + this.baseDirProperty = project.objects.property(File) + this.baseDirProperty.set(project.provider { -> + owner.baseDirStrategy ? owner.baseDirStrategy.baseDir : owner.po.projectDir + }) + } + + /** + * The base dir will be the same as the source directory. + *

    + * If an intermediate working directory is used, the the base dir will be where the + * source directory is located within the temporary working directory. + *

    + */ + @Override + void baseDirFollowsSourceDir() { + this.baseDirStrategy = new BaseDirIsFixedPath(po.provider({ AsciidoctorTaskFileOperations task -> + task.hasIntermediateWorkDir() ? task.intermediateWorkDir : task.sourceDir + }.curry(fileOperations) as Callable)) + } + + /** + * Sets the basedir to be the same directory as each individual source file. + */ + @Override + void baseDirFollowsSourceFile() { + this.baseDirStrategy = BaseDirIsNull.INSTANCE + } + + /** + * Sets the basedir to be the same directory as the current project directory. + * + * @since 2.2.0 + */ + @Override + void baseDirIsProjectDir() { + this.baseDirStrategy = new BaseDirFollowsProject(po.projectDir) + } + + /** + * Sets the basedir to be the same directory as the root project directory. + */ + @Override + void baseDirIsRootProjectDir() { + this.baseDirStrategy = new BaseDirFollowsProject(po.projectRootDir) + } + + /** + * Base directory (current working directory) for a conversion. + * + * Depending on the strateggy in use, the source language used in the conversion + * may change the final base directory relative to the value returned by {@link #getBaseDir}. + * + * @param lang Language in use + * @return Language-dependent base directory + */ + @Override + File getBaseDir(String lang) { + this.baseDirStrategy ? this.baseDirStrategy.getBaseDir(lang) : po.projectDir + } + + @Override + Provider getBaseDirProvider() { + this.baseDirProperty + } + + /** + * Returns the current basedir strategy if it has been configured. + * + * @return Strategy or @{code null}. + */ + @Override + BaseDirStrategy getBaseDirStrategy() { + this.baseDirStrategy + } + + /** + * Checks whether an explicit strategy has been set for base directory. + * + * @return {@code true} if a strategy has been configured. + */ + @Override + boolean isBaseDirConfigured() { + baseDirStrategy != null + } + + /** + * Sets the base directory for a conversion. + *

    + * The base directory is used by AsciidoctorJ to set a current working directory for + * a conversion. If never set, then {@code project.projectDir} will be assumed to be the base directory. + *

    + * @param f Can be a {@link BaseDirStrategy}, {@code null}, or anything convertible to a file. + */ + @Override + void setBaseDir(@Nullable Object f) { + switch (f) { + case BaseDirStrategy: + this.baseDirStrategy = (BaseDirStrategy) f + break + case null: + this.baseDirStrategy = BaseDirIsNull.INSTANCE + break + default: + this.baseDirStrategy = new BaseDirIsFixedPath(po.provider({ + po.fsOperations.file(f) + } as Callable)) + } + } +} diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/internal/DefaultAsciidoctorFileOperations.groovy b/base/src/main/groovy/org/asciidoctor/gradle/base/internal/DefaultAsciidoctorFileOperations.groovy new file mode 100644 index 000000000..0a6b2a156 --- /dev/null +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/internal/DefaultAsciidoctorFileOperations.groovy @@ -0,0 +1,740 @@ +/* + * Copyright 2013-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://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.asciidoctor.gradle.base.internal + +import groovy.transform.CompileStatic +import org.asciidoctor.gradle.base.AsciidoctorMultiLanguageException +import org.asciidoctor.gradle.base.AsciidoctorTaskFileOperations +import org.asciidoctor.gradle.base.AsciidoctorTaskTreeOperations +import org.asciidoctor.gradle.base.AsciidoctorUtils +import org.asciidoctor.gradle.base.BaseDirStrategy +import org.asciidoctor.gradle.base.OutputOptions +import org.asciidoctor.gradle.base.Transform +import org.gradle.api.Action +import org.gradle.api.GradleException +import org.gradle.api.InvalidUserDataException +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.file.CopySpec +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.FileTree +import org.gradle.api.file.FileTreeElement +import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider +import org.gradle.api.specs.Spec +import org.gradle.api.tasks.PathSensitivity +import org.gradle.api.tasks.util.PatternFilterable +import org.gradle.api.tasks.util.PatternSet +import org.ysb33r.grolifant.api.core.ProjectOperations + +import javax.annotation.Nullable +import java.nio.file.Path + +import static org.asciidoctor.gradle.base.AsciidoctorUtils.UNDERSCORE_LED_FILES +import static org.asciidoctor.gradle.base.AsciidoctorUtils.createDirectoryProperty +import static org.asciidoctor.gradle.base.AsciidoctorUtils.executeDelegatingClosure +import static org.asciidoctor.gradle.base.AsciidoctorUtils.mapToDirectoryProvider +import static org.ysb33r.grolifant.api.core.TaskInputFileOptions.IGNORE_EMPTY_DIRECTORIES +import static org.ysb33r.grolifant.api.core.TaskInputFileOptions.SKIP_WHEN_EMPTY + +/** + * Implements Asciidoctor conversion task file operations. + * + * Instances of this is meant to be used via delegation. + * + * @since 4.0 + * + * @author Schalk W. Cronjé + * @author Lari Hotari + * @author Gary Hale + */ +@CompileStatic +@SuppressWarnings('MethodCount') +class DefaultAsciidoctorFileOperations implements AsciidoctorTaskFileOperations, AsciidoctorTaskTreeOperations { + + private final Task task + private final String projectName + private final DirectoryProperty srcDir + private final DirectoryProperty outDir + private final ProjectOperations projectOperations + private final List languages = [] + private final Map languageResources = [:] + private final OutputOptions configuredOutputOptions = new OutputOptions() + private final Provider intermediateWorkDirProvider + private final String taskName + private final Property intermediateArtifactPattern + + private PatternSet sourceDocumentPattern + private PatternSet secondarySourceDocumentPattern + private CopySpec resourceCopy + private boolean withIntermediateWorkDir = false + + DefaultAsciidoctorFileOperations(Task task, String engineName) { + this.task = task + this.taskName = task.name + this.projectName = task.project.name + this.engineName = engineName + this.projectOperations = ProjectOperations.find(project) + this.intermediateArtifactPattern = task.project.objects.property(PatternSet) + + this.srcDir = createDirectoryProperty(project) + this.outDir = createDirectoryProperty(project) +// this.defaultRevNumber = projectOperations.projectTools.versionProvider.orElse(Project.DEFAULT_VERSION) + this.intermediateWorkDirProvider = projectOperations.buildDirDescendant( + "/tmp/${projectOperations.fsOperations.toSafeFileName(taskName)}.intermediate" + ) + + projectOperations.tasks.inputFiles( + task.inputs, + { projectOperations.fsOperations.resolveFilesFromCopySpec(getResourceCopySpec(Optional.empty())) }, + PathSensitivity.RELATIVE, + IGNORE_EMPTY_DIRECTORIES + ) + projectOperations.tasks.inputFiles( + task.inputs, + { sourceFileTree }, + PathSensitivity.RELATIVE, + IGNORE_EMPTY_DIRECTORIES, SKIP_WHEN_EMPTY + ) + projectOperations.tasks.inputFiles( + task.inputs, + { secondarySourceFileTree }, + PathSensitivity.RELATIVE, + IGNORE_EMPTY_DIRECTORIES + ) + } + + final String engineName + + /** Logs documents as they are converted + * + */ + boolean logDocuments = false + + /** + * The default pattern set for secondary sources. + * + * @return By default all *.adoc,*.ad,*.asc,*.asciidoc is included. + */ + @Override + PatternSet getDefaultSecondarySourceDocumentPattern() { + asciidocPatterns() + } + + /** + * The default {@link PatternSet} that will be used if {@code sources} was never called + * + * @return By default all *.adoc,*.ad,*.asc,*.asciidoc is included. + * Files beginning with underscore are excluded + */ + @Override + PatternSet getDefaultSourceDocumentPattern() { + asciidocPatterns(UNDERSCORE_LED_FILES) + } + + /** + * A provider of patterns identifying intermediate artifacts. + * + * @return Provider to a {@link PatternSet}. Can be empty. + */ + @Override + Provider getIntermediateArtifactPatternProvider() { + this.intermediateArtifactPattern + } + + /** + * Gets the CopySpec for additional resources. + * + * If {@code resources} was never called, it will return a default CopySpec otherwise it will return the + * one built up via successive calls to {@code resources} + * + * @param lang Language to to apply to or empty for no-language support. + * @return A{@link CopySpec}. Never {@code null}. + */ + @Override + CopySpec getResourceCopySpec(Optional lang) { + this.resourceCopy ?: getDefaultResourceCopySpec(lang) + } + + /** + * The default CopySpec that will be used if {@code resources} was never called + * + * By default, anything below {@code $sourceDir/images} will be included. + * + * @param lang Language to use. Can be empty (not {@code null}) when not to use a language. + * @return A{@link CopySpec}. Never {@code null}. + */ + @Override + CopySpec getDefaultResourceCopySpec(Optional lang) { + projectOperations.copySpec({ CopySpec cs -> + cs.tap { + from(lang.present ? new File(sourceDir, lang.get()) : sourceDir) { + include 'images/**' + } + } + } as Action) + } + + /** + * Returns the copy specification for the resources of a specific language. + * + * @param lang Language + * + * @return Copy specification. Can be {@code null}. + */ + @Override + CopySpec getLanguageResourceCopySpec(String lang) { + languageResources[lang] + } + + /** + * Gets the language-specific secondary source tree. + * + * @param lang Language + * @return Language-specific source tree. + */ + @Override + FileTree getLanguageSecondarySourceFileTree(String lang) { + getSecondarySourceFileTreeFrom(new File(sourceDir, lang)) + } + + /** + * Gets the language-specific source tree + * + * @param lang Language + * @return Language-specific source tree. + */ + @Override + FileTree getLanguageSourceFileTree(String lang) { + getSourceFileTreeFrom(new File(sourceDir, lang)) + } + + /** + * Obtains a secondary source tree based on patterns. + * + * @param dir Toplevel source directory. + * @return Source tree based upon configured pattern. + */ + @Override + FileTree getSecondarySourceFileTreeFrom(File dir) { + Spec primarySourceSpec = (this.sourceDocumentPattern ?: defaultSourceDocumentPattern).asSpec + projectOperations.fileTree(dir) + .matching(this.secondarySourceDocumentPattern ?: defaultSecondarySourceDocumentPattern) + .matching { PatternFilterable target -> + target.exclude(primarySourceSpec) + } + } + + /** + * Obtains a source tree based on patterns. + * + * @param dir Toplevel source directory. + * @return Source tree based upon configured pattern. + */ + @Override + FileTree getSourceFileTreeFrom(File dir) { + AsciidoctorUtils.getSourceFileTree( + projectOperations, + dir, + this.sourceDocumentPattern ?: defaultSourceDocumentPattern + ) + } + + /** Sets the new Asciidoctor parent source directory. + * + * @param f Any object convertible with {@code project.file}. + */ + @Override + void setSourceDir(Object f) { + this.srcDir.set(mapToDirectoryProvider(project, f)) + } + + /** Sets the new Asciidoctor parent source directory in a declarative style. + * + * @param f Any object convertible with {@code project.file}. + * + * @since 3.0 + */ + @Override + void sourceDir(Object f) { + this.srcDir.set(mapToDirectoryProvider(project, f)) + } + + /** + * Returns the parent directory for Asciidoctor source as a property object. + */ + @Override + DirectoryProperty getSourceDirProperty() { + this.srcDir + } + + /** Returns the current toplevel output directory + * + */ + @Override + File getOutputDir() { + this.outDir.asFile.get() + } + + /** Sets the new Asciidoctor parent output directory. + * + * @param f An object convertible via {@code project.file} + */ + @Override + void setOutputDir(Object f) { + this.outDir.set(project.file(f)) + } + + /** + * Returns the current toplevel output directory as a property object. + */ + @Override + DirectoryProperty getOutputDirProperty() { + this.outDir + } + + /** Configures sources. + * + * @param cfg Configuration closure. Is passed a {@link org.gradle.api.tasks.util.PatternSet}. + */ + @Override + void sources(final Closure cfg) { + if (sourceDocumentPattern == null) { + sourceDocumentPattern = new PatternSet().exclude(UNDERSCORE_LED_FILES) + } + Closure configuration = (Closure) cfg.clone() + configuration.delegate = sourceDocumentPattern + configuration() + } + + /** Configures sources. + * + * @param cfg Configuration {@link org.gradle.api.Action}. Is passed a {@link PatternSet}. + */ + @Override + void sources(final Action cfg) { + if (sourceDocumentPattern == null) { + sourceDocumentPattern = new PatternSet().exclude(UNDERSCORE_LED_FILES) + } + cfg.execute(sourceDocumentPattern) + } + + /** Include source patterns. + * + * @param includePatterns ANT-style patterns for sources to include + */ + @Override + void sources(String... includePatterns) { + sources(new Action() { + + @Override + void execute(PatternSet patternSet) { + patternSet.include(includePatterns) + } + }) + } + + /** Clears existing sources patterns. + */ + @Override + void clearSources() { + sourceDocumentPattern = null + } + + /** Clears any of the existing secondary soruces patterns. + * + * This should be used if none of the default patterns should be monitored. + */ + @Override + void clearSecondarySources() { + secondarySourceDocumentPattern = new PatternSet() + } + + /** Configures secondary sources. + * + * @param cfg Configuration closure. Is passed a {@link PatternSet}. + */ + @Override + void secondarySources(final Closure cfg) { + if (this.secondarySourceDocumentPattern == null) { + this.secondarySourceDocumentPattern = defaultSecondarySourceDocumentPattern + } + executeDelegatingClosure(this.secondarySourceDocumentPattern, cfg) + } + + /** Configures sources. + * + * @param cfg Configuration {@link Action}. Is passed a {@link PatternSet}. + */ + @Override + void secondarySources(final Action cfg) { + if (secondarySourceDocumentPattern == null) { + secondarySourceDocumentPattern = defaultSecondarySourceDocumentPattern + } + cfg.execute(secondarySourceDocumentPattern) + } + + /** Returns a FileTree containing all of the source documents + * + * If a filter with {@link #sources} was never set then all asciidoc source files + * below {@link #setSourceDir} will be included. If multiple languages are used all + * of the language source sets be will included. + * + * @return Applicable source trees. + * + * @since 1.5.1 + */ + @Override + FileTree getSourceFileTree() { + if (languages.empty) { + getSourceFileTreeFrom(sourceDir) + } else { + languages.sum { lang -> + getLanguageSourceFileTree(lang) + } as FileTree + } + } + + /** Returns a FileTree containing all of the secondary source documents. + * + * If a filter with {@link #secondarySources} was never set then all asciidoc source files + * below {@link #setSourceDir} will be included. If multiple languages are used all + * of the language secondary source sets be will included. + * + * @return Collection of secondary source files + * + */ + @Override + FileTree getSecondarySourceFileTree() { + if (languages.empty) { + getSecondarySourceFileTreeFrom(sourceDir) + } else { + languages.sum { lang -> + getLanguageSecondarySourceFileTree(lang) + } as FileTree + } + } + + /** Add to the CopySpec for extra files. The destination of these files will always have a parent directory + * of {@code outputDir} or {@code outputDir + backend} + * + * @param cfg {@link org.gradle.api.file.CopySpec} runConfiguration closure + * @since 1.5.1 + */ + @Override + void resources(Closure cfg) { + if (this.resourceCopy == null) { + this.resourceCopy = project.copySpec(cfg) + } else { + Closure configuration = (Closure) cfg.clone() + configuration.delegate = this.resourceCopy + configuration() + } + } + + /** Add to the CopySpec for extra files. The destination of these files will always have a parent directory + * of {@code outputDir} or {@code outputDir + backend} + * + * @param cfg {@link org.gradle.api.file.CopySpec} runConfiguration {@link Action} + */ + @Override + void resources(Action cfg) { + if (this.resourceCopy == null) { + this.resourceCopy = project.copySpec(cfg) + } else { + cfg.execute(this.resourceCopy) + } + } + + /** Add to the CopySpec for extra files. The destination of these files will always have a parent directory + * of {@code outputDir} or {@code outputDir + backend} + * + * If not languages are set. these resources will be ignored. + * + * @param cfg {@link CopySpec} runConfiguration closure + * @param lang Language to which these resources will be applied to. + * @since 3.0.0 + */ + @Override + void resources(final String lang, Closure cfg) { + if (this.languageResources[lang] == null) { + this.languageResources[lang] = project.copySpec(cfg) + } else { + Closure configuration = (Closure) cfg.clone() + configuration.delegate = this.languageResources[lang] + configuration() + } + } + + /** Add to the CopySpec for extra files. The destination of these files will always have a parent directory + * of {@code outputDir} or {@code outputDir + backend} + * + * If not languages are set. these resources will be ignored. + * + * @param cfg {@link CopySpec} runConfiguration {@link Action} + * @param lang Language to which these resources will be applied to. + * @since 3.0.0 + */ + @Override + void resources(final String lang, Action cfg) { + if (this.languageResources[lang] == null) { + this.languageResources[lang] = project.copySpec(cfg) + } else { + cfg.execute(this.languageResources[lang]) + } + } + + /** Some extensions such as {@code ditaa} creates images in the source directory. + * + * Use this setting to copy all sources and resources to an intermediate work directory + * before processing starts. This will keep the source directory pristine + */ + @Override + void useIntermediateWorkDir() { + withIntermediateWorkDir = true + } + + /** + * Checks whether an intermediate workdir is required. + * + * @return {@code true} is there is an intermediate working directory. + */ + @Override + boolean hasIntermediateWorkDir() { + this.withIntermediateWorkDir + } + + /** The document conversion might generate additional artifacts that could + * require copying to the final destination. + * + * An example is use of {@code ditaa} diagram blocks. These artifacts can be specified + * in this block. Use of the option implies {@link #useIntermediateWorkDir}. + * + * @param cfg Configures a {@link PatternSet} with a base directory of the intermediate working + * directory. + */ + @Override + void withIntermediateArtifacts(@DelegatesTo(PatternSet) Closure cfg) { + useIntermediateWorkDir() + if (!this.intermediateArtifactPattern.present) { + this.intermediateArtifactPattern.set(new PatternSet()) + } + executeDelegatingClosure(this.intermediateArtifactPattern.get(), cfg) + } + + /** + * Additional artifacts created by Asciidoctor that might require copying. + * + * @param cfg Action that configures a {@link PatternSet}. + * + * @see {@link #withIntermediateArtifacts(Closure cfg)} + */ + @Override + void withIntermediateArtifacts(final Action cfg) { + useIntermediateWorkDir() + if (!this.intermediateArtifactPattern.present) { + this.intermediateArtifactPattern.set(new PatternSet()) + } + cfg.execute(this.intermediateArtifactPattern.get()) + } + + /** + * The directory that will be the intermediate directory should one be required. + * + * @return Intermediate working directory + */ + @Override + Provider getIntermediateWorkDirProvider() { + this.intermediateWorkDirProvider + } + + /** + * Returns a list of all output directories by backend + * + * @since 1.5.1 + */ + @Override + Set getBackendOutputDirectories() { + if (languages.empty) { + Transform.toSet(configuredOutputOptions.backends) { + String it -> getOutputDirFor(it) + } + } else { + configuredOutputOptions.backends.collectMany { String backend -> + Transform.toList(languages) { String lang -> + getOutputDirFor(backend, lang) + } + }.toSet() + } + } + + /** Obtain List of languages the sources documents are written in. + * + * @return List of languages. Can be empty, but never {@code null}. + * + * @since 3.0.0 + */ + @Override + List getLanguages() { + this.languages + } + + /** Reset current list of languages and replace with a new set. + * + * @param langs List of new languages + * + * @since 3.0.0 + */ + @Override + void setLanguages(Iterable langs) { + this.languages.clear() + this.languages.addAll(langs) + } + + /** Add to list of languages to process. + * + * @param langs List of additional languages + * + * @since 3.0.0 + */ + @Override + void languages(Iterable langs) { + this.languages.addAll(langs) + } + + /** Add to list of languages to process. + * + * @param langs List of additional languages + * + * @since 3.0.0 + */ + @Override + void languages(String... langs) { + this.languages.addAll(langs) + } + + /** + * Validates that the path roots are sane. + * + * @param baseDir Base directory strategy. Can be {@code null} + */ + @Override + void checkForIncompatiblePathRoots(@Nullable BaseDirStrategy baseDir) { + if (outputDir == null) { + throw new InvalidUserDataException("outputDir has not been defined for task '${taskName}'") + } + + Path baseRoot = languages.empty ? + baseDir?.baseDir?.toPath()?.root : + baseDir?.getBaseDir(languages[0])?.toPath()?.root + if (baseRoot != null) { + Path sourceRoot = sourceDir.toPath().root + Path outputRoot = outputDir.toPath().root + + if (sourceRoot != baseRoot || outputRoot != baseRoot) { + throw new InvalidUserDataException( + "sourceDir, outputDir and baseDir needs to have the same root filesystem for ${engineName} " + + 'to function correctly. ' + + 'This is typically caused on Windows where everything is not on the same drive letter.' + ) + } + } + } + + /** + * Validates the source tree + */ + @Override + void checkForInvalidSourceDocuments() { + if (!sourceFileTree.filter { File f -> + f.name.startsWith('_') + }.empty) { + throw new InvalidUserDataException('Source documents may not start with an underscore') + } + } + + /** + * A task may add some default attributes. + * If the user specifies any of these attributes, then those attributes will not be utilised. + * The default implementation will add {@code includedir}, {@code revnumber}, {@code gradle-project-group}, + * {@code gradle-project-name} + * + * @param workingSourceDir Directory where source files are located. + * + * @return A collection of default attributes. + */ + @Override + Map getTaskSpecificDefaultAttributes(File workingSourceDir) { + Provider group = projectOperations.projectTools.groupProvider.orElse('') + Provider revnumber = projectOperations.projectTools.versionProvider.orElse(Project.DEFAULT_VERSION) + [ + includedir : workingSourceDir.absolutePath, + 'gradle-project-name' : projectName, + 'gradle-project-group' : group, + 'gradle-project-version': revnumber, + revnumber : revnumber + ] + } + + /** Get the output directory for a specific backend. + * + * @param backendName Name of backend + * @return Output directory. + */ + protected File getOutputDirFor(final String backendName) { + if (outputDir == null) { + throw new GradleException("outputDir has not been defined for task '${taskName}'") + } + if (!this.languages.empty) { + throw new AsciidoctorMultiLanguageException('Use getOutputDir(backendname,language) instead.') + } + configuredOutputOptions.separateOutputDirs ? new File(outputDir, backendName) : outputDir + } + + /** Get the output directory for a specific backend. + * + * @param backendName Name of backend + * @param language Language for which sources are being generated. + * @return Output directory. + * + * @since 3.0.0 + */ + protected File getOutputDirFor(final String backendName, final String language) { + if (outputDir == null) { + throw new GradleException("outputDir has not been defined for task '${taskName}'") + } + configuredOutputOptions.separateOutputDirs ? + new File(outputDir, "${language}/${backendName}") : + new File(outputDir, language) + } + + private Project getProject() { + task.project + } + + private PatternSet asciidocPatterns(String... excluding) { + PatternSet ps = new PatternSet() + ps.include '**/*.adoc' + ps.include '**/*.ad' + ps.include '**/*.asc' + ps.include '**/*.asciidoc' + + if (excluding.size()) { + ps.exclude(excluding) + } + ps + } +} diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/internal/DefaultAsciidoctorOutputOptions.groovy b/base/src/main/groovy/org/asciidoctor/gradle/base/internal/DefaultAsciidoctorOutputOptions.groovy new file mode 100644 index 000000000..fa8f4189b --- /dev/null +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/internal/DefaultAsciidoctorOutputOptions.groovy @@ -0,0 +1,242 @@ +/* + * Copyright 2013-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://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.asciidoctor.gradle.base.internal + +import groovy.transform.CompileStatic +import groovy.util.logging.Slf4j +import org.asciidoctor.gradle.base.AsciidoctorMultiLanguageException +import org.asciidoctor.gradle.base.AsciidoctorTaskFileOperations +import org.asciidoctor.gradle.base.AsciidoctorTaskOutputOptions +import org.asciidoctor.gradle.base.OutputOptions +import org.asciidoctor.gradle.base.Transform +import org.gradle.api.Action +import org.gradle.api.GradleException +import org.gradle.api.file.CopySpec +import org.gradle.api.file.FileTree +import org.ysb33r.grolifant.api.core.ProjectOperations + +import static groovy.lang.Closure.DELEGATE_FIRST + +/** + * Implementation of output options for Asciidoctor tasks + * + * @author Schalk W. Cronjé + * + * @since 4.0 + */ +@CompileStatic +@Slf4j +class DefaultAsciidoctorOutputOptions implements AsciidoctorTaskOutputOptions { + + private final AsciidoctorTaskFileOperations fileOperations + private final OutputOptions configuredOutputOptions = new OutputOptions() + private final ProjectOperations po + private final String taskName + private List copyResourcesForBackendsList = [] + + DefaultAsciidoctorOutputOptions( + ProjectOperations po, + String taskName, + AsciidoctorTaskFileOperations atfo + ) { + this.po = po + this.fileOperations = atfo + this.taskName = taskName + } + + /** + * The current set of configured Asciidoctor backends. + * + * @return Backends. Can be empty. Never null. + */ + @Override + Set backends() { + configuredOutputOptions.backends + } + + /** + * Configures output options for this task. + * + * @param cfg Closure which will delegate to a {@link org.asciidoctor.gradle.base.OutputOptions} instance. + */ + void configureOutputOptions(@DelegatesTo(OutputOptions) Closure cfg) { + Closure configurator = (Closure) cfg.clone() + configurator.delegate = this.configuredOutputOptions + configurator.resolveStrategy = DELEGATE_FIRST + configurator.call() + } + + /** Configures output options for this task. + * + * @param cfg Action which will be passed an instances of {@link org.asciidoctor.gradle.base.OutputOptions} + * to configure. + */ + void configureOutputOptions(Action cfg) { + cfg.execute(this.configuredOutputOptions) + } + + /** + * Copies all resources to the output directory. + * + * Some backends (such as {@code html5}) require all resources to be copied to the output directory. + * This is the default behaviour for this task. + */ + @Override + void copyAllResources() { + this.copyResourcesForBackendsList = [] + } + + /** + * Do not copy any resources to the output directory. + * + * Some backends (such as {@code pdf}) process all resources in place. + */ + @Override + void copyNoResources() { + this.copyResourcesForBackendsList = null + } + + /** + * Copy resources for a backend. + * + * @param backendName Name of backend for which resources are copied + * @param sourceDir Source directory of resources + * @param outputDir Final output directory. + * @param includeLang If set also copy resources for this specified language + */ + @Override + void copyResourcesByBackend(String backendName, File sourceDir, File outputDir, Optional includeLang) { + if (copyResourcesForBackendsList != null && + (copyResourcesForBackendsList.empty || backendName in copyResourcesForBackendsList) + ) { + CopySpec rcs = fileOperations.getResourceCopySpec(includeLang) + log.info "Copy resources for '${backendName}' to ${outputDir}" + + FileTree ps = fileOperations.intermediateArtifactPatternProvider.present ? + po.fileTree(sourceDir).matching(fileOperations.intermediateArtifactPatternProvider.get()) : + null + + CopySpec langSpec = includeLang.present ? + fileOperations.getLanguageResourceCopySpec(includeLang.get()) : + null + + po.copy(new Action() { + @Override + void execute(CopySpec copySpec) { + copySpec.with { + into outputDir + with rcs + + if (ps != null) { + from ps + } + + if (langSpec) { + with langSpec + } + } + } + }) + } + } + + /** + * Copy resources to the output directory only if the backend names matches any of the specified + * names. + * + * @param backendNames List of names for which resources should be copied. + */ + @Override + void copyResourcesOnlyIf(String... backendNames) { + this.copyResourcesForBackendsList = [] + this.copyResourcesForBackendsList.addAll(backendNames) + } + + /** + * Returns a list of all output directories by backend + * + * @return Output directories + */ + @Override + Set getBackendOutputDirectories() { + if (fileOperations.languages.empty) { + Transform.toSet(configuredOutputOptions.backends) { + String it -> getOutputDirForBackend(it) + } + } else { + configuredOutputOptions.backends.collectMany { String backend -> + Transform.toList(fileOperations.languages) { String lang -> + getOutputDirForBackend(backend, lang) + } + }.toSet() + } + } + + /** + * List of backends for which to copy resources. + * + * @return List of backends. Can be {@code null}. + */ + @Override + Optional> getCopyResourcesForBackends() { + Optional.ofNullable(this.copyResourcesForBackendsList) as Optional> + } + + /** + * Get the output directory for a specific backend. + * + * @param backendName Name of backend + * @return Output directory. + */ + @Override + File getOutputDirForBackend(String backendName) { + if (!fileOperations.outputDirProperty.present) { + throw new GradleException("outputDir has not been defined for task '${taskName}'") + } + if (!fileOperations.languages.empty) { + throw new AsciidoctorMultiLanguageException('Use getOutputDir(backendname,language) instead.') + } + configuredOutputOptions.separateOutputDirs ? new File(fileOperations.outputDir, backendName) : + fileOperations.outputDir + } + + /** + * Get the output directory for a specific backend. + * + * @param backendName Name of backend + * @param language Language for which sources are being generated. + * @return Output directory. + */ + @Override + File getOutputDirForBackend(String backendName, String language) { + if (!fileOperations.outputDirProperty.present) { + throw new GradleException("outputDir has not been defined for task '${taskName}'") + } + configuredOutputOptions.separateOutputDirs ? + new File(fileOperations.outputDir, "${language}/${backendName}") : + new File(fileOperations.outputDir, language) + } + + /** + * Access to the underlying {@link OutputOptions}. + * + * @return {@link OutputOptions} instance. + */ + @Override + OutputOptions getOutputOptions() { + this.configuredOutputOptions + } +} diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/internal/DefaultAsciidoctorWorkspacePreparation.groovy b/base/src/main/groovy/org/asciidoctor/gradle/base/internal/DefaultAsciidoctorWorkspacePreparation.groovy new file mode 100644 index 000000000..8012ff3a5 --- /dev/null +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/internal/DefaultAsciidoctorWorkspacePreparation.groovy @@ -0,0 +1,144 @@ +/* + * Copyright 2013-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://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.asciidoctor.gradle.base.internal + +import groovy.transform.CompileStatic +import org.asciidoctor.gradle.base.AsciidoctorMultiLanguageException +import org.asciidoctor.gradle.base.AsciidoctorTaskFileOperations +import org.asciidoctor.gradle.base.AsciidoctorTaskTreeOperations +import org.asciidoctor.gradle.base.AsciidoctorTaskWorkspacePreparation +import org.gradle.api.file.CopySpec +import org.gradle.api.file.FileTree +import org.ysb33r.grolifant.api.core.ProjectOperations + +/** + * Default implementation of an Asciidoctor workspace preparation. + * + * @author Schalk W> Cronje + * + * @since 4.0 + */ +@CompileStatic +class DefaultAsciidoctorWorkspacePreparation implements AsciidoctorTaskWorkspacePreparation { + private final AsciidoctorTaskFileOperations fileOperations + private final AsciidoctorTaskTreeOperations treeOperations + private final ProjectOperations po + + DefaultAsciidoctorWorkspacePreparation( + ProjectOperations po, + AsciidoctorTaskFileOperations atfo, + AsciidoctorTaskTreeOperations atto + ) { + this.po = po + this.fileOperations = atfo + this.treeOperations = atto + } + /** + * Prepares a workspace prior to conversion. + * + * @return A presentation of the working source directory and the source tree. + */ + @Override + Workspace prepareWorkspace() { + if (!fileOperations.languages.empty) { + throw new AsciidoctorMultiLanguageException('Use prepareWorkspace(lang) instead.') + } + if (fileOperations.hasIntermediateWorkDir()) { + File tmpDir = fileOperations.intermediateWorkDir + prepareTempWorkspace(tmpDir) + Workspace.builder() + .workingSourceDir(tmpDir) + .sourceTree(treeOperations.getSourceFileTreeFrom(tmpDir)) + .build() + } else { + Workspace.builder() + .workingSourceDir(fileOperations.sourceDir) + .sourceTree(fileOperations.sourceFileTree).build() + } + } + + /** + * Prepares a workspace for a specific language prior to conversion. + * + * @param language Language to prepare workspace for. + * @return A presentation of the working source directory and the source tree. + */ + @Override + Workspace prepareWorkspace(String language) { + if (fileOperations.hasIntermediateWorkDir()) { + File tmpDir = new File(fileOperations.intermediateWorkDir, language) + prepareTempWorkspace(tmpDir, language) + Workspace.builder() + .workingSourceDir(tmpDir) + .sourceTree(treeOperations.getSourceFileTreeFrom(tmpDir)) + .build() + } else { + File srcDir = new File(fileOperations.sourceDir, language) + Workspace.builder() + .workingSourceDir(srcDir) + .sourceTree(treeOperations.getSourceFileTreeFrom(srcDir)) + .build() + } + } + + private void prepareTempWorkspace(final File tmpDir) { + if (!fileOperations.languages.empty) { + throw new AsciidoctorMultiLanguageException('Use prepareTempWorkspace(tmpDir,lang) instead') + } + prepareTempWorkspace( + tmpDir, + fileOperations.sourceFileTree, + fileOperations.secondarySourceFileTree, + fileOperations.getResourceCopySpec(Optional.empty()), + Optional.empty() + ) + } + + private void prepareTempWorkspace(final File tmpDir, final String lang) { + prepareTempWorkspace( + tmpDir, + treeOperations.getLanguageSourceFileTree(lang), + treeOperations.getLanguageSecondarySourceFileTree(lang), + fileOperations.getResourceCopySpec(Optional.of(lang)), + Optional.ofNullable(fileOperations.getLanguageResourceCopySpec(lang)) + ) + } + + private void prepareTempWorkspace( + final File tmpDir, + final FileTree mainSourceTree, + final FileTree secondarySourceTree, + final CopySpec resourceTree, + final Optional langResourcesTree + ) { + if (tmpDir.exists()) { + tmpDir.deleteDir() + } + tmpDir.mkdirs() + po.copy { CopySpec cs -> + cs.with { + into tmpDir + from mainSourceTree + from secondarySourceTree + with resourceTree + if (langResourcesTree.present) { + with langResourcesTree.get() + } + } + } + } + +} diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/internal/DeprecatedFeatures.groovy b/base/src/main/groovy/org/asciidoctor/gradle/base/internal/DeprecatedFeatures.groovy index 9987eb856..c6d207a70 100644 --- a/base/src/main/groovy/org/asciidoctor/gradle/base/internal/DeprecatedFeatures.groovy +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/internal/DeprecatedFeatures.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,17 +15,16 @@ */ package org.asciidoctor.gradle.base.internal -import groovy.transform.CompileDynamic import groovy.transform.CompileStatic import org.gradle.api.Named import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.api.logging.LogLevel import org.gradle.api.plugins.ExtraPropertiesExtension import org.gradle.util.GradleVersion -/** A simplified way of grouping deprecation messages. +/** + * A simplified way of grouping deprecation messages. * * @author Schalk W. Cronjé * @@ -43,7 +42,7 @@ class DeprecatedFeatures implements Plugin { static void addDeprecationMessage(Project project, String identifier, String message) { try { NamedDomainObjectContainer msgContainer = - (NamedDomainObjectContainer) project.extensions.extraProperties.get(EXTENSION_NAME) + (NamedDomainObjectContainer) project.extensions.extraProperties.get(EXTENSION_NAME) Messages msgs = msgContainer.findByName(identifier) if (msgs) { @@ -70,27 +69,15 @@ class DeprecatedFeatures implements Plugin { break default: project.logger.lifecycle( - "${BASE_MESSAGE} To help with migration run with ${COMMAND_LINE}." + "${BASE_MESSAGE} To help with migration run with ${COMMAND_LINE}." ) } } } } - @CompileDynamic private static String getWarningMode(Project project) { - if (GRADLE_4_5_OR_LATER) { - project.gradle.startParameter.warningMode.toString().toLowerCase() - } else { - switch (project.gradle.startParameter.logLevel) { - case LogLevel.QUIET: - return 'none' - case LogLevel.INFO: - return 'all' - default: - '' - } - } + project.gradle.startParameter.warningMode.toString().toLowerCase() } private static String createOutputMessage(NamedDomainObjectContainer msgContainer) { diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/internal/Workspace.groovy b/base/src/main/groovy/org/asciidoctor/gradle/base/internal/Workspace.groovy index 2122783e8..1f5e49068 100644 --- a/base/src/main/groovy/org/asciidoctor/gradle/base/internal/Workspace.groovy +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/internal/Workspace.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/internal/slides/ProfileUtils.groovy b/base/src/main/groovy/org/asciidoctor/gradle/base/internal/slides/ProfileUtils.groovy index 12fd8629b..9b4e0b1a7 100644 --- a/base/src/main/groovy/org/asciidoctor/gradle/base/internal/slides/ProfileUtils.groovy +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/internal/slides/ProfileUtils.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/log/Severity.groovy b/base/src/main/groovy/org/asciidoctor/gradle/base/log/Severity.groovy index c9fc353e8..261d24e01 100644 --- a/base/src/main/groovy/org/asciidoctor/gradle/base/log/Severity.groovy +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/log/Severity.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/base/src/main/groovy/org/asciidoctor/gradle/base/process/ProcessMode.groovy b/base/src/main/groovy/org/asciidoctor/gradle/base/process/ProcessMode.groovy index 510b95e0a..3454427a2 100644 --- a/base/src/main/groovy/org/asciidoctor/gradle/base/process/ProcessMode.groovy +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/process/ProcessMode.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,26 +16,40 @@ package org.asciidoctor.gradle.base.process import groovy.transform.CompileStatic +import org.ysb33r.grolifant.api.core.jvm.ExecutionMode /** Ways of executing Java processes. * * @since 3.0 (Relocated from {@code org.asciidoctor.gradle.jvm.ProcessMode} which existed since 2.0.0) * @author Schalk W. Cronjé + * + * @deprecated Since 4.0. Use {@link ExecutionMode} instead */ @CompileStatic +@Deprecated enum ProcessMode { /** Use Gradle worker in-process. * */ - IN_PROCESS, + IN_PROCESS(ExecutionMode.CLASSPATH), /** Use out-of-process Gradle worker. * */ - OUT_OF_PROCESS, + OUT_OF_PROCESS(ExecutionMode.OUT_OF_PROCESS), /** Use a classic out-of-process Java execution. * */ - JAVA_EXEC + JAVA_EXEC(ExecutionMode.JAVA_EXEC) + + final ExecutionMode executionMode + + static ProcessMode fromExecutionMode(ExecutionMode mode) { + ProcessMode.values().find { it.executionMode == mode } ?: JAVA_EXEC + } + + private ProcessMode(ExecutionMode em) { + this.executionMode = em + } } \ No newline at end of file diff --git a/base/src/main/java/org/asciidoctor/gradle/base/slides/Profile.java b/base/src/main/groovy/org/asciidoctor/gradle/base/slides/Profile.java similarity index 97% rename from base/src/main/java/org/asciidoctor/gradle/base/slides/Profile.java rename to base/src/main/groovy/org/asciidoctor/gradle/base/slides/Profile.java index 1e2034483..76bc24845 100644 --- a/base/src/main/java/org/asciidoctor/gradle/base/slides/Profile.java +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/slides/Profile.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/base/src/main/java/org/asciidoctor/gradle/base/slides/SlidesToExportAware.java b/base/src/main/groovy/org/asciidoctor/gradle/base/slides/SlidesToExportAware.java similarity index 94% rename from base/src/main/java/org/asciidoctor/gradle/base/slides/SlidesToExportAware.java rename to base/src/main/groovy/org/asciidoctor/gradle/base/slides/SlidesToExportAware.java index 2c8641d23..e3c6a4abb 100644 --- a/base/src/main/java/org/asciidoctor/gradle/base/slides/SlidesToExportAware.java +++ b/base/src/main/groovy/org/asciidoctor/gradle/base/slides/SlidesToExportAware.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/base/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.base.properties b/base/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.base.properties deleted file mode 100644 index 30b74fcba..000000000 --- a/base/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.base.properties +++ /dev/null @@ -1,17 +0,0 @@ -# -# Copyright 2013-2023 the original author or authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://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. -# - -implementation-class=org.asciidoctor.gradle.base.AsciidoctorBasePlugin \ No newline at end of file diff --git a/base/src/test/groovy/org/asciidoctor/gradle/base/AbstractImplementationEngineExtensionSpec.groovy b/base/src/test/groovy/org/asciidoctor/gradle/base/AbstractImplementationEngineExtensionSpec.groovy index e7a40dd14..0d3c9c78c 100644 --- a/base/src/test/groovy/org/asciidoctor/gradle/base/AbstractImplementationEngineExtensionSpec.groovy +++ b/base/src/test/groovy/org/asciidoctor/gradle/base/AbstractImplementationEngineExtensionSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ package org.asciidoctor.gradle.base import org.gradle.api.Project import org.gradle.api.Task import org.gradle.testfixtures.ProjectBuilder +import org.ysb33r.grolifant.api.core.ProjectOperations import spock.lang.Specification class AbstractImplementationEngineExtensionSpec extends Specification { @@ -31,6 +32,7 @@ class AbstractImplementationEngineExtensionSpec extends Specification { Task proxyTask void setup() { + ProjectOperations.maybeCreateExtension(project) projectExtension = project.extensions.create(EXTNAME, TestExtension, project) proxyTask = project.tasks.create('proxyTask') taskExtension = proxyTask.extensions.create(EXTNAME, TestExtension, proxyTask, EXTNAME) diff --git a/base/src/test/groovy/org/asciidoctor/gradle/base/AsciidoctorBasePluginSpec.groovy b/base/src/test/groovy/org/asciidoctor/gradle/base/AsciidoctorBasePluginSpec.groovy index f4b1d587e..0abbea880 100644 --- a/base/src/test/groovy/org/asciidoctor/gradle/base/AsciidoctorBasePluginSpec.groovy +++ b/base/src/test/groovy/org/asciidoctor/gradle/base/AsciidoctorBasePluginSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,9 @@ package org.asciidoctor.gradle.base import org.gradle.api.Project import org.gradle.api.artifacts.Configuration +import org.gradle.api.file.CopySpec +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.util.PatternSet import org.gradle.testfixtures.ProjectBuilder import spock.lang.Specification @@ -37,6 +40,7 @@ class AsciidoctorBasePluginSpec extends Specification { noExceptionThrown() } + @SuppressWarnings(['GetterMethodCouldBeProperty']) static class TestTask extends AbstractAsciidoctorBaseTask { Map attributes List attributeProviders @@ -47,7 +51,27 @@ class AsciidoctorBasePluginSpec extends Specification { } @Override - protected String getEngineName() { + String getEngineName() { + null + } + + @Override + Provider getIntermediateArtifactPatternProvider() { + null + } + + @Override + CopySpec getLanguageResourceCopySpec(String lang) { + null + } + + @Override + boolean hasIntermediateWorkDir() { + false + } + + @Override + Provider getIntermediateWorkDirProvider() { null } } diff --git a/base/src/test/groovy/org/asciidoctor/gradle/base/AsciidoctorUtilsSpec.groovy b/base/src/test/groovy/org/asciidoctor/gradle/base/AsciidoctorUtilsSpec.groovy index 9dfdda67b..161de52a6 100755 --- a/base/src/test/groovy/org/asciidoctor/gradle/base/AsciidoctorUtilsSpec.groovy +++ b/base/src/test/groovy/org/asciidoctor/gradle/base/AsciidoctorUtilsSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/base/src/test/groovy/org/asciidoctor/gradle/base/BaseTaskPatternSpec.groovy b/base/src/test/groovy/org/asciidoctor/gradle/base/BaseTaskPatternSpec.groovy index d14e8320a..000860ed7 100644 --- a/base/src/test/groovy/org/asciidoctor/gradle/base/BaseTaskPatternSpec.groovy +++ b/base/src/test/groovy/org/asciidoctor/gradle/base/BaseTaskPatternSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,8 @@ package org.asciidoctor.gradle.base import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.artifacts.Configuration +import org.gradle.api.file.CopySpec +import org.gradle.api.provider.Provider import org.gradle.api.tasks.util.PatternSet import org.gradle.testfixtures.ProjectBuilder import org.ysb33r.grolifant.api.core.ProjectOperations @@ -33,6 +35,43 @@ class BaseTaskPatternSpec extends Specification { ProjectOperations.maybeCreateExtension(project) } + void "Should include patterns passed to sources method"() { + when: + def task1 = createTask('task') { + sources('myfile.adoc', 'otherfile.adoc') + } + then: + task1.internalSourceDocumentPattern.includes == ['myfile.adoc', 'otherfile.adoc'] as Set + } + + void "Should support clearing configured patterns"() { + given: + def task = createTask('task') { + sources('myfile.adoc', 'otherfile.adoc') + } + when: + task.clearSources() + then: + task.internalSourceDocumentPattern == null + } + + void "Should support replacing the configured patterns"() { + given: + def task = createTask('task') { + sources('myfile.adoc', 'otherfile.adoc') + } + when: + task.clearSources() + task.sources('myfile2.adoc', 'myfile3.adoc') + then: + task.internalSourceDocumentPattern.includes == ['myfile2.adoc', 'myfile3.adoc'] as Set + } + + private Task createTask(String name, Closure cfg) { + project.tasks.create(name: name, type: PatternSpecAsciidoctorTask).configure cfg + } + + @SuppressWarnings(['GetterMethodCouldBeProperty']) static class PatternSpecAsciidoctorTask extends AbstractAsciidoctorBaseTask { // method for accessing internal field "sourceDocumentPattern" PatternSet getInternalSourceDocumentPattern() { @@ -61,44 +100,29 @@ class BaseTaskPatternSpec extends Specification { } @Override - protected String getEngineName() { + String getEngineName() { null } - } - void "Should include patterns passed to sources method"() { - when: - def task1 = createTask('task') { - sources('myfile.adoc', 'otherfile.adoc') + @Override + Provider getIntermediateArtifactPatternProvider() { + null } - then: - task1.internalSourceDocumentPattern.includes == ['myfile.adoc', 'otherfile.adoc'] as Set - } - void "Should support clearing configured patterns"() { - given: - def task = createTask('task') { - sources('myfile.adoc', 'otherfile.adoc') + @Override + CopySpec getLanguageResourceCopySpec(String lang) { + null } - when: - task.clearSources() - then: - task.internalSourceDocumentPattern == null - } - void "Should support replacing the configured patterns"() { - given: - def task = createTask('task') { - sources('myfile.adoc', 'otherfile.adoc') + @Override + boolean hasIntermediateWorkDir() { + false } - when: - task.clearSources() - task.sources('myfile2.adoc', 'myfile3.adoc') - then: - task.internalSourceDocumentPattern.includes == ['myfile2.adoc', 'myfile3.adoc'] as Set - } - private Task createTask(String name, Closure cfg) { - project.tasks.create(name: name, type: PatternSpecAsciidoctorTask).configure cfg + @Override + Provider getIntermediateWorkDirProvider() { + null + } } + } diff --git a/base/src/test/groovy/org/asciidoctor/gradle/base/SafeModeSpec.groovy b/base/src/test/groovy/org/asciidoctor/gradle/base/SafeModeSpec.groovy index f4541cea5..997b5fd66 100644 --- a/base/src/test/groovy/org/asciidoctor/gradle/base/SafeModeSpec.groovy +++ b/base/src/test/groovy/org/asciidoctor/gradle/base/SafeModeSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/base/src/test/groovy/org/asciidoctor/gradle/base/process/ProcessModeSpec.groovy b/base/src/test/groovy/org/asciidoctor/gradle/base/process/ProcessModeSpec.groovy index 235727a31..915312fdb 100644 --- a/base/src/test/groovy/org/asciidoctor/gradle/base/process/ProcessModeSpec.groovy +++ b/base/src/test/groovy/org/asciidoctor/gradle/base/process/ProcessModeSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/base/src/test/groovy/org/asciidoctor/gradle/base/slides/ProfileSpec.groovy b/base/src/test/groovy/org/asciidoctor/gradle/base/slides/ProfileSpec.groovy index 4b42f99b2..889db21e9 100644 --- a/base/src/test/groovy/org/asciidoctor/gradle/base/slides/ProfileSpec.groovy +++ b/base/src/test/groovy/org/asciidoctor/gradle/base/slides/ProfileSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/base/src/test/resources/META-INF/asciidoctor.gradle/org.asciidoctor.gradle.base.test.properties b/base/src/test/resources/META-INF/asciidoctor.gradle/org.asciidoctor.gradle.base.test.properties index 6d838ea70..0011c6ca0 100644 --- a/base/src/test/resources/META-INF/asciidoctor.gradle/org.asciidoctor.gradle.base.test.properties +++ b/base/src/test/resources/META-INF/asciidoctor.gradle/org.asciidoctor.gradle.base.test.properties @@ -1,5 +1,5 @@ # -# Copyright 2013-2023 the original author or authors. +# Copyright 2013-2024 the original author or authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/build.gradle b/build.gradle index 191ae98fa..78631b639 100644 --- a/build.gradle +++ b/build.gradle @@ -15,35 +15,21 @@ */ plugins { -// id 'com.gradle.build-scan' version '3.11.1' id 'net.nemerosa.versioning' version '2.6.1' apply false id 'com.github.ben-manes.versions' version '0.17.0' apply false - id 'com.gradle.plugin-publish' version '0.11.0' apply false - id 'com.github.hierynomus.license' version '0.14.0' apply false + id 'com.github.hierynomus.license' version '0.16.1' apply false id 'com.github.kt3k.coveralls' version '2.8.2' apply false id 'net.ossindex.audit' version '0.1.1' apply false id 'org.kordamp.jdeps' version '0.2.0' apply false id 'fi.linuxbox.download.worker' version '0.3' apply false - id 'org.ysb33r.ivypot' version '0.13.3' apply false + id 'org.ysb33r.ivypot' version '1.0.0' apply false id 'org.ysb33r.os' version '0.9' apply false id 'org.ysb33r.cloudci' version '2.5' apply false id 'org.ysb33r.cloudci.appveyor.testreporter' version '2.5' apply false - id 'org.ysb33r.gradletest' version '2.0' apply false + id 'org.ysb33r.gradletest' version '3.0.0-alpha.3' apply false id 'idea' } -//buildScan { -// termsOfServiceUrl = 'https://gradle.com/terms-of-service' -// termsOfServiceAgree = 'yes' -// -// buildFinished { buildResult -> -// buildScanPublished { scan -> -// ['curl', '-s', '-d', "message=asciidoctor-gradle-plugin build scan: ${scan.buildScanUri}", 'https://webhooks.gitter.im/e/6ba348eef26407adc22a'].execute() -// } -// } -//} - - import org.gradle.util.GradleVersion import java.text.SimpleDateFormat @@ -84,12 +70,10 @@ subprojects { if (project.name != 'testfixtures-offline-repo') { apply plugin: 'groovy' apply plugin: 'idea' - apply plugin: 'org.ysb33r.cloudci.appveyor.testreporter' if (!project.name.startsWith('testfixture')) { apply plugin: AsciidoctorGradlePluginProject - apply plugin: 'maven-publish' apply plugin: 'com.github.ben-manes.versions' apply plugin: 'net.ossindex.audit' apply plugin: 'org.kordamp.jdeps' @@ -106,23 +90,22 @@ subprojects { } } - apply from: "${rootProject.projectDir}/gradle/standard-repositories.gradle" - apply from: "${rootProject.projectDir}/gradle/code-quality.gradle" + apply from: "${rootDir}/gradle/standard-repositories.gradle" + apply from: "${rootDir}/gradle/code-quality.gradle" if (!project.name.startsWith('testfixture')) { if (!(project.name in withoutIntegrationTests)) { - apply from: "${rootProject.projectDir}/gradle/integration-tests.gradle" + apply from: "${rootDir}/gradle/integration-tests.gradle" } - apply from: "${rootProject.projectDir}/gradle/publishing.gradle" - apply from: "${rootProject.projectDir}/gradle/gradle-plugin-dependencies.gradle" - apply from: "${rootProject.projectDir}/gradle/gradle-plugin-documentation.gradle" - apply from: "${rootProject.projectDir}/gradle/ci.gradle" + apply from: "${rootDir}/gradle/publishing.gradle" + apply from: "${rootDir}/gradle/gradle-plugin-documentation.gradle" + apply from: "${rootDir}/gradle/ci.gradle" if (project.name.startsWith('asciidoctor-gradle-jvm')) { - apply from: "${rootProject.projectDir}/gradle/asciidoctorj-versions.gradle" - apply from: "${rootProject.projectDir}/gradle/jruby-versions.gradle" + apply from: "${rootDir}/gradle/asciidoctorj-versions.gradle" + apply from: "${rootDir}/gradle/jruby-versions.gradle" } dependencyUpdates.resolutionStrategy = { @@ -138,12 +121,27 @@ subprojects { } } + configurations.all { + resolutionStrategy { + eachDependency { DependencyResolveDetails details -> + if (details.requested.group == 'org.codehaus.groovy') { + details.useVersion GroovySystem.version + details.because 'THe same version as Grodale Groovy is required' + } + } + } + } + audit { failOnError = false } if (!(project.name in withoutCompatibilityTests)) { - apply from: "${rootProject.projectDir}/gradle/compatibility-tests.gradle" + apply from: "${rootDir}/gradle/compatibility-tests.gradle" + } + + tasks.withType(Test).configureEach { + useJUnitPlatform() } } diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 7a379b228..4767c2aa1 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -1,5 +1,6 @@ plugins { id 'groovy' + id 'java-library' } apply from: 'gradle/load-gradle-properties.gradle' @@ -16,6 +17,7 @@ repositories { dependencies { implementation localGroovy() implementation gradleApi() + implementation "com.gradle.publish:plugin-publish-plugin:${gradleProperties.pluginPublishPlugin}" api "org.ysb33r.gradle:nodejs-gradle-plugin:${gradleProperties.nodejsGradleVersion}" api "org.ysb33r.gradle:grolifant-herd:${gradleProperties.grolifantVersion}" } diff --git a/buildSrc/src/main/groovy/AsciidoctorGradleGroovyProject.groovy b/buildSrc/src/main/groovy/AsciidoctorGradleGroovyProject.groovy index e81737364..1bdb1f11b 100644 --- a/buildSrc/src/main/groovy/AsciidoctorGradleGroovyProject.groovy +++ b/buildSrc/src/main/groovy/AsciidoctorGradleGroovyProject.groovy @@ -9,6 +9,7 @@ import org.gradle.api.tasks.SourceSetContainer import org.gradle.api.tasks.TaskProvider import org.gradle.plugins.ide.idea.model.IdeaModel import org.ysb33r.gradle.nodejs.NodeJSExtension +import org.ysb33r.grolifant.api.core.ProjectOperations @CompileStatic class AsciidoctorGradleGroovyProject implements Plugin { @@ -16,7 +17,12 @@ class AsciidoctorGradleGroovyProject implements Plugin { public final static String GENERATOR_NAME = 'generateModuleVersions' void apply(Project project) { - project.apply plugin: 'groovy' + project.pluginManager.identity { + apply 'java-library' + apply 'groovy' + } + ProjectOperations.maybeCreateExtension(project) + project.extensions.create('agProject',AsciidoctorGradleProjectExtension,project) TaskProvider generateModuleVersions = project.tasks.register(GENERATOR_NAME, ModuleVersions) diff --git a/buildSrc/src/main/groovy/AsciidoctorGradlePluginProject.groovy b/buildSrc/src/main/groovy/AsciidoctorGradlePluginProject.groovy index cc6d5b978..cfbf46abd 100644 --- a/buildSrc/src/main/groovy/AsciidoctorGradlePluginProject.groovy +++ b/buildSrc/src/main/groovy/AsciidoctorGradlePluginProject.groovy @@ -1,10 +1,62 @@ import groovy.transform.CompileStatic import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration +import org.gradle.api.artifacts.ExternalModuleDependency +import org.gradle.api.artifacts.dsl.DependencyHandler +import org.gradle.api.tasks.SourceSetContainer +import org.gradle.api.tasks.testing.Test + +import static org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME +import static org.gradle.api.tasks.SourceSet.TEST_SOURCE_SET_NAME @CompileStatic class AsciidoctorGradlePluginProject implements Plugin { void apply(Project project) { - project.apply plugin: AsciidoctorGradleGroovyProject + project.pluginManager.identity { + apply 'maven-publish' + apply 'groovy-gradle-plugin' + apply 'java-gradle-plugin' + apply 'com.gradle.plugin-publish' + apply AsciidoctorGradleGroovyProject + } + + final agProject = project.extensions.getByType(AsciidoctorGradleProjectExtension) + final sourceSets = project.extensions.getByType(SourceSetContainer) + final main = sourceSets.getByName(MAIN_SOURCE_SET_NAME) + final test = sourceSets.getByName(TEST_SOURCE_SET_NAME) + + project.dependencies.identity { + add(main.implementationConfigurationName, gradleApi()) + add(main.apiConfigurationName, "org.ysb33r.gradle:grolifant-herd:${agProject.versionOf('grolifant')}") + } + + addTestDependencies(agProject, project.dependencies, test.implementationConfigurationName) + + project.configurations.whenObjectAdded { Configuration configuration -> + if (configuration.name.contains('Test')) { + addTestDependencies(agProject, project.dependencies, configuration.name) + } + } + + project.tasks.withType(Test).configureEach { Test t -> + t.useJUnitPlatform() + } + } + + static void addTestDependencies( + AsciidoctorGradleProjectExtension agProject, + DependencyHandler dependencies, + String configurationName + ) { + dependencies.identity { + add(configurationName, it.project(path: ':testfixtures-jvm')) + add(configurationName, "org.spockframework:spock-core:${agProject.versionOf('spock')}") { ExternalModuleDependency emd -> + emd.identity { + exclude(group: 'org.codehaus.groovy') + exclude(group: 'org.hamcrest', module: 'hamcrest-core') + } + } + } } } diff --git a/buildSrc/src/main/groovy/AsciidoctorGradleProjectExtension.groovy b/buildSrc/src/main/groovy/AsciidoctorGradleProjectExtension.groovy new file mode 100644 index 000000000..ee7216698 --- /dev/null +++ b/buildSrc/src/main/groovy/AsciidoctorGradleProjectExtension.groovy @@ -0,0 +1,89 @@ +import com.gradle.publish.PluginBundleExtension +import groovy.transform.CompileStatic +import org.gradle.api.Project +import org.gradle.api.artifacts.ConfigurationContainer +import org.gradle.api.plugins.ExtensionContainer +import org.gradle.api.provider.Provider +import org.gradle.plugin.devel.GradlePluginDevelopmentExtension +import org.gradle.plugin.devel.PluginDeclaration +import org.gradle.plugin.devel.tasks.PluginUnderTestMetadata +import org.ysb33r.grolifant.api.core.ProjectOperations + +@CompileStatic +class AsciidoctorGradleProjectExtension { + + private final ProjectOperations projectOperations + private final ConfigurationContainer configurations + private final ExtensionContainer extensions + private final Provider pluginExtraTextProvider + + AsciidoctorGradleProjectExtension(Project project) { + this.projectOperations = ProjectOperations.find(project) + this.configurations = project.configurations + this.extensions = project.extensions + + this.pluginExtraTextProvider = projectOperations.versionProvider.map { version -> + (version.contains('-alpha') || version.contains('-beta')) ? + ". (If you need a production-ready version of the AsciidoctorJ plugin for Gradle use a 3.x release of this plugin instead)." + : '' + } + } + + void withOfflineTestConfigurations() { + final intTestOfflineRepo = configurations.maybeCreate('intTestOfflineRepo') + final intTestOfflineRepo2 = configurations.maybeCreate('intTestOfflineRepo2') + intTestOfflineRepo.tap { + extendsFrom(configurations.getByName('compileOnly')) + canBeResolved = true + canBeConsumed = false + } + intTestOfflineRepo2.tap { + canBeResolved = true + canBeConsumed = false + } + } + + void withAdditionalPluginClasspath() { + final apc = configurations.maybeCreate('additionalPluginClasspath') + apc.tap { + canBeResolved = true + canBeConsumed = false + } + projectOperations.tasks.whenNamed('pluginUnderTestMetadata', PluginUnderTestMetadata) { t -> + t.pluginClasspath.from(apc) + } + } + + String versionOf(String versionPropName) { + // For now it reads a Gradle property. IN future it will will read from a TOML file. + projectOperations.gradleProperty( + "${versionPropName}Version", + projectOperations.atConfigurationTime() + ).get() + } + + void configurePlugin( + String pluginId, + String providedDisplayName, + String providedDescription, + String implClass, + List providedTags + ) { + final gradlePlugin = extensions.getByType(GradlePluginDevelopmentExtension) + final pluginBundle = extensions.getByType(PluginBundleExtension) + final providedName = "${pluginId.replaceAll(~/\./, '')}Plugin".toString() + final extraText = pluginExtraTextProvider.get() + gradlePlugin.plugins.create(providedName) { PluginDeclaration pd -> + pd.tap { + id = pluginId + displayName = providedDisplayName + description = extraText ? "${providedDescription}. ${extraText}" : "${extraText}." + implementationClass = implClass + } + } + if (pluginBundle.pluginTags.isEmpty()) { + pluginBundle.pluginTags = [(providedName): (Collection)(['asciidoctor'] + providedTags)] + } + pluginBundle.pluginTags.putAll([(providedName): (['asciidoctor'] + providedTags)]) + } +} diff --git a/config/codenarc/codenarc.groovy b/config/codenarc/codenarc.groovy index 0452ce765..32860f8c7 100755 --- a/config/codenarc/codenarc.groovy +++ b/config/codenarc/codenarc.groovy @@ -129,9 +129,6 @@ ruleset { doNotApplyToFileNames = '*Spec.groovy,*Specification.groovy' } TernaryCouldBeElvis - VariableTypeRequired { - doNotApplyToFileNames = '*Spec.groovy,*Specification.groovy' - } VectorIsObsolete // rulesets/design.xml @@ -220,7 +217,7 @@ ruleset { SpaceAroundOperator SpaceBeforeClosingBrace SpaceBeforeOpeningBrace - TrailingWhitespace { + TrailingWhitespace { doNotApplyToFileNames = '*FunctionalSpec.groovy,*FunctionalSpecification.groovy' } @@ -365,7 +362,9 @@ ruleset { UnnecessaryFinalOnPrivateMethod UnnecessaryFloatInstantiation UnnecessaryGString - UnnecessaryGetter + UnnecessaryGetter { + ignoreMethodNames = 'isEmpty' + } UnnecessaryIfStatement UnnecessaryInstanceOfCheck UnnecessaryInstantiationToGetClass @@ -385,7 +384,6 @@ ruleset { UnnecessarySemicolon UnnecessarySetter UnnecessaryStringInstantiation - UnnecessarySubstring UnnecessaryTernaryExpression UnnecessaryToString UnnecessaryTransientModifier diff --git a/docs/src/docs/asciidoc/index.adoc b/docs/src/docs/asciidoc/index.adoc index 628a8e4a7..a8b39ede8 100644 --- a/docs/src/docs/asciidoc/index.adoc +++ b/docs/src/docs/asciidoc/index.adoc @@ -27,7 +27,7 @@ include::parts/asciidoctorj-epub-plugin.adoc[] include::parts/asciidoctorj-revealjs-plugin.adoc[] -include::parts/asciidoctorj-leanpub-plugin.adoc[] +// include::parts/asciidoctorj-leanpub-plugin.adoc[] include::parts/asciidoctor-diagram.adoc[] diff --git a/docs/src/docs/asciidoc/parts/common-task-configuration.adoc b/docs/src/docs/asciidoc/parts/common-task-configuration.adoc index d3f018abc..f25df4fbe 100644 --- a/docs/src/docs/asciidoc/parts/common-task-configuration.adoc +++ b/docs/src/docs/asciidoc/parts/common-task-configuration.adoc @@ -18,8 +18,10 @@ configurations:: Specify additional configurations copyAllResources:: Copy all resources to the output directory copyNoResources:: Do not copy any resources to the output directory copyResourcesOnlyIf:: Only copy resources if the backend matches the listed backend. +executionMode:: Specifies whether Asciidoctor conversions should be run in-process or out-of-process. + Default: `JAVA_EXEC`. + In version 3.x this was called `inProcess` languages:: Invoke source language support but specifying one or more languages. -inProcess:: Specifies whether Asciidoctor conversions should be run in-process or out-of-process. Default: `true` (in-process). logDocuments:: Specifies if documents being processed should be logged on console. Type: boolean. Default: `false`. options:: A shortcut to `asciidoctorj.options`. outputDir:: where generated docs go. diff --git a/gems/build.gradle b/gems/build.gradle index 4c563efa6..3e29c8033 100644 --- a/gems/build.gradle +++ b/gems/build.gradle @@ -1,30 +1,27 @@ +import org.ysb33r.gradle.gradletest.GradleTest + +agProject { + configurePlugin 'org.asciidoctor.jvm.gems', + 'Simplifies support for using external GEMs with AsciidoctorJ', + 'Provides appropriate tasks and configurations for adding GEMs to AsciidoctorJ conversions', + 'org.asciidoctor.gradle.jvm.gems.AsciidoctorGemSupportPlugin', + ['asciidoctorj', 'gems', 'jruby'] +} dependencies { - implementation "com.github.jruby-gradle:jruby-gradle-core-plugin:${jrubyGradleVersion}" + implementation "org.ysb33r.gradle.jruby:jrubygradle-resolver:${pluginJrubySimpleVersion}" api project(':asciidoctor-gradle-jvm') } -test { +tasks.named('test', Test) { dependsOn ':testfixtures-offline-repo:buildOfflineRepositories' systemProperties OFFLINE_REPO: offlineRepoRoot.absolutePath } -gradleTest { -// gradleArguments '-d' - if(OS.windows) { +tasks.named('gradleTest', GradleTest) { + if (OS.windows) { // Expecting external-gems to fail due to JRuby issue expectFailure ~/external-gems/ } - - gradleArguments '-d' - - // TODO: Re-enable this test - enabled = false - println "************ :asciidoctor-gradle-jvm-gems:gradleTest is disabled due to potential bug in asciidoctorj *********" } -configurePlugin 'org.asciidoctor.jvm.gems', - 'Simplifies support for using external GEMs with AsciidoctorJ', - 'Provides appropriate tasks and configurations for adding GEMs to AsciidoctorJ conversions', - [ 'asciidoctorj', 'gems', 'jruby'] - diff --git a/gems/src/gradleTest/external-gems/build.gradle b/gems/src/gradleTest/external-gems/build.gradle index c5a24ebd2..6bd634747 100644 --- a/gems/src/gradleTest/external-gems/build.gradle +++ b/gems/src/gradleTest/external-gems/build.gradle @@ -18,12 +18,8 @@ dependencies { } } -asciidoctorj { - requires 'asciidoctor-bibtex' -} - asciidoctor { - dependsOn asciidoctorGemsPrepare + withGemJar 'asciidoctorGemsJar' secondarySources { include 'biblio.bib' @@ -34,6 +30,6 @@ asciidoctor { // end::use-gems[] task runGradleTest { - dependsOn asciidoctor + dependsOn 'asciidoctor' } diff --git a/gems/src/intTest/groovy/org/asciidoctor/gradle/jvm/gems/AsciidoctorGemPrepareTaskCachingFunctionalSpec.groovy b/gems/src/intTest/groovy/org/asciidoctor/gradle/jvm/gems/AsciidoctorGemPrepareTaskCachingFunctionalSpec.groovy index f36aae8ce..b8ef8fc50 100644 --- a/gems/src/intTest/groovy/org/asciidoctor/gradle/jvm/gems/AsciidoctorGemPrepareTaskCachingFunctionalSpec.groovy +++ b/gems/src/intTest/groovy/org/asciidoctor/gradle/jvm/gems/AsciidoctorGemPrepareTaskCachingFunctionalSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,14 @@ package org.asciidoctor.gradle.jvm.gems import org.asciidoctor.gradle.jvm.gems.internal.FunctionalSpecification -import org.asciidoctor.gradle.testfixtures.CachingTest +import org.asciidoctor.gradle.testfixtures.BuildScanFixture +import org.asciidoctor.gradle.testfixtures.CachingTestFixture import spock.lang.Issue import spock.lang.Timeout -class AsciidoctorGemPrepareTaskCachingFunctionalSpec extends FunctionalSpecification implements CachingTest { +class AsciidoctorGemPrepareTaskCachingFunctionalSpec extends FunctionalSpecification + implements CachingTestFixture, BuildScanFixture { + private static final String DEFAULT_TASK = 'asciidoctorGemsPrepare' private static final String OUTPUT_DIR_PATH = 'build/.asciidoctorGems' private static final String DEFAULT_GEM_NAME = 'asciidoctor-revealjs' @@ -136,23 +139,22 @@ class AsciidoctorGemPrepareTaskCachingFunctionalSpec extends FunctionalSpecifica @Override File getBuildFile(String extraContent) { - File buildFile = testProjectDir.newFile('build.gradle') - buildFile << """ - plugins { - id 'org.asciidoctor.jvm.gems' + writeGroovyBuildFile( + 'org.asciidoctor.jvm.gems', + extraContent + ).withWriterAppend { w -> + if (performBuildScan) { + w.println(buildScanConfiguration) } - ${ -> scan ? buildScanConfiguration : '' } - ${offlineRepositories} - + w.println ''' repositories { ruby { gems() } } - - ${extraContent} - """ + '''.stripIndent() + } buildFile } diff --git a/gems/src/intTest/groovy/org/asciidoctor/gradle/jvm/gems/internal/FunctionalSpecification.groovy b/gems/src/intTest/groovy/org/asciidoctor/gradle/jvm/gems/internal/FunctionalSpecification.groovy index 27f2e304b..78529a4c3 100644 --- a/gems/src/intTest/groovy/org/asciidoctor/gradle/jvm/gems/internal/FunctionalSpecification.groovy +++ b/gems/src/intTest/groovy/org/asciidoctor/gradle/jvm/gems/internal/FunctionalSpecification.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,56 +15,31 @@ */ package org.asciidoctor.gradle.jvm.gems.internal -import org.apache.commons.io.FileUtils +import org.asciidoctor.gradle.testfixtures.FunctionalTestFixture import org.gradle.testkit.runner.GradleRunner -import org.junit.Rule -import org.junit.rules.TemporaryFolder import org.ysb33r.grolifant.api.core.OperatingSystem import spock.lang.Specification +import spock.lang.TempDir -class FunctionalSpecification extends Specification { +class FunctionalSpecification extends Specification implements FunctionalTestFixture { public static final String TEST_PROJECTS_DIR = System.getProperty( - 'TEST_PROJECTS_DIR', - './asciidoctor-gradle-jvm-gems/src/intTest/projects' + 'TEST_PROJECTS_DIR', + './asciidoctor-gradle-jvm-gems/src/intTest/projects' ) public static final String TEST_REPO_DIR = System.getProperty( - 'OFFLINE_REPO', - './testfixtures/offline-repo/build/repo' + 'OFFLINE_REPO', + './testfixtures/offline-repo/build/repo' ) public static final OperatingSystem OS = OperatingSystem.current() - @Rule - TemporaryFolder testProjectDir + @TempDir + File testProjectDir - @Rule - TemporaryFolder alternateProjectDir - - GradleRunner getGradleRunner(List taskNames) { - GradleRunner.create() - .withProjectDir(testProjectDir.root) - .withArguments(taskNames) - .withPluginClasspath() - .forwardOutput() - .withDebug(true) - } - - @SuppressWarnings(['BuilderMethodWithSideEffects']) - void createTestProject(String docGroup) { - FileUtils.copyDirectory(new File(TEST_PROJECTS_DIR, docGroup), testProjectDir.root) + void setup() { + projectDir.mkdirs() } - String getOfflineRepositories() { - File repo = new File(TEST_REPO_DIR, 'repositories.gradle') - if (!repo.exists()) { - throw new FileNotFoundException( - "${repo} not found. Run ':testfixture-offline-repo:buildOfflineRepositories' build task" - ) - } - - if (OS.windows) { - "apply from: /${repo.absolutePath}/" - } else { - "apply from: '${repo.absolutePath}'" - } + GradleRunner getGradleRunner(List taskNames) { + getGroovyGradleRunner(taskNames) } } \ No newline at end of file diff --git a/gems/src/main/groovy/org/asciidoctor/gradle/jvm/gems/AsciidoctorGemPrepare.groovy b/gems/src/main/groovy/org/asciidoctor/gradle/jvm/gems/AsciidoctorGemPrepare.groovy index 932e1c751..cb7be0963 100644 --- a/gems/src/main/groovy/org/asciidoctor/gradle/jvm/gems/AsciidoctorGemPrepare.groovy +++ b/gems/src/main/groovy/org/asciidoctor/gradle/jvm/gems/AsciidoctorGemPrepare.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,48 +15,34 @@ */ package org.asciidoctor.gradle.jvm.gems -import com.github.jrubygradle.api.core.AbstractJRubyPrepare import groovy.transform.CompileStatic import org.asciidoctor.gradle.jvm.AsciidoctorJExtension -import org.gradle.api.provider.Provider import org.gradle.api.tasks.CacheableTask +import org.gradle.workers.WorkerExecutor +import org.ysb33r.gradle.jruby.api.tasks.AbstractGemPrepareTask +import javax.inject.Inject import java.util.concurrent.Callable -import static com.github.jrubygradle.api.gems.GemUtils.JRUBY_ARCHIVE_NAME - -/** Prepare additional GEMs for AsciidoctorJ. +/** + * Prepare additional GEMs for AsciidoctorJ. * * @since 2.0 */ @CacheableTask @CompileStatic -class AsciidoctorGemPrepare extends AbstractJRubyPrepare { - - /** Location of {@code jruby-complete} JAR. - * - * @return Path on local filesystem - */ - @Override - protected Provider getJrubyJarLocation() { - project.provider({ AsciidoctorJExtension jruby -> - jruby.configuration.files.find { it.name.startsWith(JRUBY_ARCHIVE_NAME) } - }.curry(jruby) as Callable) - } +class AsciidoctorGemPrepare extends AbstractGemPrepareTask { - /** Version of JRuby that will be used if explicitly set - * - * This method does not resolve any files to obtain the version. - * - * @return Explicitly configured project global version of JRuby or - * {@code null} if inferred from the asciidoctorj dependency. - */ - @Override - protected String getProposedJRubyVersion() { - jruby.jrubyVersion - } +// private final Provider jrubyJarLocationProvider + private final AsciidoctorJExtension jruby - private AsciidoctorJExtension getJruby() { - project.extensions.getByType(AsciidoctorJExtension) + @Inject + @SuppressWarnings('UnnecessarySetter') + AsciidoctorGemPrepare(WorkerExecutor we) { + super(we) + this.jruby = project.extensions.getByType(AsciidoctorJExtension) + setJrubyJarProvider(project.provider({ AsciidoctorJExtension jruby -> + jruby.configuration.files.find { it.name.startsWith(JRUBY_COMPLETE_NAME) } + }.curry(jruby) as Callable)) } } diff --git a/gems/src/main/groovy/org/asciidoctor/gradle/jvm/gems/AsciidoctorGemSupportPlugin.groovy b/gems/src/main/groovy/org/asciidoctor/gradle/jvm/gems/AsciidoctorGemSupportPlugin.groovy index da548ace8..8162fe9d6 100644 --- a/gems/src/main/groovy/org/asciidoctor/gradle/jvm/gems/AsciidoctorGemSupportPlugin.groovy +++ b/gems/src/main/groovy/org/asciidoctor/gradle/jvm/gems/AsciidoctorGemSupportPlugin.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,17 +15,15 @@ */ package org.asciidoctor.gradle.jvm.gems -import com.github.jrubygradle.api.core.JRubyCorePlugin import groovy.transform.CompileStatic import org.asciidoctor.gradle.jvm.AsciidoctorJBasePlugin -import org.asciidoctor.gradle.jvm.AsciidoctorJExtension import org.gradle.api.Action import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.artifacts.Configuration -import org.ysb33r.grolifant.api.v4.TaskProvider - -import static org.ysb33r.grolifant.api.v4.TaskProvider.registerTask +import org.gradle.api.tasks.bundling.Jar +import org.ysb33r.gradle.jruby.api.plugins.JRubyResolverPlugin +import org.ysb33r.grolifant.api.core.ProjectOperations /** Plugin that simplifies that management of external GEMs. * @@ -44,19 +42,25 @@ import static org.ysb33r.grolifant.api.v4.TaskProvider.registerTask class AsciidoctorGemSupportPlugin implements Plugin { public static final String GEM_CONFIGURATION = 'asciidoctorGems' - public static final String GEMPREP_TASK = 'asciidoctorGemsPrepare' + public static final String GEMPREP_TASK = "${GEM_CONFIGURATION}Prepare" + public static final String JAR_TASK = "${GEM_CONFIGURATION}Jar" @Override void apply(Project project) { - project.apply plugin: AsciidoctorJBasePlugin - project.apply plugin: JRubyCorePlugin + project.pluginManager.tap { + apply AsciidoctorJBasePlugin + apply JRubyResolverPlugin + } + final po = ProjectOperations.find(project) Configuration gemConfig = project.configurations.maybeCreate(GEM_CONFIGURATION) + gemConfig.canBeResolved = true + gemConfig.canBeConsumed = false Action gemPrepDefaults = new Action() { @Override void execute(AsciidoctorGemPrepare asciidoctorGemPrepare) { - asciidoctorGemPrepare.with { - dependencies gemConfig + asciidoctorGemPrepare.tap { + gemConfiguration = gemConfig group = 'dependencies' description = 'Prepare additional GEMs for AsciidoctorJ' outputDir = "${project.buildDir}/.asciidoctorGems" @@ -64,12 +68,23 @@ class AsciidoctorGemSupportPlugin implements Plugin { } } - TaskProvider prepTask = registerTask( - project, - GEMPREP_TASK, - AsciidoctorGemPrepare, - gemPrepDefaults + final prepTask = project.tasks.register( + GEMPREP_TASK, + AsciidoctorGemPrepare, + gemPrepDefaults ) - project.extensions.getByType(AsciidoctorJExtension).gemPaths { prepTask.get().outputDir } + + project.tasks.register(JAR_TASK, Jar) { jar -> + jar.tap { + from(prepTask.map { it.outputDir }) + archiveFileName.set("${GEM_CONFIGURATION}.jar".toString()) + destinationDirectory.set( + project.objects + .directoryProperty() + .fileProvider(po.buildDirDescendant('.asciidoctorGemsJars')) + ) + dependsOn(prepTask) + } + } } } diff --git a/gems/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.jvm.gems.properties b/gems/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.jvm.gems.properties deleted file mode 100644 index 104a2e15a..000000000 --- a/gems/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.jvm.gems.properties +++ /dev/null @@ -1,17 +0,0 @@ -# -# Copyright 2013-2023 the original author or authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://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. -# - -implementation-class=org.asciidoctor.gradle.jvm.gems.AsciidoctorGemSupportPlugin diff --git a/gems/src/test/groovy/org/asciidoctor/gradle/jvm/gems/AsciidoctorGemSupportPluginSpec.groovy b/gems/src/test/groovy/org/asciidoctor/gradle/jvm/gems/AsciidoctorGemSupportPluginSpec.groovy index d2fa75b6e..664d7db6b 100644 --- a/gems/src/test/groovy/org/asciidoctor/gradle/jvm/gems/AsciidoctorGemSupportPluginSpec.groovy +++ b/gems/src/test/groovy/org/asciidoctor/gradle/jvm/gems/AsciidoctorGemSupportPluginSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,7 +33,6 @@ class AsciidoctorGemSupportPluginSpec extends Specification { then: project.configurations.getByName(GEM_CONFIGURATION) - project.tasks.getByName(GEMPREP_TASK).outputDir == project.file( "${project.buildDir}/.asciidoctorGems" ) - !project.tasks.getByName(GEMPREP_TASK).dependencies.empty + project.tasks.getByName(GEMPREP_TASK).outputDir.get() == project.file( "${project.buildDir}/.asciidoctorGems" ) } } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index c45565b46..1001e9a57 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,9 +1,9 @@ -version = 4.0.0-alpha.1 +version = 4.0.0-alpha.2-SNAPSHOT group = org.asciidoctor sourceCompatibility = 1.8 targetCompatibility = 1.8 -copyrightYear = 2013-2023 +copyrightYear = 2013-2024 project_description = A Gradle plugin suite that uses Asciidoctor via JRuby/Node.js to process AsciiDoc source files within the project. project_website = https://github.com/asciidoctor/asciidoctor-gradle-plugin project_issues = https://github.com/asciidoctor/asciidoctor-gradle-plugin/issues @@ -11,14 +11,15 @@ project_vcs = https://github.com/asciidoctor/asciidoctor-gradle-plugin.g cglibVersion = 3.3.0 jsoupVersion = 1.13.1 -spockVersion = 1.3-groovy-2.5 -grolifantVersion = 2.0.0-alpha.6 +spockVersion = 2.3-groovy-3.0 +grolifantVersion = 2.2.1 jacocoVersion = 0.8.6 -jrubyGradleVersion = 2.0.2 -codenarcVersion = 1.3 -nodejsGradleVersion = 0.12.3 +codenarcVersion = 3.3.0 +nodejsGradleVersion = 2.2.0 +pluginJrubySimpleVersion = 1.0.0 +pluginPublishPlugin = 1.2.1 org.gradle.daemon = true org.gradle.parallel = true -org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m +org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=768m diff --git a/gradle/compatibility-tests.gradle b/gradle/compatibility-tests.gradle index fb40b4c5b..066ac1bc8 100644 --- a/gradle/compatibility-tests.gradle +++ b/gradle/compatibility-tests.gradle @@ -2,14 +2,9 @@ apply plugin: 'org.ysb33r.gradletest' pluginManager.withPlugin('org.ysb33r.cloudci') { ci { - appveyor { - gradleTest { - versions '7.0.2', '7.6' - } - } no_ci { gradleTest { - versions '7.0.2', '7.6' + versions '7.0.2', '7.6.1', '8.5' } } } diff --git a/gradle/gradle-plugin-dependencies.gradle b/gradle/gradle-plugin-dependencies.gradle deleted file mode 100644 index 61212a03d..000000000 --- a/gradle/gradle-plugin-dependencies.gradle +++ /dev/null @@ -1,17 +0,0 @@ -dependencies { - implementation gradleApi() - api "org.ysb33r.gradle:grolifant-herd:${grolifantVersion}" - testImplementation project(':testfixtures-jvm') - testImplementation("org.spockframework:spock-core:$spockVersion") { - exclude group: 'org.codehaus.groovy' - exclude group: 'org.hamcrest', module: 'hamcrest-core' - } - - if(configurations.findByName('intTestCompile')) { - intTestImplementation project(':testfixtures-jvm') - intTestImplementation("org.spockframework:spock-core:$spockVersion") { - exclude group: 'org.codehaus.groovy' - exclude group: 'org.hamcrest', module: 'hamcrest-core' - } - } -} diff --git a/gradle/integration-tests.gradle b/gradle/integration-tests.gradle index b6fe11c74..16f30260f 100644 --- a/gradle/integration-tests.gradle +++ b/gradle/integration-tests.gradle @@ -1,14 +1,17 @@ + sourceSets { intTest } configurations { - intTestOfflineRepo + intTestOfflineRepo { + canBeConsumed = false + canBeResolved = true + } } - // For a single test, you can run "gradle intTest --tests " -task intTest(type: Test) { +tasks.register('intTest', Test) { description = "Runs the plugin's integration tests" group = "verification" @@ -20,17 +23,18 @@ task intTest(type: Test) { classpath = sourceSets.intTest.runtimeClasspath dependsOn ':testfixtures-offline-repo:buildOfflineRepositories' - systemProperties OFFLINE_REPO: offlineRepoRoot.absolutePath + systemProperties OFFLINE_REPO: offlineRepoRoot.absolutePath, + TEST_PROJECTS_DIR: file('src/intTest/projects') } -check { - dependsOn intTest +tasks.named('check') { + dependsOn 'intTest' } pluginManager.withPlugin('jacoco') { jacocoTestReport { - executionData intTest - mustRunAfter intTest + executionData tasks.intTest + mustRunAfter 'intTest' } } diff --git a/gradle/publishing.gradle b/gradle/publishing.gradle index 3c7299a20..bb5783b14 100644 --- a/gradle/publishing.gradle +++ b/gradle/publishing.gradle @@ -14,73 +14,28 @@ * limitations under the License. */ -apply plugin: 'java-gradle-plugin' -apply plugin: 'com.gradle.plugin-publish' - ext { pluginIdPrefix = 'org.asciidoctor' pluginExtraText = (version.contains('-alpha') || version.contains('-beta')) ? - " (If you need a production-ready version of the AsciidoctorJ plugin for Gradle use a 3.x release of this plugin instead)" - : '' - - - configurePlugin = { String providedId, String providedDisplayName, String providedDescription, List providedTags -> - final String providedName = providedId.replaceAll(~/\./, '') - final File props = file("src/main/resources/META-INF/gradle-plugins/${providedId}.properties") - if (!props.exists()) { - throw new GradleException("${props} does not exist") - } - String className - for (String line : props.readLines()) { - def match = line =~ /^implementation-class\s*=\s*(.+?)$/ - - if (match.matches()) { - className = match[0][1] - break - } - } - - if (className == null) { - throw new GradleException("${props} does not contain implemention-class") - } - - gradlePlugin { - plugins { - "${providedName}Plugin" { - id = providedId - implementationClass = className - } - } - } - - pluginBundle { - plugins { - "${providedName}Plugin" { - id = providedId - displayName = providedDisplayName - description = "${providedDescription}${pluginExtraText}" - tags = (['asciidoctor'] + providedTags) - } - } - } - } + ". (If you need a production-ready version of the AsciidoctorJ plugin for Gradle use a 3.x release of this plugin instead)." + : '' } -jar { +tasks.named('jar', Jar) { manifest { attributes( - 'Built-By': System.properties['user.name'], - 'Created-By': buildCreatedBy, + 'Built-By': System.properties['user.name'], + 'Created-By': buildCreatedBy, // 'Build-Date': buildDate, // 'Build-Time': buildTime, - 'Build-Revision': buildRevision, - 'Specification-Title': project.name, - 'Specification-Version': project.version, - 'Specification-Vendor': project.name, - 'Implementation-Title': project.name, - 'Implementation-Version': project.version, - 'Implementation-Vendor': project.name + 'Build-Revision': buildRevision, + 'Specification-Title': project.name, + 'Specification-Version': project.version, + 'Specification-Vendor': project.name, + 'Implementation-Title': project.name, + 'Implementation-Version': project.version, + 'Implementation-Vendor': project.name ) } @@ -116,8 +71,8 @@ ext { } } [ - aalmiray: 'Andres Almiray', - ysb33r : 'Schalk W. Cronjé' + aalmiray: 'Andres Almiray', + ysb33r : 'Schalk W. Cronjé' ].each { devId, devName -> developer { id devId @@ -130,23 +85,23 @@ ext { } contributors { [ - afolmert : 'Adam Folmert', - anschmi : 'Andreas Schmidt', - bmuschko : 'Benjamin Muschko', - bobbytank42: 'Robert Panzer', - dvyazelenko: 'Dmitri Vyazelenko', - jlupi : 'Lukasz Pielak', - lhotari : 'Lari Hotari', - McPringle : 'Marcus Fihlon', - Mogztter : 'Guillaume Grossetie', - mrhaki : 'Hubert Klein Ikkink', - msgilligan : 'Sean Gilligan', - noamt : 'Noam Tenne', - oti : 'Otmar Humbel', - rwinch : 'Rob Winch', - sclassen : 'Stephan Classen', - Skyr : 'Stefan Schlott', - tombujok : 'Tom Bujok' + afolmert : 'Adam Folmert', + anschmi : 'Andreas Schmidt', + bmuschko : 'Benjamin Muschko', + bobbytank42: 'Robert Panzer', + dvyazelenko: 'Dmitri Vyazelenko', + jlupi : 'Lukasz Pielak', + lhotari : 'Lari Hotari', + McPringle : 'Marcus Fihlon', + Mogztter : 'Guillaume Grossetie', + mrhaki : 'Hubert Klein Ikkink', + msgilligan : 'Sean Gilligan', + noamt : 'Noam Tenne', + oti : 'Otmar Humbel', + rwinch : 'Rob Winch', + sclassen : 'Stephan Classen', + Skyr : 'Stefan Schlott', + tombujok : 'Tom Bujok' ].each { devId, devName -> contributor { name devName @@ -159,37 +114,14 @@ ext { } } -publishing { - publications { - mavenJava(MavenPublication) { - from components.java - afterEvaluate { - artifact sourcesJar.baseName - artifact javadocJar.baseName - } - pom.withXml { - asNode().children().last() + pomConfig - asNode().appendNode('description', project.project_description) - } - } +publishing.publications.withType(MavenPublication).configureEach { + pom.withXml { + asNode().appendNode('description', project.project_description) } } -//tasks.withType(ValidateTaskProperties) { validateTaskProperties -> -// validateTaskProperties.failOnWarning = true -// validateTaskProperties.enableStricterValidation = true -//} - pluginBundle { website = project.project_website vcsUrl = project.project_vcs - description = project.project_description - tags = ['asciidoctor'] - - mavenCoordinates { - groupId = project.group - artifactId = project.name - version = project.version - } } diff --git a/gradle/remote-tests.gradle b/gradle/remote-tests.gradle new file mode 100644 index 000000000..54918247e --- /dev/null +++ b/gradle/remote-tests.gradle @@ -0,0 +1,39 @@ +sourceSets { + remoteTest { + compileClasspath += sourceSets.main.output + runtimeClasspath += sourceSets.main.output + } +} + +configurations { + remoteTestImplementation.extendsFrom implementation + remoteTestRuntimeOnly.extendsFrom runtimeOnly +} + +// For a single test, you can run "gradle remoteTest --tests " +tasks.register('remoteTest', Test) { + description = "Runs the plugin's remote execution tests" + group = "verification" + + mustRunAfter "test" + inputs.files sourceSets.main.output.classesDirs + inputs.files sourceSets.main.output.resourcesDir + + testClassesDirs = sourceSets.remoteTest.output.classesDirs + classpath = sourceSets.remoteTest.runtimeClasspath + + dependsOn ':testfixtures-offline-repo:buildOfflineRepositories' + systemProperties OFFLINE_REPO: offlineRepoRoot.absolutePath +} + +check { + dependsOn remoteTest +} + +pluginManager.withPlugin('jacoco') { + jacocoTestReport { + executionData remoteTest + mustRunAfter remoteTest + } +} + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 62d4c053550b91381bbd28b1afc82d634bf73a8a..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f 100644 GIT binary patch delta 6656 zcmY+Ibx_pN*Z*PZ4(U#j1qtbvrOTyO8fghZ8kYJfEe%U|$dV!@ASKczEZq$fg48M@ z;LnHO_j#Uq?%bL4dY^md%$$4Y+&@nKC|1uHR&59YNhubGh72|a#ylPdh9V+akp|I; zPk^W-a00GrFMkz_NSADdv2G2-i6rb=cB_@WnG(**4ZO$=96R=t|NZ@|0_z&q3GwO^ ziUFcuj$a9QaZ3j?xt`5#q`sT-ufrtBP0nt3IA&dr*+VCsBzBVW?vZ6eZr0oD%t33z zm~-5IVsjy(F>;S~Pm@bxX85>Z*@(QL6i3JQc?1ryQFcC@X^2^mZWhFv|v? z49>l|nA&XNQ6#OvccUTyBMB*WO#NA;FW5|eE_K6dtVYP2G?uUZ09!`Iq1IF2gA(aS zLu@G^cQJmh=x?-YsYa@E6QnE5+1@ds&0f#OQRDl^GnIT_m84G5XY%W z;Ck6bk^Oeu*Ma-XmxI5GjqzWNbJMsQF4)WfMZEA{oxW0E32e)*JfG}3otPishIQBw zkBe6N#4pKPN>q1R6G1@5&(u#5yPEToMBB6_oEK|q z@(i5j!?;NNCv~=HvW%zF&1yWBq(nJa_#``G&SRmQvE|jePUPs{J!$TacM|e}Fsceb zx+76|mDp6@w>)^DIl{8?)6XYNRU|2plG8Jy&7(^9SdOWNKKJK&>0!z6XiN4J*Jkao z=E1y5x-XDC==Ub+8fLb#OW&{2ww{h^xlJFYAMOUd)}Xg@j?ak{7Kno6?9S~F?|6Df zHo|ijXX~`Sp;Vf!nR;m%vUhq>zvlRXsL0u*Tt?F#yR}3tF0#of{(UjitqST|!{aBA zicWh+URU}Jnc*sg9iMkf0pggpd?3TI*C-q$2QOdCC7rV+CHBmjS3O%a3VeZ$ZSs5ubJuJp%e%$LHgrj0niYjX;4kt z&2~j%@q3MO)-QGCA{>o%eZu){ou^MgC6~Z8Y=tc!qF=|TOlG3wJXbaLYr-;$Ch=2J z_UcE59Xzq&h0LsjLrcZrQSa}#=0~Lk|4?e4M z6d;v->NCC1oMti)RRc`Ys0?JXQjsZ@VdCy%Z)TptCrI>0Tte$pR!@yJesoU2dtyuW z7iFsE8)CkbiJP+OP28;(%?!9WddQZcAid@R@`*e%3W65$g9ee`zvwb(VPO+uVBq6p z{QDR%CR(2z@?&9Obm3xPi2lzvfip`7q`_7UDD|lRS}4=bsl3xQIOi0@GSvMuDQX}* z4B^(DI<${qUhcLqO`itJU;e<%%iS+R3I^_xIV1O%sp*x~;-dn` zt$8>RnSUh#rU3{-47067W^WNwTdq-t$-U>Hj%r!GD!gLa;kV zW5g6pCqV+!q8LgrI49(}fIc5K_`FLV4_E#XZ6{<>w8wzc%V9k!!Byg5-0WY+J?1*z%9~Aj4WQr1Jsn2(G!U8fFpi(wsy@JLg^d+IB0kl89 z0@Ssqf!L9JjYKK$J=978+NO*5^C)GPH2a%4hm$HROjM|N3g9ch9kDLh*nlwqy{mVM z`P(l#>3NnK%#O8tSb(VmZrG+`dRD#=Cc1P%(y5S?*Hj5E{vg&Eiw!YV>S#7_WRDVoFxT5m=gFi4)}y5V%KT8!xbsH_rmR& zsmM?%J}K$1l8d?2+m(}2c}-G`x>CY%Y&QBJRC$sKM}zN<9{IlF@yJEG<^0={$+`Hc zDodJ)gCADJ_bD#am(c2ojXKb|j+ENJ#58PAA&pZXufrFzBwnuuo+khfMgd!DMlU#v z9|JelQO~E2;d^w!RZJbt%IANIudpKSP)cssoWhq)>({nvcfCr0=9=FAIMuZm8Eo=} z|DND}8_PB5HqG(QwDvaM@orYBZ9kCkHV*rxKTy>q7n~0emErUwLbhq;VN<2nKT&*a2Ajz z;lKBzU2i8KLV`d)Y&ae)!HcGk$dO}Or%8KF@kE@jU1h@zwpw{6p4ME|uC$Za-ERR2 ztQvL&uOZLe(k{w_+J^ng+l}~N8MP>F1Z$fLu}D-WWaeu#XduP@#8JpmH(X>rIL)k3 zyXNyTIB1(IH%S&pQ{rWaTVfB$~-;RnlY z^(y7mR>@=brI>!TrA)BQsQ={b*6$=1Eqbuu6IdhJ&$YD$08AwtNr9*J?%-WT<;O1< zPl1<@yeqfZ>@s4azqTf<=I4(kU^+^Qkstm%WM-0_VLm({jFc8`5Df2Q1Y9zMZu0^! zsO_yh2Sz9K>Jq6fkYbBZocEJ6C!SdEzYDkiEtNJs{?!tA#e|oiN+VaaAobwKef_kUup&4scD?1+}Q8)DaekkMYn-FOS{J%NY za^mmJ^n`t*1p@hF*gl#L+5wr40*(ub4J#L|@oCl~@|4UvCjHBYDQv&S zhyGMAkRO^tF_dyi&XM)4mQ;k>kj?RgRo@-?==oD+ns*>bf@&fPXF|4U0&ib2 zo~1ZdmCPWf!W9#sGP@9X$;Rc`tjbz^&JY}z{}j9bl?;VC{x)TfQH$D^WowKL&4Zx@ zdSn+QV7H(e0xRfN6aBfH)Q=@weoD?dvu6^ZS)zqb>GwMmIuS8zJfaMUQx9>%k~w34 z3}_B2Jj~u=SnJ~vZPj*)UoDi_FtT=UAb#J^b4B%R6z3H%cj-1OCjU5F$ky>By1zsg z>2A0ccp29(Y<;my|J_g-r{1I@+*O$>!R3`_sFNP4e}LD1e1mM&SA`;;TR0I`_hESV zh4U*9ecK$0=lYk`{SR_cm$}iS*?yQR(}T-5ub?Wn^#RTe*^1~ya%`!xWq-F*WH@%nnZTNREA z3eUX2uM9b_w!Zo$nVTotEtzuL(88N)H~v_G=89|(@IFz~Wq6ME);z(!2^PkR2B&kE zxR)xV8PE|Hszyjp#jNf=ZIQ7JR~4Ls#Vd@mPF(7R5VO$akUq8JM+sn>ZVg(lJZ)5qjqdw(*7tuwjY#0tx+|!sTz9yV~%HOdrb#!5w9>*0LrCS z%wF$Yc6~hqVQZzoC^D<(-h0aOtk}kn<<*xF61HQr<5}efY{zXXA+PaJG7vT&{Oz(@Uu!V#Fp9%Ht!~@;6AcD z$lvlPu&yd(YnAHfpN51*)JN0aYw9gGk{NE7!Oqu4rBp}F30669;{zcH-a7w9KSpDQPIE_f9T zit? zJSjTKWbe{f{9BmSDAFO1(K0oqB4578tU0(oRBE^28X>xDA!1C&VJEiYak4_ZTM*7M`hv_ zw3;2ndv3X$zT!wa7TrId{gNE`Vxf}j5wsyX+;Kn<^$EJT`NzznjyYx=pYMkZjizEU zb;Gg8Pl_pqxg)9P)C)Hxh_-mQ;u-I_Ol>d^>q08zFF!>Z3j1-HmuME_TGZ*Ev;O0O z%e(edJfV<6t3&FKwtInnj9EeQhq9;o5oLJoiKwWF5bP2~Feh#P4oN()JT0pdq!9x* ze3D-1%AV#{G=Op$6q?*Z>s{qFn}cl@9#m@DK_Bs@fdwSN`Qe18_WnveRB583mdMG- z?<3pJC!YljOnO8=M=|Cg)jw;4>4sna`uI>Kh&F20jNOk9HX&}Ry|mHJ+?emHnbYLJ zwfkx@slh31+3nq-9G5FVDQBHWWY}&hJ-fpDf!lQdmw8dlTt#=)20X74S>c&kR(?PT zBg)Y%)q&|hW1K;`nJPAGF*c3{3`FvrhD9=Ld{3M*K&5$jRhXNsq$0CLXINax1AmXX ziF39vkNtcK6i^+G^AEY!WalGazOQ$_#tx?BQ{YY$&V&42sICVl8@AI6yv;sGnT;@f zL=}rZcJqNwrEEA=GDdEe8Z=f9>^?($oS8xGdFf1eUWTYtZF<3tu2V%noPBnd=thZ+ zO&xoc?jvXG7Xt!RTw#5VN50UjgqSntw9Y35*~pxz=8OzkXg{@S2J%+{l3Q>B_qbnl z20Deb7JM&ZSp`%X>xWpb>FF8q7Nq&4#a1}A-(-!aMDmVbz05D!NpUzVe{~72h%cOh zwQFNai2a$K|hFgDk(oPF_tuf{BV!=m0*xqSzGAJ(~XUh8rk#{YOg0ReK>4eJl z;-~u5v$}DM)#vER>F)-}y(X6rGkp<{AkiPM7rFgAV^)FUX8XmCKKaWlS4;MSEagj$ z#pvH`vLX1q{&eOm>htnk4hmv=_)ao!MCp}9ql5yfre&Py!~hBAGNBa}PH&J8K=~<% z&?!J-QaH|0bq_uo6rt*r-M>d7jm1cbW^T>s)S?L{n8v`^?VIPA+qi^6e@cM|5boqEO!p1e|_{7U3Yl6K?0xMN1bbjf0@$TE-T))w> zFe?E?g$PUT-)AJ(PS^By^D^Ed!K5iv$*_eW~VA(I3~UMy*ZcgVu0$XZC*_0PgDmUL)qTCn927LD~p$yXR_GCJ&iQ; z4*`%l-dC5pALH!y*nmhdHRh02QjW1vZL4ySucz*w3f|#`=u@@YvMV1?i!&DIa2+S< z8z!gvN3FV4I;%fl;ruFeV{jKjI~?GlgkmGBuJ<7vY|l3xMOc?S@Q#C(zo*m&JLrjT2rU9PYOniB8O~yO5<1CCcQz# z17B2m1Z{R!Y)UO#CU-Y&mOlv4*Gz%rC_YkRcO)jTUEWHDvv!GWmEihE>OKPx1J?Av z8J{-#7NsT>>R#*7**=QL)1@IR77G9JGZZiVt!=jD+i(oRV;I`JkiTSZkAXuHm-VG1 z+2-LD!!2dNEk@1@Rp|C$MD9mH^)H*G*wI(i*Rc6Vvdik+BDycYQ*=0JA3dxxha|Zg zCIW1Ye-DdpMGTEwbA^6hVC<(@0FL4dkDOYcxxC5c%MJQ^)zpA%>>~Q|Y=@)XW!px; z_Fx+xOo7>sz4QX|Ef~igE+uFnzFWP<-#||*V0`0p7E*+n5+awuOWmvR{-M*chIXgo zYiZvQMond#{F8+4Zh_;>MsaZUuhp=onH@P!7W>sq|CWv|u}Wg0vo&f4UtmLzhCwwu zJaR=IO;sQxS}h(K>9VZjnED+>9rGgB3ks+AwTy_EYH{oc)mo`451n&YH%A1@WC{;1 z=fB6n zIYp46_&u`COM&Di?$P}pPAlAF*Ss<)2Xc?=@_2|EMO?(A1u!Vc=-%bDAP#zDiYQvJ z0}+}3GaLxsMIlh6?f=iRs0K=RyvMOcWl*xqe-IBLv?K{S^hP)@K|$I+h_)pdD9r~! zxhw2u66+F(E`&6hY}B_qe>wil|#*0R0B;<@E?L zVrhXKfwRg0l8r>LuNs1QqW&39ME0sOXe8zycivGVqUOjEWpU)h|9fwp@d(8=M-WxY zeazSz6x5e`k821fgylLIbdqx~Kdh^Oj`Q!4vc*Km)^Tr-qRxPHozdvvU^#xNsKVr6aw8={70&S4y*5xeoF@Q^y596*09`XF56-N z1=Rm5?-An178o?$ix}y7gizQ9gEmGHF5AW+92DYaOcwEHnjAr~!vI>CK%h`E_tO8L Yte!%o?r4GTrVtxD61Ym!|5fq-1K$0e!T1w z1SC8j)_dObefzK9b=~*c&wBRW>;B{VGKiBofK!FMN5oJBE0V;;!kWUz!jc1W?5KdY zyZ3mCBHprpchz-9{ASiJJh&&h1|4rdw6wxD2+9= z#6#}Uq8&^1F3wgvGFoNDo?bIeEQXpcuAR0-+w$JWoK-@yUal1M&~W_O)r+Rx;{@hWH5n^oQWR36GMYBDDZyPK4L@WVjRrF+XlSzi4X4!_!U%Uujl6LHQ#|l(sUU%{ zefYd8jnVYP91K}Qn-OmmSLYFK1h~_}RPS~>+Xdz%dpvpJ{ll!IKX=JN99qowqslbO zV3DmqPZ}6>KB!9>jEObpi$u5oGPfO3O5!o3N2Mn`ozpje<}1I1H)m2rJDcB7AwXc6 z6j)tnPiql7#)r+b+p9?MVahp&=qJ^$oG+a^C*);FoJ!+V*^W+|2Olx5{*&$bXth)U zejc7mU6cBp?^Rj|dd{GL-0eHRTBi6_yJ&GLP5kIncv^z{?=0AVy^5{S8_n=rtua!J zFGY=A(yV^ZhB}1J_y(F`3QTu+zkHlw;1GiFeP&pw0N1k%NShHlO(4W+(!wy5phcg4 zA-|}(lE_1@@e6y`veg;v7m;q%(PFG&K3#}eRhJioXUU0jg_8{kn$;KVwf;zpL2X_( zC*_R#5*PaBaY73(x*oZ}oE#HPLJQRQ7brNK=v!lsu==lSG1(&q>F)`adBT~d*lMS| z%!%7(p~<7kWNmpZ5-N31*e=8`kih|g5lVrI%2wnLF-2D+G4k6@FrYsJ_80AJ}KMRi>) z-kIeHp{maorNWkF81v0FKgB==_6blyaF$5GaW)B!i4v*jNk6r)vU6?G$0pV8(Y+UK z5lgRVt%;N_gWp)^osv=h+^07UY6+$4^#t=M3>0i0`{`aEkFLL#a)93uXhYO+aKTtu zckg2T9S&GKNtZmdAS^8PzvDva-%-K&g9eqPXQ4$dM^inr@6Zl z{!Cq&C_+V;g*{>!0cZP}?ogDb$#ZS=n@NHE{>k@84lOkl&$Bt2NF)W%GClViJq14_ zQIfa^q+0aq){}CO8j%g%R9|;G0uJuND*HO$2i&U_uW_a5xJ33~(Vy?;%6_(2_Cuq1 zLhThN@xH7-BaNtkKTn^taQHrs$<<)euc6z(dhps>SM;^Wx=7;O&IfNVJq3wk4<1VS z-`*7W4DR_i^W4=dRh>AXi~J$K>`UqP>CKVVH&+T(ODhRJZO7DScU$F7D)di-%^8?O z6)Ux`zdrVOe1GNkPo0FgrrxSu1AGQkJe@pqu}8LkBDm+V!N_1l}`tjLW8${rgDLv3m@E*#zappt-Mm zSC<$o+6UO~w0C=(0$&*y**@nKe_Q{|eAuD!(0YL0_a{z%+sdfSyP={Nyd$re6Rzbp zvsgTY7~VflX0^Vf7qqomYZ_$ryrFVV2$sFyzw2r%Q8*uYDA+)iQdfKms_5(>!s#!( z!P5S(N0i9CKQKaqg(U%Gk#V3*?)lO6dLv`8KB~F<-%VhbtL8Rl>mEz+PN=qx&t*|= zQHV=qG)YKlPk4iCyWIUGjC?kpeA>hIBK*A?B0)rB=RqAal#D%1C9yVQwBcz${#Jb5 zR{TRmMrOrJsLc&6x9qDo@FJ^=do_Y?3oU0G^nV5_EU&+DS+VA7Tp{^TAF>yZbyM3c zf*1CqHY9T|aL_lyY7c)i!_MtGPA!sdy3|mrsKVj1mi&>dms@-ozSa}OZ?2I*tAndg z@S7er$t^d^-;!wLQbG60nWd@1pQVD7tw-G_B#OscoYyremiZ_hj8*sXqQdchuD^!R zpXGuSj5psk+jR>3rWu3^`17>j&*^9^rWbszP=Mf@5KIEj%b=z98v=Ymp%$FYt>%Ld zm8})EDbNOJu9n)gwhz_RS``#Ag)fr)3<*?(!9O~mTQWeh;8c;0@o=iBLQNqx3d_2#W7S9#FXzr6VXfs>4 z;QXw}-STvK9_-7H=uqgal2{GkbjVLN+=D5ddd)4^WvX;(NYA*X*(JxTdiUzqVJopd zQg#~psX4o<)cF>r=rxP`(Xsf<+HG-pf&7aFPL8z|-&B*P?Vmsu5d>Nlg^2$WRY!S@#`g2{81;(1w#o5HsvN}5pFZi});>|VK^kL{Zkx~wgn ztlZp;HW`H8(GdRfIwc~?#N6}o#h158ohI*GIsK%56I_9sf2k_K@4vD!l{(dX9E7PJ;w>$|Y;-VBJSO4@){07bo-89^LZ9g<<%;dOl zyIq{s8`8Ltp*GDwu(l_Z$6sA2nam$BM$Q~6TpZg)w2TtW?G5whV(lRwaf$6EU86is zBP9Rs&vS_~sk?Nn_b}^HkM8LiO@>J}=g(T4hLmvH@5Jj#2aHa~K)lD9VB0k>$V2BP zgh;(=y9Op(KQ=H5vj+%qs>?s4tYN~-Q|fyQePA)s?HrF~;l!+@t8VMzqUpqMLudFT z)=o~s!MM4XkgbetIsODwtQ=FF$IcIp&!pjh6Q6{tL+l*7GQ%8Wsg(tC#qU3oW$~n) zL=>XIxI}Hi7HS0F_mmi+(c%1HDuKiWm>|6Xa}nW7ei55ggru9)xjBvC#JcEIN*#cp zv*ACvr=HTC?dX9NNo9Yhulu_gX5Z~}QQ2&QZ&C77{(>Y3_ z6j5Z1Uc5FtPEpS_31HsgmSLHZijGb_p$WlRJ1p^_1!ZLP8kr6OtCEK7Qh267o$H>e zf<4cNGQRk{g5h$XfvTFQ@`qm@iju83-~}ebAYpZryARHVR$AEt3229U{y@Fp4 z-8FBBtGG&(hTyUdx5ZOfiz`c=<0F%+w|Fl=rWk{K7>70k04SN?RU(^mrKSeKDqA!K^Hsv8C?#ioj4@WUL zC*?{hTai6q0%_oBTqDHygp_Kl;({sAScYQIwMDM1U>{x0ww zve?_}E;DG?+|zsUrsph5X_G7l#Y~vqkq3@NNDabbw7|`eJBmn`Qrlr%?`va=mm$Mc{+FBbQbogAZ6{MuzT|P%QZZotd21eb1hfj|;GYAX&>bx#D5EB+=XMj2XJkpnyMUykaVo) zj3ZLqEl1&)Rturc8m@+uUuD^vaNaSxGwP4dq0-OSb~62lPv8E_K4usLvG{Qg zdR%z8dd2H!{JaT|X_bfm{##*W$YM;_J8Y8&Z)*ImOAf4+| zEyi)qK%Ld1bHuqD+}-WiCnjszDeC-%8g+8JRpG1bOc!xUGB?@?6f~FTrI%U#5R~YF z%t5(S2Q>?0`(XNHa8xKdTEZ~Z4SJOheit#ldfdg63}#W6j8kO;SjQD`vftxS+#x1B zYu|5szEvkyz|}|B3x|DNlyi$;+n+cW$Hu+?)=X1!sa%{H-^;oBO9XACZJ}wkQ!sTa zQ#J3h|HX{{&WwIG3h7d6aWktuJaO)ie6&=KJBoX@w(rBWfin`*a6OmCC5M0HzL(gv zY<*e4hmW>SWVhxk-`UGOAbD%Hk+uu<^7zJ_ytVXamfqCd0$g+W08>?QAB}Cv{b}eM z@X}ILg+uT%>-6`A25p@uhS3%;u>ccSq}8|H_^o&`nBT5S0y z;2H0I^(4MO*S+(4l$gULc4KSeKvidto5Nl0P|%9CqQ*ikY!w_GUlo}sb9HYB=L^oFpJ zfTQskXW!LFVnUo4(OHPDaZSf3zB|3{RGu1>ueE$(+dr?tT zp!SGlqDU8vu{5xLWSvj+j$arHglg54#Lx&TvuO3LIIU>hF9Uoj&=-b*Q?uYr`#V?xz?2 zhirZrv^eA{k%{hFh%9LYVXEYWd5#PuUd1QqaqB*J!CMXEM>fEB$@#1>mtB`Bfil}t zhhTIObqh5HRvT+4q_Do$Q*Jika?qV=Np-DtPkU z(KoXyWLfPwr@UY1)hBAvR3nCBZgd|CevTG?H~HqDF}dzy%2sd2`f{^CBbTk*^K~RO zN~O0+2EjAJlywF%SjgYz810l&G5AqzI<=Ber{912^PpSPRJl3dm8W@dKHL}7_@k3)Y!SXYkyxQy>Q4I2o zr`ev7fLF$1t96h|sH<-#*YzGD-b^3$_!#wsh(Yw;)b@udLz9mm`mFYh z1Zz24KIQJ(*_-E0(3&1InqG;U?wF)GYd>DFo(em`#|UaaYmkA9;GTX7b?0@C@QkTVpGD#mf$dQoRNV=n{^Zi_W*ps;3?^$s`0;ER7;==~OmQ~9 zS5P=FjxE5%|;xq6h4@!_h?@|aK&FYI2IT(OHXv2%1 zWEo-v!L7x^YT(xLVHlpJttcwaF@1Y;-S*q3CRa!g7xdzl|Jan>2#dI0`LKl!T1GMk zRKe4|bQO&ET}Z^Aiym*HII>cSxIzl|F~JEUGxz;+DB=8fxXhnBI4R12q6ews$lA`Jfi}r@A@-)6TOAUMNYFYJ zZ-Zd?lxFTyjN3mXnL!%#>Z%$0gJ4*9g;e;@zSmQ{eGGDaRRNM3s@6!;hYuVc=c+3B z=qzNNS~n^EsJU4aOGE|mdy={C^lPKEfPL-IJAsTpQsDgZ@~s+eHZYmp9yb=YW_4r?lqQaYZQ`nau){W`LY#P)>i zq^wHEuOYs#FlPZeMuT@Etb@~A6feCebq`miJE3w+gAL%bVF_s*5e*@)?xmKSo%I3? zLELHVdWia$}~s6 zr!^LfxSSB4Td&9iTXrzQpl5ZDo#SdmNr;23QsPHQ!x!UT9xtb!Ycz^JF8x)%cFOXK z^EXw%dRz_VD}7?RU^4{)1+xFO=z!EI8IUa3U*rag=1BpHX$Xi<__kSbS{y_xa*MJv z_`thq0Z^sPzjAk48ssDQj}!$N8Q$XC84(bU$t_Bm69Jf+C!h_}ep zwzpQj9sRA94<{x3{~z&ix-DwX;RAzka)4-#6ZHJqKh|SVuO|>Yrv+m30+!|sK<-|E z=)5E->#y<_1V|T1f%Af!ZYqXg}`O zI$qKOWdnclF`%_Z`WGOe{`A`l-#a?s=Q1a#@BOWmExH2;Wl`OB!B-%lq3nO{4=WO& z#k_x|N&(qzm*6S{G*|GCegF2N2ulC+(58z2DG~yUs}i8zvRf&$CJCaexJ6Xu!`qz( z)*v8*kAE#D0KCo*s{8^Rbg=`*E2MzeIt0|x55%n-gO&yX#$l=3W7-_~&(G8j1E(XB hw}tl`5K!1C(72%nnjQrp<7@!WCh47rWB+@R{{wClNUHz< diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ec991f9aa..0f80bbf51 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index fbd7c5158..1b6c78733 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,101 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,95 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index a9f778a7a..ac1b06f93 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -54,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -64,21 +64,6 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line @@ -86,7 +71,7 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell diff --git a/js/build.gradle b/js/build.gradle index e64499e01..1892f2286 100644 --- a/js/build.gradle +++ b/js/build.gradle @@ -14,25 +14,28 @@ * limitations under the License. */ -repositories { - gradlePluginPortal() -} - -configurations { - intTestOfflineRepo.extendsFrom compileOnly - intTestOfflineRepo2 -} +agProject { + withOfflineTestConfigurations() + configurePlugin( + 'org.asciidoctor.js.base', + 'Asciidoctor.js Base Plugin', + 'Base plugin for all Asciidoctor.js tasks & extensions. Provides the asciidoctorjs project extension', + 'org.asciidoctor.gradle.js.nodejs.core.AsciidoctorNodeJSBasePlugin', + ['asciidoctor.js'] + ) -generateModuleVersions { - basename = 'asciidoctorjs-extension' - propertyNames ~/^asciidoctorjs$/, ~/^asciidoctorjs\./ + configurePlugin( + 'org.asciidoctor.js.convert', + 'Asciidoctor.js General Purpose Document Conversion Plugin', + 'Provides asciidoctor task and convention using the asciidoctor.js erngine', + 'org.asciidoctor.gradle.js.nodejs.AsciidoctorNodeJSPlugin', + ['asciidoctor.js', 'html5', 'docbook'] + ) } ext { nodejsCacheDir = "${offlineRepoBinariesRoot}/nodejs" -} -ext { if (OS.windows) { nodejsExt = 'zip' if (System.getProperty('os.arch').contains('64')) { @@ -61,26 +64,21 @@ dependencies { cachedBinaries.add "nodejs:node:${project.ext.defaultNodeJsVersion}:${nodejsArch}@${nodejsExt}" } -configurePlugin 'org.asciidoctor.js.base', - 'Asciidoctor.js Base Plugin', - 'Base plugin for all Asciidoctor.js tasks & extensions. Provides the asciidoctorjs project extension.', - ['asciidoctor', 'asciidoctor.js'] - -configurePlugin 'org.asciidoctor.js.convert', - 'Asciidoctor.js General Purpose Document Conversion Plugin', - 'Provides asciidoctor task and conventions', - ['asciidoctor', 'asciidoctor.js', 'html5', 'docbook'] +generateModuleVersions { + basename = 'asciidoctorjs-extension' + propertyNames ~/^asciidoctorjs$/, ~/^asciidoctorjs\./ +} -test { +tasks.named('test', Test) { systemProperties ROOT_PROJECT_DIR: rootProject.projectDir.absolutePath } -intTest { +tasks.named('intTest', Test) { systemProperties TEST_PROJECTS_DIR: file('src/intTest/projects') } -// Saves testing time and bandwidth, but pointing the tests to the locally cached -// Nodejs. distribution -[tasks.test,tasks.intTest,tasks.gradleTest].each { - it.systemProperties 'org.ysb33r.gradle.nodejs.uri' : file(nodejsCacheDir).absoluteFile.toURI() -} \ No newline at end of file +tasks.withType(Test).configureEach { + // Save testing time and bandwidth, but pointing the tests to the locally cached Nodejs. distribution + systemProperties 'org.ysb33r.gradle.nodejs.uri': file(nodejsCacheDir).absoluteFile.toURI() +} + diff --git a/js/src/intTest/groovy/org/asciidoctor/gradle/js/nodejs/AsciidoctorTaskFunctionalSpec.groovy b/js/src/intTest/groovy/org/asciidoctor/gradle/js/nodejs/AsciidoctorTaskFunctionalSpec.groovy index 6e92150de..7d3fcbbd0 100644 --- a/js/src/intTest/groovy/org/asciidoctor/gradle/js/nodejs/AsciidoctorTaskFunctionalSpec.groovy +++ b/js/src/intTest/groovy/org/asciidoctor/gradle/js/nodejs/AsciidoctorTaskFunctionalSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,8 +43,8 @@ class AsciidoctorTaskFunctionalSpec extends FunctionalSpecification { getGroovyGradleRunner(DEFAULT_ARGS).build() then: - new File(testProjectDir.root, 'build/docs/asciidoc/sample.html').exists() - new File(testProjectDir.root, 'build/docs/asciidoc/subdir/sample2.html').exists() + new File(projectDir, 'build/docs/asciidoc/sample.html').exists() + new File(projectDir, 'build/docs/asciidoc/subdir/sample2.html').exists() } @Unroll @@ -76,12 +76,12 @@ class AsciidoctorTaskFunctionalSpec extends FunctionalSpecification { then: 'content is generated as HTML and XML' verifyAll { - new File(testProjectDir.root, 'build/docs/asciidoc/html5/sample.html').exists() - new File(testProjectDir.root, 'build/docs/asciidoc/html5/subdir/sample2.html').exists() - new File(testProjectDir.root, 'build/docs/asciidoc/docbook/sample.xml').exists() - !new File(testProjectDir.root, 'build/docs/asciidoc/docinfo/docinfo.xml').exists() - !new File(testProjectDir.root, 'build/docs/asciidoc/docinfo/sample-docinfo.xml').exists() - !new File(testProjectDir.root, 'build/docs/asciidoc/html5/subdir/_include.html').exists() + new File(projectDir, 'build/docs/asciidoc/html5/sample.html').exists() + new File(projectDir, 'build/docs/asciidoc/html5/subdir/sample2.html').exists() + new File(projectDir, 'build/docs/asciidoc/docbook/sample.xml').exists() + !new File(projectDir, 'build/docs/asciidoc/docinfo/docinfo.xml').exists() + !new File(projectDir, 'build/docs/asciidoc/docinfo/sample-docinfo.xml').exists() + !new File(projectDir, 'build/docs/asciidoc/html5/subdir/_include.html').exists() } where: @@ -115,7 +115,7 @@ class AsciidoctorTaskFunctionalSpec extends FunctionalSpecification { then: 'content is generated as XML' verifyAll { - new File(testProjectDir.root, 'build/docs/asciidoc/sample.xml').exists() + new File(projectDir, 'build/docs/asciidoc/sample.xml').exists() } where: @@ -124,8 +124,8 @@ class AsciidoctorTaskFunctionalSpec extends FunctionalSpecification { File getBuildFile(String extraContent) { getJsConvertGroovyBuildFile(""" - -${extraContent} -""") + + ${extraContent} + """.stripIndent()) } } \ No newline at end of file diff --git a/js/src/intTest/groovy/org/asciidoctor/gradle/js/nodejs/internal/FunctionalSpecification.groovy b/js/src/intTest/groovy/org/asciidoctor/gradle/js/nodejs/internal/FunctionalSpecification.groovy index 3a3ab0589..6758c0c7f 100644 --- a/js/src/intTest/groovy/org/asciidoctor/gradle/js/nodejs/internal/FunctionalSpecification.groovy +++ b/js/src/intTest/groovy/org/asciidoctor/gradle/js/nodejs/internal/FunctionalSpecification.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,57 +15,35 @@ */ package org.asciidoctor.gradle.js.nodejs.internal -import groovy.transform.CompileStatic import org.apache.commons.io.FileUtils import org.asciidoctor.gradle.testfixtures.DslType -import org.asciidoctor.gradle.testfixtures.FunctionalTestSetup -import org.gradle.testkit.runner.GradleRunner -import org.junit.Rule -import org.junit.rules.TemporaryFolder +import org.asciidoctor.gradle.testfixtures.FunctionalTestFixture import spock.lang.Specification +import spock.lang.TempDir import static org.asciidoctor.gradle.testfixtures.DslType.GROOVY_DSL import static org.asciidoctor.gradle.testfixtures.DslType.KOTLIN_DSL -import static org.asciidoctor.gradle.testfixtures.FunctionalTestSetup.getOfflineRepositoriesGroovyDsl -import static org.asciidoctor.gradle.testfixtures.FunctionalTestSetup.getOfflineRepositoriesKotlinDsl -class FunctionalSpecification extends Specification { +class FunctionalSpecification extends Specification implements FunctionalTestFixture { public static final String TEST_PROJECTS_DIR = System.getProperty( - 'TEST_PROJECTS_DIR', - './asciidoctor-gradle-js/src/intTest/projects' + 'TEST_PROJECTS_DIR', + './asciidoctor-gradle-js/src/intTest/projects' ) - public static final String TEST_REPO_DIR = System.getProperty( - 'OFFLINE_REPO', - './testfixtures/offline-repo/build/repo' - ) - - @Rule - TemporaryFolder testProjectDir - @CompileStatic - GradleRunner getGroovyGradleRunner(List taskNames = ['asciidoctor']) { - FunctionalTestSetup.getGradleRunner(GROOVY_DSL, testProjectDir.root, taskNames) - } + @TempDir + File testProjectDir - @CompileStatic - GradleRunner getKotlinGradleRunner(List taskNames = ['asciidoctor']) { - FunctionalTestSetup.getGradleRunner(KOTLIN_DSL, testProjectDir.root, taskNames) + void setup() { + initializeProjectLayout() } @SuppressWarnings(['FactoryMethodName', 'BuilderMethodWithSideEffects']) void createTestProject(String docGroup = 'normal') { - FileUtils.copyDirectory(new File(TEST_PROJECTS_DIR, docGroup), testProjectDir.root) - } - - @CompileStatic - String getOfflineRepositories(DslType dslType = GROOVY_DSL) { - dslType == GROOVY_DSL ? getOfflineRepositoriesGroovyDsl(new File(TEST_REPO_DIR)) : - getOfflineRepositoriesKotlinDsl(new File(TEST_REPO_DIR)) + FileUtils.copyDirectory(new File(TEST_PROJECTS_DIR, docGroup), projectDir) } File getJsConvertGroovyBuildFile(String extraContent, String plugin = 'org.asciidoctor.js.convert') { - File buildFile = testProjectDir.newFile('build.gradle') buildFile << """ plugins { id '${plugin}' @@ -79,8 +57,7 @@ class FunctionalSpecification extends Specification { } File getJvmConvertKotlinBuildFile(String extraContent, String plugin = 'org.asciidoctor.js.convert') { - File buildFile = testProjectDir.newFile('build.gradle.kts') - buildFile << """ + buildFileKts << """ plugins { id ("${plugin}") } @@ -89,7 +66,7 @@ class FunctionalSpecification extends Specification { ${extraContent} """ - buildFile + buildFileKts } String getDefaultProcessModeForAppveyor(final DslType dslType = GROOVY_DSL) { diff --git a/js/src/main/groovy/org/asciidoctor/gradle/js/base/AbstractAsciidoctorJSExtension.groovy b/js/src/main/groovy/org/asciidoctor/gradle/js/base/AbstractAsciidoctorJSExtension.groovy index 051033f9d..ecdd928d4 100644 --- a/js/src/main/groovy/org/asciidoctor/gradle/js/base/AbstractAsciidoctorJSExtension.groovy +++ b/js/src/main/groovy/org/asciidoctor/gradle/js/base/AbstractAsciidoctorJSExtension.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,8 +23,7 @@ import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.artifacts.Configuration -import static org.ysb33r.grolifant.api.v4.ClosureUtils.configureItem -import static org.ysb33r.grolifant.api.v4.StringUtils.stringize +import static org.ysb33r.grolifant.api.core.ClosureUtils.configureItem /** * @author Schalk W. Cronjé @@ -51,7 +50,7 @@ abstract class AbstractAsciidoctorJSExtension extends AbstractImplementationEngi /** Set a new version to use. * * @param v New version to be used. Can be of anything that be resolved by - * {@link org.ysb33r.grolifant.api.v4.StringUtils.stringize}. + * {@link org.ysb33r.grolifant.api.core.StringTools#stringize}. */ void setVersion(Object v) { this.version = v @@ -141,4 +140,7 @@ abstract class AbstractAsciidoctorJSExtension extends AbstractImplementationEngi task ? (AbstractAsciidoctorJSExtension) projectExtension : this } + private String stringize(Object stringy) { + projectOperations.stringTools.stringize(stringy) + } } diff --git a/js/src/main/groovy/org/asciidoctor/gradle/js/base/AbstractAsciidoctorTask.groovy b/js/src/main/groovy/org/asciidoctor/gradle/js/base/AbstractAsciidoctorTask.groovy index f8ddc5242..22582f619 100644 --- a/js/src/main/groovy/org/asciidoctor/gradle/js/base/AbstractAsciidoctorTask.groovy +++ b/js/src/main/groovy/org/asciidoctor/gradle/js/base/AbstractAsciidoctorTask.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/js/src/main/groovy/org/asciidoctor/gradle/js/base/AsciidoctorJSModules.groovy b/js/src/main/groovy/org/asciidoctor/gradle/js/base/AsciidoctorJSModules.groovy index 22a4daf91..c99dd8adb 100644 --- a/js/src/main/groovy/org/asciidoctor/gradle/js/base/AsciidoctorJSModules.groovy +++ b/js/src/main/groovy/org/asciidoctor/gradle/js/base/AsciidoctorJSModules.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,10 @@ package org.asciidoctor.gradle.js.base import groovy.transform.CompileStatic import org.asciidoctor.gradle.base.AsciidoctorModuleDefinition +import org.gradle.api.Action -/** Define versions for standard AsciidoctorJS modules. +/** + * Define versions for standard AsciidoctorJS modules. * * @author Schalk W. Cronjé * @@ -28,19 +30,29 @@ import org.asciidoctor.gradle.base.AsciidoctorModuleDefinition @CompileStatic interface AsciidoctorJSModules { - /** Configure docbook via closure. + /** + * Configure docbook via closure. * * @param cfg Configurating closure */ void docbook(@DelegatesTo(AsciidoctorModuleDefinition) Closure cfg) - /** The Docbook module + /** + * Configure docbook via an action. * - * @return Acess to the Docbook module. Never {@code null}. + * @param cfg Configurator + */ + void docbook(Action cfg) + + /** + * The Docbook module + * + * @return Access to the Docbook module. Never {@code null}. */ AsciidoctorModuleDefinition getDocbook() - /** For the module that are configured in both module sets, + /** + * For the module that are configured in both module sets, * compare to see if the versions are the same * * @param other Other module set to compare. @@ -49,7 +61,8 @@ interface AsciidoctorJSModules { */ boolean isSetVersionsDifferentTo(AsciidoctorJSModules other) - /** Returns a module by name + /** + * Returns a module by name * * @param name Name of module * @return Module* @throws {@link org.asciidoctor.gradle.base.ModuleNotFoundException} when the module diff --git a/js/src/main/groovy/org/asciidoctor/gradle/js/base/internal/AsciidoctorJSModule.groovy b/js/src/main/groovy/org/asciidoctor/gradle/js/base/internal/AsciidoctorJSModule.groovy index 7a3e7c971..5c721e60c 100644 --- a/js/src/main/groovy/org/asciidoctor/gradle/js/base/internal/AsciidoctorJSModule.groovy +++ b/js/src/main/groovy/org/asciidoctor/gradle/js/base/internal/AsciidoctorJSModule.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,34 +17,37 @@ package org.asciidoctor.gradle.js.base.internal import org.asciidoctor.gradle.base.AsciidoctorModuleDefinition import org.gradle.api.Action +import org.ysb33r.grolifant.api.core.ProjectOperations -import static org.ysb33r.grolifant.api.v4.StringUtils.stringize +import java.util.concurrent.Callable -/** A single configurable asciidoctor.js module. +/** + * A single configurable asciidoctor.js module. * * @author Schalk W. Cronjé * @since 3.0.0 */ -class AsciidoctorJSModule implements AsciidoctorModuleDefinition { +class AsciidoctorJSModule implements AsciidoctorModuleDefinition, VersionUpdateTrigger { final String name private Optional version = Optional.empty() private final Object defaultVersion private final Action setAction + private final ProjectOperations projectOperations + private Callable updateTrigger - static AsciidoctorJSModule of(final String name, final Object defaultVersion) { - new AsciidoctorJSModule(name, defaultVersion) + static AsciidoctorJSModule of(ProjectOperations po, final String name, final Object defaultVersion) { + new AsciidoctorJSModule(po, name, defaultVersion) } - static AsciidoctorJSModule of(final String name, final Object defaultVersion, Closure setAction) { - new AsciidoctorJSModule(name, defaultVersion, setAction as Action) - } - - AsciidoctorJSModule(final String name, final Object defaultVersion, Action setAction = null) { - this.name = name - this.defaultVersion = defaultVersion - this.setAction = setAction + static AsciidoctorJSModule of( + ProjectOperations po, + final String name, + final Object defaultVersion, + Closure setAction + ) { + new AsciidoctorJSModule(po, name, defaultVersion, setAction as Action) } @Override @@ -58,6 +61,9 @@ class AsciidoctorJSModule implements AsciidoctorModuleDefinition { if (setAction) { setAction.execute(o) } + if (updateTrigger) { + updateTrigger.call() + } } @Override @@ -68,7 +74,7 @@ class AsciidoctorJSModule implements AsciidoctorModuleDefinition { @Override String getVersion() { - this.version.present ? stringize(this.version.get()) : null + this.version.present ? projectOperations.stringTools.stringize(this.version.get()) : null } /** Whether the component has been allocated a version. @@ -79,4 +85,22 @@ class AsciidoctorJSModule implements AsciidoctorModuleDefinition { boolean isDefined() { version.present } + + @Override + void setUpdateAction(Callable callable) { + this.updateTrigger = callable + } + + protected AsciidoctorJSModule( + final ProjectOperations projectOperations1, + final String name, + final Object defaultVersion, + Action setAction = null + ) { + this.name = name + this.defaultVersion = defaultVersion + this.setAction = setAction + this.projectOperations = projectOperations1 + } + } \ No newline at end of file diff --git a/js/src/main/groovy/org/asciidoctor/gradle/js/base/internal/BaseAsciidoctorJSModules.groovy b/js/src/main/groovy/org/asciidoctor/gradle/js/base/internal/BaseAsciidoctorJSModules.groovy index 524c49c12..905d97501 100644 --- a/js/src/main/groovy/org/asciidoctor/gradle/js/base/internal/BaseAsciidoctorJSModules.groovy +++ b/js/src/main/groovy/org/asciidoctor/gradle/js/base/internal/BaseAsciidoctorJSModules.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,9 +19,10 @@ import groovy.transform.CompileStatic import org.asciidoctor.gradle.base.AsciidoctorModuleDefinition import org.asciidoctor.gradle.base.ModuleNotFoundException import org.asciidoctor.gradle.js.base.AsciidoctorJSModules +import org.gradle.api.Action +import org.ysb33r.grolifant.api.core.ProjectOperations -import static org.ysb33r.grolifant.api.v4.ClosureUtils.configureItem -import static org.ysb33r.grolifant.api.v4.StringUtils.stringize +import static org.ysb33r.grolifant.api.core.ClosureUtils.configureItem /** Define versions for standard AsciidoctorJS modules. * @@ -32,22 +33,35 @@ import static org.ysb33r.grolifant.api.v4.StringUtils.stringize @SuppressWarnings(['ConfusingMethodName', 'ClassName']) @CompileStatic class BaseAsciidoctorJSModules implements AsciidoctorJSModules { + private final ProjectOperations projectOperations private final AsciidoctorModuleDefinition docbook private final Map index = new TreeMap() + private Action updater - /** Creates a module definition that is attached to a specific asciidoctorjs + /** + * Creates a module definition that is attached to a specific asciidoctorjs * extension. * * @param asciidoctorjs Extension that this module is attached to. + * + * @since 4.0 */ + @SuppressWarnings('UnnecessarySetter') BaseAsciidoctorJSModules( - AsciidoctorModuleDefinition docbook + ProjectOperations projectOperations, + AsciidoctorModuleDefinition docbook ) { + this.projectOperations = projectOperations this.docbook = docbook index.put(docbook.name, docbook) + + if (docbook instanceof VersionUpdateTrigger) { + docbook.setUpdateAction { owner.updateNow() } + } } - /** Configure docbook via closure. + /** + * Configure docbook via closure. * * @param cfg Configurating closure */ @@ -56,16 +70,28 @@ class BaseAsciidoctorJSModules implements AsciidoctorJSModules { configureItem(this.docbook, cfg) } - /** The Docbook module + /** + * Configure docbook via an action. + * + * @param cfg Configurator + */ + @Override + void docbook(Action cfg) { + cfg.execute(this.docbook) + } + + /** + * The Docbook module * - * @return Acess to the Docbook module. Never {@code null}. + * @return Access to the Docbook module. Never {@code null}. */ @Override AsciidoctorModuleDefinition getDocbook() { this.docbook } - /** For the module that are configured in both module sets, + /** + * For the modules that are configured in both module sets, * compare to see if the versions are the same * * @param other Other module set to compare. @@ -75,7 +101,8 @@ class BaseAsciidoctorJSModules implements AsciidoctorJSModules { different(docbook, other.docbook) } - /** Returns a module by name + /** + * Returns a module by name * * @param name Name of module * @throws {@link org.asciidoctor.gradle.base.ModuleNotFoundException} when the module is not registered. @@ -90,7 +117,21 @@ class BaseAsciidoctorJSModules implements AsciidoctorJSModules { } } + void onUpdate(Action callback) { + this.updater = callback + } + + protected void updateNow() { + if (updater) { + updater.execute(this) + } + } + private boolean different(AsciidoctorModuleDefinition lhs, AsciidoctorModuleDefinition rhs) { lhs.version != null && rhs.version != null && stringize(lhs.version) != stringize(rhs.version) } + + private String stringize(Object stringy) { + projectOperations.stringTools.stringize(stringy) + } } diff --git a/js/src/main/groovy/org/asciidoctor/gradle/js/base/internal/VersionUpdateTrigger.java b/js/src/main/groovy/org/asciidoctor/gradle/js/base/internal/VersionUpdateTrigger.java new file mode 100644 index 000000000..f7fc48d53 --- /dev/null +++ b/js/src/main/groovy/org/asciidoctor/gradle/js/base/internal/VersionUpdateTrigger.java @@ -0,0 +1,32 @@ +/* + * Copyright 2013-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://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.asciidoctor.gradle.js.base.internal; + +import java.util.concurrent.Callable; + +/** + * Allows changes to execute a callback + * + * @since 4.0 + */ +public interface VersionUpdateTrigger { + + /** + * An action to be executed when versions are changed. + * @param callable A callback. + */ + void setUpdateAction(Callable callable); +} diff --git a/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/AbstractAsciidoctorNodeJSTask.groovy b/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/AbstractAsciidoctorNodeJSTask.groovy index 45e59e71f..9795ec616 100644 --- a/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/AbstractAsciidoctorNodeJSTask.groovy +++ b/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/AbstractAsciidoctorNodeJSTask.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,17 +20,17 @@ import groovy.transform.CompileStatic import org.asciidoctor.gradle.base.AsciidoctorAttributeProvider import org.asciidoctor.gradle.base.internal.Workspace import org.asciidoctor.gradle.js.base.AbstractAsciidoctorTask +import org.asciidoctor.gradle.js.nodejs.core.AsciidoctorJSNodeExtension +import org.asciidoctor.gradle.js.nodejs.core.AsciidoctorJSNpmExtension import org.asciidoctor.gradle.js.nodejs.internal.AsciidoctorJSRunner import org.gradle.api.artifacts.Configuration import org.gradle.api.tasks.Input import org.gradle.api.tasks.Internal import org.gradle.api.tasks.TaskAction import org.gradle.workers.WorkerExecutor -import org.ysb33r.gradle.nodejs.NodeJSExtension -import org.ysb33r.gradle.nodejs.NpmExtension -import org.ysb33r.grolifant.api.v4.MapUtils import static org.asciidoctor.gradle.base.internal.AsciidoctorAttributes.resolveAsCacheable +import static org.asciidoctor.gradle.js.nodejs.core.AsciidoctorNodeJSBasePlugin.NPM_EXTENSION_NAME import static org.asciidoctor.gradle.js.nodejs.core.NodeJSUtils.initPackageJson /** Base class for all Asciidoctor tasks using Asciidoctor.js as rendering engine. @@ -44,8 +44,11 @@ class AbstractAsciidoctorNodeJSTask extends AbstractAsciidoctorTask { private final WorkerExecutor worker private final AsciidoctorJSExtension asciidoctorjs - private final NodeJSExtension nodejs - private final NpmExtension npm + private final AsciidoctorJSNodeExtension nodejs + private final AsciidoctorJSNpmExtension npm + private final String projectAlias + + final String engineName = 'Asciidoctor.js' /** Returns all of the Asciidoctor options. * @@ -118,20 +121,15 @@ class AbstractAsciidoctorNodeJSTask extends AbstractAsciidoctorTask { @SuppressWarnings('ThisReferenceEscapesConstructor') protected AbstractAsciidoctorNodeJSTask(WorkerExecutor we) { this.worker = we + this.nodejs = this.extensions.create(AsciidoctorJSNodeExtension.NAME, AsciidoctorJSNodeExtension, this) + this.npm = this.extensions.create(NPM_EXTENSION_NAME, AsciidoctorJSNpmExtension, this) this.asciidoctorjs = this.extensions.create(AsciidoctorJSExtension.NAME, AsciidoctorJSExtension, this) - this.nodejs = project.extensions.getByType(AsciidoctorJSNodeExtension) - this.npm = project.extensions.getByType(AsciidoctorJSNpmExtension) - } - - @Override - @Internal - protected String getEngineName() { - 'Asciidoctor.js' + this.projectAlias = "${project.name}-${name}" } @CompileDynamic private Map prepareAttributesForSerialisation(final File workingSourceDir, Optional lang) { - MapUtils.stringizeValues(prepareAttributes( + projectOperations.stringTools.stringizeValues(prepareAttributes( workingSourceDir, asciidoctorjs.attributes, lang.present ? asciidoctorjs.getAttributesForLang(lang.get()) : [:], @@ -148,8 +146,8 @@ class AbstractAsciidoctorNodeJSTask extends AbstractAsciidoctorTask { Optional lang ) { new AsciidoctorJSRunner( - nodejs.resolvableNodeExecutable.executable, - project, + nodejs.executable.get(), + projectOperations, asciidoctorjsExe, backend, asciidoctorjs.safeMode, @@ -163,18 +161,17 @@ class AbstractAsciidoctorNodeJSTask extends AbstractAsciidoctorTask { } private AsciidoctorJSRunner.FileLocations resolveAsciidoctorjsEnvironment() { - File home = asciidoctorjs.toolingWorkDir + File home = asciidoctorjs.toolingWorkDir.get() initPackageJson( home, - "${project.name}-${name}", - project, + projectAlias, + projectOperations, nodejs, npm ) - asciidoctorjs.configuration.resolve() new AsciidoctorJSRunner.FileLocations( - executable: new File(home, 'node_modules/asciidoctor/bin/asciidoctor'), + executable: new File(home, 'node_modules/@asciidoctor/cli/bin/asciidoctor'), workingDir: home ) } diff --git a/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/AsciidoctorJSExtension.groovy b/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/AsciidoctorJSExtension.groovy index 70097d92d..184d2a64e 100644 --- a/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/AsciidoctorJSExtension.groovy +++ b/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/AsciidoctorJSExtension.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,17 +19,19 @@ import groovy.transform.CompileStatic import org.asciidoctor.gradle.base.AsciidoctorModuleDefinition import org.asciidoctor.gradle.js.base.AbstractAsciidoctorJSExtension import org.asciidoctor.gradle.js.base.AsciidoctorJSModules +import org.asciidoctor.gradle.js.nodejs.core.AsciidoctorJSNodeExtension +import org.asciidoctor.gradle.js.nodejs.core.AsciidoctorJSNpmExtension import org.asciidoctor.gradle.js.nodejs.core.NodeJSDependencyFactory import org.asciidoctor.gradle.js.nodejs.internal.AsciidoctorNodeJSModules import org.asciidoctor.gradle.js.nodejs.internal.PackageDescriptor import org.gradle.api.Project -import org.gradle.api.Task import org.gradle.api.artifacts.Configuration -import org.gradle.api.artifacts.ConfigurationContainer -import org.gradle.api.artifacts.Dependency -import org.gradle.api.artifacts.SelfResolvingDependency -import org.gradle.api.artifacts.dsl.DependencyHandler +import org.gradle.api.artifacts.ResolutionStrategy +import org.gradle.api.provider.Provider import org.ysb33r.gradle.nodejs.NpmDependency +import org.ysb33r.gradle.nodejs.dependencies.npm.BaseNpmSelfResolvingDependency + +import static org.asciidoctor.gradle.js.nodejs.internal.AsciidoctorNodeJSModules.SCOPE_ASCIIDOCTOR /** Extension for configuring AsciidoctorJS. * @@ -42,21 +44,17 @@ import org.ysb33r.gradle.nodejs.NpmDependency @CompileStatic class AsciidoctorJSExtension extends AbstractAsciidoctorJSExtension { public final static String NAME = 'asciidoctorjs' - public final static PackageDescriptor PACKAGE_ASCIIDOCTOR = PackageDescriptor.of('asciidoctor') + public final static PackageDescriptor PACKAGE_ASCIIDOCTOR = PackageDescriptor.of(SCOPE_ASCIIDOCTOR, 'cli') + private final static String CONFIGURATION_NAME = "__\$\$${NAME}\$\$__" private final List additionalRequires = [] private final String projectName private boolean onlyTaskRequires = false private final AsciidoctorJSNodeExtension nodejs private final AsciidoctorJSNpmExtension npm - - @Deprecated - // We need to find a better solution than the curretn detached configuration usage. - private final ConfigurationContainer configurations - - @Deprecated - // We need to find a better way than how we are creasting dependencies atm. - private final DependencyHandler dependencies + private final NodeJSDependencyFactory dependencyFactory + private final Configuration publicConfiguration + private final Configuration privateConfiguration /** Attach extension to project. * @@ -64,24 +62,64 @@ class AsciidoctorJSExtension extends AbstractAsciidoctorJSExtension { */ AsciidoctorJSExtension(Project project) { super(project) - this.configurations = project.configurations - this.dependencies = project.dependencies + + String privateName = "${CONFIGURATION_NAME}_d" + String publicName = "${CONFIGURATION_NAME}_r" + projectOperations.configurations.createLocalRoleFocusedConfiguration(privateName, publicName) + this.privateConfiguration = project.configurations.getByName(privateName) + this.publicConfiguration = project.configurations.getByName(publicName) this.projectName = project.name this.nodejs = project.extensions.getByType(AsciidoctorJSNodeExtension) this.npm = project.extensions.getByType(AsciidoctorJSNpmExtension) + this.dependencyFactory = new NodeJSDependencyFactory( + projectOperations, + this.nodejs, + this.npm + ) + loadDependencies() } /** Attach extension to a task. * * @param task Task to attach to */ - AsciidoctorJSExtension(Task task) { + AsciidoctorJSExtension(AbstractAsciidoctorNodeJSTask task) { super(task, NAME) - this.configurations = task.project.configurations - this.dependencies = task.project.dependencies + this.publicConfiguration = task.project.configurations.create("__\$\$${NAME}_${task.name}\$\$__") + + String privateName = "__\$\$${NAME}_${task.name}\$\$__d" + String publicName = "__\$\$${NAME}_${task.name}\$\$__r" + projectOperations.configurations.createLocalRoleFocusedConfiguration(privateName, publicName) + this.privateConfiguration = task.project.configurations.getByName(privateName) + this.publicConfiguration = task.project.configurations.getByName(publicName) this.projectName = task.project.name - this.nodejs = task.project.extensions.getByType(AsciidoctorJSNodeExtension) - this.npm = task.project.extensions.getByType(AsciidoctorJSNpmExtension) + this.nodejs = task.extensions.getByType(AsciidoctorJSNodeExtension) + this.npm = task.extensions.getByType(AsciidoctorJSNpmExtension) + this.dependencyFactory = new NodeJSDependencyFactory( + projectOperations, + this.nodejs, + this.npm + ) + + Configuration projectConfiguration = task.project.configurations.findByName("${CONFIGURATION_NAME}_d") + if (projectConfiguration) { + this.publicConfiguration.extendsFrom(projectConfiguration) + } + + npm.homeDirectory = projectOperations.provider { -> + if (versionsDifferFromGlobal()) { + new File(npm.projectExtension.homeDirectory.parentFile, "${projectName}-${task.name}") + } else { + npm.projectExtension.homeDirectory + } + } + } + + @SuppressWarnings('UnnecessarySetter') + @Override + void setVersion(Object v) { + super.setVersion(v) + setResolutionStrategy() } /** Adds an additional NPM package that is required @@ -119,7 +157,7 @@ class AsciidoctorJSExtension extends AbstractAsciidoctorJSExtension { * @Return Set of strings in a format suitable for {@code npm --require}. */ Set getRequires() { - Set reqs = [].toSet() + Set reqs = (Set) [].toSet() final String docbook = moduleVersion(modules.docbook) if (docbook) { @@ -129,25 +167,15 @@ class AsciidoctorJSExtension extends AbstractAsciidoctorJSExtension { reqs } - /** A configuration containing all required NPM packages. + /** + * A configuration containing all required NPM packages. * The configuration will differ between global state and task state if the task has set * different requires or different docbook versions. * * @return All resolvable NPM dependencies including {@code asciidoctor.js}. */ - @SuppressWarnings('UnnecessaryGetter') Configuration getConfiguration() { - final String docbook = moduleVersion(modules.docbook) - final NodeJSDependencyFactory factory = new NodeJSDependencyFactory(project, nodejs, npm) - final List deps = [factory.createDependency(PACKAGE_ASCIIDOCTOR, getVersion())] - - if (docbook) { - deps.add(factory.createDependency(packageDescriptorFor(modules.docbook), docbook)) - } - - configurations.detachedConfiguration( - deps.toArray() as Dependency[] - ) + this.publicConfiguration } /** The suggested working directory that the implementation tool should use. @@ -159,12 +187,8 @@ class AsciidoctorJSExtension extends AbstractAsciidoctorJSExtension { * * @return Suggested working directory. */ - File getToolingWorkDir() { - if (versionsDifferFromGlobal()) { - new File(npm.homeDirectory.parentFile, "${projectName}-${task.name}") - } else { - npm.homeDirectory - } + Provider getToolingWorkDir() { + npm.homeDirectoryProvider } /** Creates a new modules block. @@ -173,7 +197,12 @@ class AsciidoctorJSExtension extends AbstractAsciidoctorJSExtension { */ @Override protected AsciidoctorJSModules createModulesConfiguration() { - new AsciidoctorNodeJSModules(this, defaultVersionMap) + final newModules = new AsciidoctorNodeJSModules(projectOperations, defaultVersionMap) + newModules.onUpdate { + owner.loadDependencies() + owner.setResolutionStrategy() + } + newModules } private List getAllAdditionalRequires() { @@ -218,4 +247,40 @@ class AsciidoctorJSExtension extends AbstractAsciidoctorJSExtension { private AsciidoctorJSExtension getExtFromProject() { task ? (AsciidoctorJSExtension) projectExtension : this } + + private void loadDependencies() { + this.privateConfiguration.dependencies.add( + dependencyFactory.createDependency(PACKAGE_ASCIIDOCTOR, version) + ) + + if (moduleVersion(modules.docbook)) { + this.privateConfiguration.dependencies.add( + dependencyFactory.createDependency( + packageDescriptorFor(modules.docbook), + moduleVersion(modules.docbook) + ) + ) + } + } + + private void setResolutionStrategy() { + final String docbook = moduleVersion(modules.docbook) + this.publicConfiguration.resolutionStrategy { ResolutionStrategy rs -> + rs.eachDependency { drd -> + if (drd.requested.group.startsWith(BaseNpmSelfResolvingDependency.NPM_MAVEN_GROUP_PREFIX)) { + if (drd.requested.group.endsWith(PACKAGE_ASCIIDOCTOR.scope) && + drd.requested.name == PACKAGE_ASCIIDOCTOR.name) { + drd.useVersion(version) + } + if (docbook) { + final pd = packageDescriptorFor(modules.docbook) + if (drd.requested.group.endsWith(pd.scope) && + drd.requested.name == pd.name) { + drd.useVersion(docbook) + } + } + } + } + } + } } diff --git a/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/AsciidoctorNodeJSPlugin.groovy b/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/AsciidoctorNodeJSPlugin.groovy index b1aa3a5ad..2792eb6cb 100644 --- a/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/AsciidoctorNodeJSPlugin.groovy +++ b/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/AsciidoctorNodeJSPlugin.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,10 +16,10 @@ package org.asciidoctor.gradle.js.nodejs import groovy.transform.CompileStatic +import org.asciidoctor.gradle.js.nodejs.core.AsciidoctorNodeJSBasePlugin import org.gradle.api.Action import org.gradle.api.Plugin import org.gradle.api.Project -import org.ysb33r.grolifant.api.v4.TaskProvider /** Adds a task called asciidoctor. * @@ -29,24 +29,21 @@ import org.ysb33r.grolifant.api.v4.TaskProvider class AsciidoctorNodeJSPlugin implements Plugin { @Override void apply(Project project) { - project.with { - apply plugin: AsciidoctorNodeJSBasePlugin + project.pluginManager.apply(AsciidoctorNodeJSBasePlugin) - Action asciidoctorDefaults = new Action() { - @Override - void execute(AsciidoctorTask asciidoctorTask) { - asciidoctorTask.with { - group = TASK_GROUP - description = 'Generic task to convert AsciiDoc files and copy related resources' - } + Action asciidoctorDefaults = new Action() { + @Override + void execute(AsciidoctorTask asciidoctorTask) { + asciidoctorTask.with { + group = TASK_GROUP + description = 'Generic task to convert AsciiDoc files and copy related resources' } } + } - TaskProvider.registerTask( - project, + project.tasks.register( 'asciidoctor', AsciidoctorTask - ).configure((Action) asciidoctorDefaults) - } + ).configure((Action) asciidoctorDefaults) } } \ No newline at end of file diff --git a/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/AsciidoctorTask.groovy b/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/AsciidoctorTask.groovy index e9e1e2c1c..4768dbfca 100644 --- a/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/AsciidoctorTask.groovy +++ b/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/AsciidoctorTask.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,6 @@ import org.asciidoctor.gradle.base.OutputOptions import org.gradle.api.Action import org.gradle.api.tasks.CacheableTask import org.gradle.workers.WorkerExecutor -import org.ysb33r.grolifant.api.v4.FileUtils import javax.inject.Inject @@ -48,7 +47,7 @@ class AsciidoctorTask extends AbstractAsciidoctorNodeJSTask { } else { folderName = "asciidoc${name.capitalize()}" } - final String safeFolderName = FileUtils.toSafeFileName(folderName) + final String safeFolderName = projectOperations.fsOperations.toSafeFileName(folderName) setConvention(project, sourceDirProperty, project.layout.projectDirectory.dir("src/docs/${folderName}")) setConvention(outputDirProperty, project.layout.buildDirectory.dir("docs/${safeFolderName}")) } diff --git a/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/core/AsciidoctorJSNodeExtension.groovy b/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/core/AsciidoctorJSNodeExtension.groovy new file mode 100644 index 000000000..32eac5f59 --- /dev/null +++ b/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/core/AsciidoctorJSNodeExtension.groovy @@ -0,0 +1,52 @@ +/* + * Copyright 2013-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://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.asciidoctor.gradle.js.nodejs.core + +import groovy.transform.CompileStatic +import org.asciidoctor.gradle.js.nodejs.AbstractAsciidoctorNodeJSTask +import org.gradle.api.Project +import org.ysb33r.gradle.nodejs.BaseNodeJSExtension +import org.ysb33r.gradle.nodejs.NodeJSExecSpec + +/** + * An extension to configure Node.js. + * + * @since 3.0 + */ +@CompileStatic +class AsciidoctorJSNodeExtension extends BaseNodeJSExtension { + public final static String NAME = 'asciidoctorNodejs' + + AsciidoctorJSNodeExtension(Project project) { + super(project) + } + + AsciidoctorJSNodeExtension(AbstractAsciidoctorNodeJSTask task) { + super(task, task.project.extensions.getByType(AsciidoctorJSNodeExtension)) + } + + /** + * Create execution specification. + * + * @return Execution specification. + * + * @since 4.0 + */ + @Override + NodeJSExecSpec createExecSpec() { + new NodeJSExecSpec(projectOperations, executable) + } +} diff --git a/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/AsciidoctorJSNpmExtension.groovy b/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/core/AsciidoctorJSNpmExtension.groovy similarity index 50% rename from js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/AsciidoctorJSNpmExtension.groovy rename to js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/core/AsciidoctorJSNpmExtension.groovy index c5e897db4..361fb011a 100644 --- a/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/AsciidoctorJSNpmExtension.groovy +++ b/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/core/AsciidoctorJSNpmExtension.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,33 +13,36 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.asciidoctor.gradle.js.nodejs +package org.asciidoctor.gradle.js.nodejs.core import groovy.transform.CompileStatic +import org.asciidoctor.gradle.js.nodejs.AbstractAsciidoctorNodeJSTask import org.gradle.api.Project -import org.gradle.api.Task -import org.ysb33r.gradle.nodejs.NpmExtension +import org.ysb33r.gradle.nodejs.BaseNpmExtension -/** An extension to configure Npm. +/** + * An extension to configure Npm. * * @since 3.0 */ @CompileStatic -class AsciidoctorJSNpmExtension extends NpmExtension { - public final static String NAME = 'asciidoctorNpm' +class AsciidoctorJSNpmExtension extends BaseNpmExtension { AsciidoctorJSNpmExtension(Project project) { - super(project) - homeDirectory = { new File(project.buildDir, "/.npm/${project.name}") } + super(project, project.extensions.getByType(AsciidoctorJSNodeExtension)) + homeDirectory = projectOperations.buildDirDescendant(".asciidoctor-npm/${project.name}") } - AsciidoctorJSNpmExtension(Task task) { - super(task, NAME) + AsciidoctorJSNpmExtension(AbstractAsciidoctorNodeJSTask task) { + super( + task, + task.project.extensions.getByType(AsciidoctorJSNodeExtension), + task.project.extensions.getByType(AsciidoctorJSNpmExtension) + ) } @Override protected String getNodeJsExtensionName() { AsciidoctorJSNodeExtension.NAME } - } diff --git a/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/AsciidoctorNodeJSBasePlugin.groovy b/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/core/AsciidoctorNodeJSBasePlugin.groovy similarity index 54% rename from js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/AsciidoctorNodeJSBasePlugin.groovy rename to js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/core/AsciidoctorNodeJSBasePlugin.groovy index 78420367b..499977914 100644 --- a/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/AsciidoctorNodeJSBasePlugin.groovy +++ b/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/core/AsciidoctorNodeJSBasePlugin.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,24 +13,28 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.asciidoctor.gradle.js.nodejs +package org.asciidoctor.gradle.js.nodejs.core import groovy.transform.CompileStatic -import org.asciidoctor.gradle.js.nodejs.core.NodeJSBasePlugin +import org.asciidoctor.gradle.base.AsciidoctorBasePlugin +import org.asciidoctor.gradle.js.nodejs.AsciidoctorJSExtension import org.gradle.api.Plugin import org.gradle.api.Project -import org.ysb33r.grolifant.api.core.ProjectOperations -/** Base plugin for AsciidoctorJS implementations. - * +/** * @since 3.0 */ @CompileStatic class AsciidoctorNodeJSBasePlugin implements Plugin { + public final static String NPM_EXTENSION_NAME = 'asciidoctorNpm' + @Override void apply(Project project) { - ProjectOperations.maybeCreateExtension(project) - project.apply plugin: NodeJSBasePlugin - project.extensions.create( AsciidoctorJSExtension.NAME, AsciidoctorJSExtension, project ) + project.pluginManager.identity { + apply AsciidoctorBasePlugin + } + project.extensions.create(AsciidoctorJSNodeExtension.NAME, AsciidoctorJSNodeExtension, project) + project.extensions.create(NPM_EXTENSION_NAME, AsciidoctorJSNpmExtension, project) + project.extensions.create(AsciidoctorJSExtension.NAME, AsciidoctorJSExtension, project) } } diff --git a/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/core/NodeJSBasePlugin.groovy b/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/core/NodeJSBasePlugin.groovy deleted file mode 100644 index ed7d12c1e..000000000 --- a/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/core/NodeJSBasePlugin.groovy +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2013-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://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.asciidoctor.gradle.js.nodejs.core - -import groovy.transform.CompileStatic -import org.asciidoctor.gradle.js.nodejs.AsciidoctorJSNodeExtension -import org.asciidoctor.gradle.js.nodejs.AsciidoctorJSNpmExtension -import org.gradle.api.Plugin -import org.gradle.api.Project - -/** - * @since 3.0 - */ -@CompileStatic -class NodeJSBasePlugin implements Plugin { - @Override - void apply(Project project) { - project.extensions.create( AsciidoctorJSNodeExtension.NAME, AsciidoctorJSNodeExtension, project ) - project.extensions.create( AsciidoctorJSNpmExtension.NAME, AsciidoctorJSNpmExtension, project ) - } -} diff --git a/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/core/NodeJSDependencyFactory.groovy b/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/core/NodeJSDependencyFactory.groovy index 56ab928e6..a3e2da7ad 100644 --- a/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/core/NodeJSDependencyFactory.groovy +++ b/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/core/NodeJSDependencyFactory.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,13 @@ package org.asciidoctor.gradle.js.nodejs.core import groovy.transform.CompileStatic -import org.asciidoctor.gradle.js.nodejs.AsciidoctorJSNodeExtension -import org.asciidoctor.gradle.js.nodejs.AsciidoctorJSNpmExtension import org.asciidoctor.gradle.js.nodejs.internal.PackageDescriptor -import org.gradle.api.Project import org.gradle.api.artifacts.SelfResolvingDependency -import org.ysb33r.gradle.nodejs.dependencies.npm.NpmSelfResolvingDependency +import org.ysb33r.gradle.nodejs.dependencies.npm.BaseNpmSelfResolvingDependency +import org.ysb33r.grolifant.api.core.ProjectOperations -/** A factory class for creating NPM self resolving dependencies in a Gradle context. +/** + * A factory class for creating NPM self resolving dependencies in a Gradle context. * * @author Schalk W. Cronjé * @@ -31,22 +30,24 @@ import org.ysb33r.gradle.nodejs.dependencies.npm.NpmSelfResolvingDependency */ @CompileStatic class NodeJSDependencyFactory { - private final Project project + private final ProjectOperations projectOperations private final AsciidoctorJSNodeExtension nodejs private final AsciidoctorJSNpmExtension npm /** Instantiates a factory for a specific context. * - * @param project The GRadle project for which is is done. + * @param po The Gradle project for which is is done. * @param nodejs The NodeJS extension which is being used for this operation. * @param npm The NPM extension that is being used for this operation. + * + * @since 4.0 */ NodeJSDependencyFactory( - Project project, + ProjectOperations po, AsciidoctorJSNodeExtension nodejs, AsciidoctorJSNpmExtension npm ) { - this.project = project + this.projectOperations = po this.nodejs = nodejs this.npm = npm } @@ -86,7 +87,7 @@ class NodeJSDependencyFactory { * @param name Name of NPM package. * @param version Version (tag) of NPM package. * @param scope Scope of NPM package. - * @param withPaths A set paths to be added to the system search path. + * @param withPaths A set of paths to be added to the system search path. * @return A Gradle-style resolvable dependency. */ @SuppressWarnings('FactoryMethodName') @@ -108,9 +109,14 @@ class NodeJSDependencyFactory { } if (withPaths) { - description.put('path', project.files(withPaths).asPath) + description.put('path', projectOperations.fsOperations.files(withPaths).asPath) } - new NpmSelfResolvingDependency(project, nodejs, npm, description) + new BaseNpmSelfResolvingDependency( + projectOperations, + nodejs, + npm, + description + ) } } diff --git a/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/core/NodeJSUtils.groovy b/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/core/NodeJSUtils.groovy index c48fdfb00..f66117746 100644 --- a/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/core/NodeJSUtils.groovy +++ b/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/core/NodeJSUtils.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,8 @@ package org.asciidoctor.gradle.js.nodejs.core import groovy.transform.CompileStatic -import org.gradle.api.Project -import org.ysb33r.gradle.nodejs.NodeJSExtension -import org.ysb33r.gradle.nodejs.NpmExtension import org.ysb33r.gradle.nodejs.utils.npm.NpmExecutor -import org.ysb33r.grolifant.api.v4.StringUtils +import org.ysb33r.grolifant.api.core.ProjectOperations /** Utilities for dealing with NodeJS in an Asciidoctor-Gradle context * @@ -30,35 +27,32 @@ import org.ysb33r.grolifant.api.v4.StringUtils */ @CompileStatic class NodeJSUtils { - - /** Creaes a skeleton {@code package.json} file. + /** + * Creates a skeleton {@code package.json} file. * * Primarily used to keep NPM from complaining. * * @param npmHome Working home directory for NPM. * This is the directory in which {@code node_modules} will be created. * @param projectAlias An name for the project than can be used inside {@code package.json}. - * @param project The GRadle project for which is is done. + * @param po The {@link ProjectOperations} instance to use to resolve the version. * @param nodejs The NodeJS extension which is being used for this operation. * @param npm The NPM extension that is being used for this operation. + * + * @since 4.0 */ static void initPackageJson( File npmHome, String projectAlias, - Project project, - NodeJSExtension nodejs, - NpmExtension npm + ProjectOperations po, + AsciidoctorJSNodeExtension nodejs, + AsciidoctorJSNpmExtension npm ) { File packageJson = new File(npmHome, 'package.json') if (!packageJson.exists()) { npmHome.mkdirs() - NpmExecutor.initPkgJson( - projectAlias, - project.version ? StringUtils.stringize(project.version) : 'UNDEFINED', - project, - nodejs, - npm - ) + new NpmExecutor(po, nodejs, npm) + .initPkgJson(projectAlias, po.projectTools.versionProvider.orElse('UNDEFINED')) } } } diff --git a/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/internal/AsciidoctorJSRunner.groovy b/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/internal/AsciidoctorJSRunner.groovy index 9f6c26ff1..c83ba1881 100644 --- a/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/internal/AsciidoctorJSRunner.groovy +++ b/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/internal/AsciidoctorJSRunner.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,17 +16,19 @@ package org.asciidoctor.gradle.js.nodejs.internal import groovy.transform.CompileStatic +import groovy.util.logging.Slf4j import org.asciidoctor.gradle.base.SafeMode import org.asciidoctor.gradle.base.Transform -import org.gradle.api.Project import org.gradle.process.ExecSpec import org.ysb33r.gradle.nodejs.utils.NodeJSExecutor +import org.ysb33r.grolifant.api.core.ProjectOperations /** Executes an instance of Asciidoctor.Js * * @since 3.0 */ @CompileStatic +@Slf4j class AsciidoctorJSRunner { private static final String ATTR = '-a' @@ -38,7 +40,7 @@ class AsciidoctorJSRunner { private static final String DESTDIR = '-D' private final List arguments - private final Project project + private final ProjectOperations projectOperations private final File nodejs private final File asciidoctorjs private final File destinationDir @@ -47,19 +49,19 @@ class AsciidoctorJSRunner { @SuppressWarnings('ParameterCount') AsciidoctorJSRunner( - File nodejs, - Project project, - FileLocations asciidoctorjs, - String backend, - SafeMode safeMode, - File baseDir, - File destinationDir, - Map attributes, - Set requires, - Optional doctype, - boolean logDocuments + File nodejs, + ProjectOperations projectOperations, + FileLocations asciidoctorjs, + String backend, + SafeMode safeMode, + File baseDir, + File destinationDir, + Map attributes, + Set requires, + Optional doctype, + boolean logDocuments ) { - this.project = project + this.projectOperations = projectOperations this.asciidoctorjs = asciidoctorjs.executable this.nodejs = nodejs this.destinationDir = destinationDir @@ -67,9 +69,9 @@ class AsciidoctorJSRunner { this.nodeWorkingDir = asciidoctorjs.workingDir this.arguments = [ - BACKEND, backend, - SAFEMODE, safeMode.toString().toLowerCase(Locale.US), - BASEDIR, baseDir.absolutePath + BACKEND, backend, + SAFEMODE, safeMode.toString().toLowerCase(Locale.US), + BASEDIR, baseDir.absolutePath ] if (doctype.present) { @@ -96,13 +98,12 @@ class AsciidoctorJSRunner { args(asciidoctorjs.absolutePath) args(arguments) args( - DESTDIR, - (relativeOutputPath.empty ? - destinationDir : - new File(destinationDir, relativeOutputPath) - ).absolutePath + DESTDIR, + (relativeOutputPath.empty ? + destinationDir : + new File(destinationDir, relativeOutputPath) + ).absolutePath ) - args('--') args(Transform.toList(sources) { it.absolutePath }) @@ -112,10 +113,10 @@ class AsciidoctorJSRunner { } if (logDocuments) { - project.logger.info("Converting ${sources*.name.join(', ')}") + log.info("Converting ${sources*.name.join(', ')}") } - project.exec((Closure) configurator) + projectOperations.exec((Closure) configurator) } @SuppressWarnings('ClassName') @@ -123,15 +124,4 @@ class AsciidoctorJSRunner { File executable File workingDir } - -// --embedded, -e suppress enclosing document structure and output an embedded document -// [boolean] [default: false] -// --no-header-footer, -s suppress enclosing document structure and output an embedded document -// Optional sectionNumbers -n -// Optional failureLevel --failure-level -// [choices: "info", "INFO", "warn", "WARN", "warning", "WARNING", -// "error", "ERROR", "fatal", "FATAL"] [default: "FATAL"] -// boolean verboseMode -v -// boolean traceMode --trace -// boolean withTimings -t } diff --git a/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/internal/AsciidoctorNodeJSModules.groovy b/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/internal/AsciidoctorNodeJSModules.groovy index 8eddb789f..950ec0efd 100644 --- a/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/internal/AsciidoctorNodeJSModules.groovy +++ b/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/internal/AsciidoctorNodeJSModules.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,8 @@ import groovy.transform.CompileStatic import org.asciidoctor.gradle.js.base.AsciidoctorJSModules import org.asciidoctor.gradle.js.base.internal.AsciidoctorJSModule import org.asciidoctor.gradle.js.base.internal.BaseAsciidoctorJSModules -import org.asciidoctor.gradle.js.nodejs.AsciidoctorJSExtension import org.gradle.api.Action +import org.ysb33r.grolifant.api.core.ProjectOperations /** Define versions for standard AsciidoctorJS modules. * @@ -34,48 +34,62 @@ class AsciidoctorNodeJSModules extends BaseAsciidoctorJSModules implements Ascii public final static String SCOPE_ASCIIDOCTOR = 'asciidoctor' - /** Creates a module definition that is attached to a specific asciidoctorjs - * extension. + /** + * Standard modules that support asciidoctor.js development. * - * @param asciidoctorjs Extension that this module is attached to. + * @param po {@link ProjectOperations} instance + * @param versionMap Map containing Asciidoctor.js versions. + * @since 4.0 */ - AsciidoctorNodeJSModules(AsciidoctorJSExtension asciidoctorjs, Map versionMap) { + AsciidoctorNodeJSModules( + ProjectOperations po, + Map versionMap + ) { super( - Module.of( - 'docbook', - versionMap['asciidoctorjs.docbook'], - PackageDescriptor.of(SCOPE_ASCIIDOCTOR, 'docbook-converter') - ) + po, + Module.of( + po, + 'docbook', + versionMap['asciidoctorjs.docbook'], + PackageDescriptor.of(SCOPE_ASCIIDOCTOR, 'docbook-converter'), + ) ) } - /** Adds a package descriptor for a module - * + /** + * Adds a package descriptor for a module. */ static class Module extends AsciidoctorJSModule { private final PackageDescriptor packageIdentifier - static Module of(final String name, final Object defaultVersion, final PackageDescriptor packageIdentifier) { - new Module(name, defaultVersion, packageIdentifier) + static Module of( + final ProjectOperations po, + final String name, + final Object defaultVersion, + final PackageDescriptor packageIdentifier + ) { + new Module(po, name, defaultVersion, packageIdentifier) } static Module of( - final String name, - final Object defaultVersion, - final PackageDescriptor packageIdentifier, - Closure setAction + ProjectOperations po, + final String name, + final Object defaultVersion, + final PackageDescriptor packageIdentifier, + Closure setAction ) { - new Module(name, defaultVersion, packageIdentifier, setAction as Action) + new Module(po, name, defaultVersion, packageIdentifier, setAction as Action) } private Module( - final String name, - final Object defaultVersion, - final PackageDescriptor packageIdentifier, - Action setAction = null + final ProjectOperations po, + final String name, + final Object defaultVersion, + final PackageDescriptor packageIdentifier, + Action setAction = null ) { - super(name, defaultVersion, setAction) + super(po, name, defaultVersion, setAction) this.packageIdentifier = packageIdentifier } diff --git a/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/internal/PackageDescriptor.groovy b/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/internal/PackageDescriptor.groovy index 5f94eb433..f1b709173 100644 --- a/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/internal/PackageDescriptor.groovy +++ b/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/internal/PackageDescriptor.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/js/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.js.base.properties b/js/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.js.base.properties deleted file mode 100644 index 2ee99d791..000000000 --- a/js/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.js.base.properties +++ /dev/null @@ -1,17 +0,0 @@ -# -# Copyright 2013-2023 the original author or authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://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. -# - -implementation-class=org.asciidoctor.gradle.js.nodejs.AsciidoctorNodeJSBasePlugin \ No newline at end of file diff --git a/js/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.js.convert.properties b/js/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.js.convert.properties deleted file mode 100644 index 315913307..000000000 --- a/js/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.js.convert.properties +++ /dev/null @@ -1,17 +0,0 @@ -# -# Copyright 2013-2023 the original author or authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://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. -# - -implementation-class=org.asciidoctor.gradle.js.nodejs.AsciidoctorNodeJSPlugin \ No newline at end of file diff --git a/js/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.js.nodejs-base.properties b/js/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.js.nodejs-base.properties deleted file mode 100644 index c74572aba..000000000 --- a/js/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.js.nodejs-base.properties +++ /dev/null @@ -1,17 +0,0 @@ -# -# Copyright 2013-2023 the original author or authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://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. -# - -implementation-class=org.asciidoctor.gradle.js.nodejs.core.NodeJSBasePlugin \ No newline at end of file diff --git a/js/src/test/groovy/org/asciidoctor/gradle/js/nodejs/AsciidoctorJSExtensionSpec.groovy b/js/src/test/groovy/org/asciidoctor/gradle/js/nodejs/AsciidoctorJSExtensionSpec.groovy index cfd363b2a..6debf4ba6 100644 --- a/js/src/test/groovy/org/asciidoctor/gradle/js/nodejs/AsciidoctorJSExtensionSpec.groovy +++ b/js/src/test/groovy/org/asciidoctor/gradle/js/nodejs/AsciidoctorJSExtensionSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/js/src/test/groovy/org/asciidoctor/gradle/js/nodejs/AsciidoctorTaskSpec.groovy b/js/src/test/groovy/org/asciidoctor/gradle/js/nodejs/AsciidoctorTaskSpec.groovy index 42eb21ff3..9d14b4b18 100755 --- a/js/src/test/groovy/org/asciidoctor/gradle/js/nodejs/AsciidoctorTaskSpec.groovy +++ b/js/src/test/groovy/org/asciidoctor/gradle/js/nodejs/AsciidoctorTaskSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/jvm-epub/build.gradle b/jvm-epub/build.gradle index 572c788ec..1225dbb18 100644 --- a/jvm-epub/build.gradle +++ b/jvm-epub/build.gradle @@ -1,10 +1,22 @@ +agProject { + withOfflineTestConfigurations() + + configurePlugin( + 'org.asciidoctor.jvm.epub', + 'AsciidoctorJ EPUB Plugin', + 'Asciidoctor task for creating EPUB3 documents', + 'org.asciidoctor.gradle.jvm.epub.AsciidoctorJEpubPlugin', + ['asciidoctorj', 'epub', 'epub3'] + ) +} + generateModuleVersions { basename = 'asciidoctorj-epub' propertyNames ~/^kindlegen$/ } dependencies { - compile project(':asciidoctor-gradle-jvm') + implementation project(':asciidoctor-gradle-jvm') intTestOfflineRepo "org.asciidoctor:asciidoctorj:${compileOnlyAsciidoctorJVersion}" intTestOfflineRepo "org.asciidoctor:asciidoctorj-epub3:${downloadOnlyEpubVersion}" @@ -13,14 +25,3 @@ dependencies { intTest { systemProperties TEST_PROJECTS_DIR: file('src/intTest/projects') } - -configurePlugin 'org.asciidoctor.jvm.epub', - 'AsciidoctorJ EPUB Plugin', - 'Asciidoctor task for creating EPUB3 documents', - ['asciidoctorj', 'epub', 'epub3'] - -gradleTest { -// TODO: Re-enable this test -// enabled = false -// println "************ :asciidoctor-gradle-jvm-epub:gradleTest is disabled due to potential bug in asciidoctorj *********" -} \ No newline at end of file diff --git a/jvm-epub/src/gradleTest/epub3/build.gradle b/jvm-epub/src/gradleTest/epub3/build.gradle index 108b2136c..74c8e40b5 100644 --- a/jvm-epub/src/gradleTest/epub3/build.gradle +++ b/jvm-epub/src/gradleTest/epub3/build.gradle @@ -12,7 +12,7 @@ asciidoctorEpub { include 'epub3.adoc' } - inProcess IN_PROCESS + executionMode = IN_PROCESS } task runGradleTest { diff --git a/jvm-epub/src/gradleTest/issue-409-link-regression/src/docs/asciidoc/chapter1.adoc b/jvm-epub/src/gradleTest/issue-409-link-regression/src/docs/asciidoc/chapter1.adoc deleted file mode 100644 index 202e1d8f7..000000000 --- a/jvm-epub/src/gradleTest/issue-409-link-regression/src/docs/asciidoc/chapter1.adoc +++ /dev/null @@ -1,6 +0,0 @@ -[[chapter1]] -= Chapter 1 - -Some text. - -<> diff --git a/jvm-epub/src/gradleTest/issue-409-link-regression/src/docs/asciidoc/chapter2.adoc b/jvm-epub/src/gradleTest/issue-409-link-regression/src/docs/asciidoc/chapter2.adoc deleted file mode 100644 index 59b93f91c..000000000 --- a/jvm-epub/src/gradleTest/issue-409-link-regression/src/docs/asciidoc/chapter2.adoc +++ /dev/null @@ -1,9 +0,0 @@ -[[chapter2]] -= Chapter 2 - -Some text. - -[[subtitle]] -== Subtitle - -More text. diff --git a/jvm-epub/src/gradleTest/issue-409-link-regression/src/docs/asciidoc/epub3.adoc b/jvm-epub/src/gradleTest/issue-409-link-regression/src/docs/asciidoc/epub3.adoc deleted file mode 100644 index 7172f3e25..000000000 --- a/jvm-epub/src/gradleTest/issue-409-link-regression/src/docs/asciidoc/epub3.adoc +++ /dev/null @@ -1,6 +0,0 @@ -// Taken from https://github.com/slonopotamus/asciidoc-epub3-link-regression/tree/master/src/docs/asciidoc -= Project Euler -:doctype: book - -include::chapter1.adoc[leveloffset=+1] -include::chapter2.adoc[leveloffset=+1] diff --git a/jvm-epub/src/intTest/groovy/org/asciidoctor/gradle/jvm/epub/AsciidoctorEpubTaskCachingFunctionalSpec.groovy b/jvm-epub/src/intTest/groovy/org/asciidoctor/gradle/jvm/epub/AsciidoctorEpubTaskCachingFunctionalSpec.groovy index a299c5119..3d69d32e1 100644 --- a/jvm-epub/src/intTest/groovy/org/asciidoctor/gradle/jvm/epub/AsciidoctorEpubTaskCachingFunctionalSpec.groovy +++ b/jvm-epub/src/intTest/groovy/org/asciidoctor/gradle/jvm/epub/AsciidoctorEpubTaskCachingFunctionalSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,13 @@ package org.asciidoctor.gradle.jvm.epub import org.asciidoctor.gradle.jvm.epub.internal.FunctionalSpecification -import org.asciidoctor.gradle.testfixtures.CachingTest +import org.asciidoctor.gradle.testfixtures.BuildScanFixture +import org.asciidoctor.gradle.testfixtures.CachingTestFixture import static org.asciidoctor.gradle.testfixtures.JRubyTestVersions.AJ20_SAFE_MAXIMUM -class AsciidoctorEpubTaskCachingFunctionalSpec extends FunctionalSpecification implements CachingTest { +class AsciidoctorEpubTaskCachingFunctionalSpec extends FunctionalSpecification + implements CachingTestFixture, BuildScanFixture { private static final String DEFAULT_TASK = 'asciidoctorEpub' private static final String DEFAULT_OUTPUT_FILE = 'build/docs/asciidocEpub/epub3.epub' private static final String JRUBY_TEST_VERSION = AJ20_SAFE_MAXIMUM @@ -59,17 +61,11 @@ class AsciidoctorEpubTaskCachingFunctionalSpec extends FunctionalSpecification i } File getBuildFile(String extraContent) { - File buildFile = testProjectDir.newFile('build.gradle') - buildFile << """ - plugins { - id 'org.asciidoctor.jvm.epub' + writeGroovyBuildFile('org.asciidoctor.jvm.epub', extraContent).withWriterAppend { w -> + if (performBuildScan) { + w.println buildScanConfiguration } - - ${scan ? buildScanConfiguration : ''} - ${offlineRepositories} - - ${extraContent} - """ + } buildFile } diff --git a/jvm-epub/src/intTest/groovy/org/asciidoctor/gradle/jvm/epub/AsciidoctorEpubTaskFunctionalSpec.groovy b/jvm-epub/src/intTest/groovy/org/asciidoctor/gradle/jvm/epub/AsciidoctorEpubTaskFunctionalSpec.groovy index 31c7d98a4..87002866a 100644 --- a/jvm-epub/src/intTest/groovy/org/asciidoctor/gradle/jvm/epub/AsciidoctorEpubTaskFunctionalSpec.groovy +++ b/jvm-epub/src/intTest/groovy/org/asciidoctor/gradle/jvm/epub/AsciidoctorEpubTaskFunctionalSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,7 +40,7 @@ class AsciidoctorEpubTaskFunctionalSpec extends FunctionalSpecification { then: verifyAll { - new File(testProjectDir.root, 'build/docs/asciidocEpub/epub3.epub').exists() + new File(projectDir, 'build/docs/asciidocEpub/epub3.epub').exists() !result.output.contains('include file not found:') } } @@ -57,7 +57,7 @@ class AsciidoctorEpubTaskFunctionalSpec extends FunctionalSpecification { then: verifyAll { - new File(testProjectDir.root, 'build/docs/asciidocEpub/epub3.mobi').exists() + new File(projectDir, 'build/docs/asciidocEpub/epub3.mobi').exists() !result.output.contains('include file not found:') } } @@ -86,7 +86,7 @@ class AsciidoctorEpubTaskFunctionalSpec extends FunctionalSpecification { then: verifyAll { - new File(testProjectDir.root, 'build/docs/asciidocEpub/epub3.epub').exists() + new File(projectDir, 'build/docs/asciidocEpub/epub3.epub').exists() !result.output.contains('include file not found:') } @@ -132,17 +132,6 @@ class AsciidoctorEpubTaskFunctionalSpec extends FunctionalSpecification { } File getBuildFile(String extraContent) { - File buildFile = testProjectDir.newFile('build.gradle') - buildFile << """ -plugins { - id 'org.asciidoctor.jvm.epub' -} - -${offlineRepositories} - -${extraContent} -""" - buildFile + writeGroovyBuildFile('org.asciidoctor.jvm.epub', extraContent) } - } \ No newline at end of file diff --git a/jvm-epub/src/intTest/groovy/org/asciidoctor/gradle/jvm/epub/LinkedChaptersFunctionalSpec.groovy b/jvm-epub/src/intTest/groovy/org/asciidoctor/gradle/jvm/epub/LinkedChaptersFunctionalSpec.groovy index c117b92d9..d49dc44bb 100644 --- a/jvm-epub/src/intTest/groovy/org/asciidoctor/gradle/jvm/epub/LinkedChaptersFunctionalSpec.groovy +++ b/jvm-epub/src/intTest/groovy/org/asciidoctor/gradle/jvm/epub/LinkedChaptersFunctionalSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,24 +50,14 @@ class LinkedChaptersFunctionalSpec extends FunctionalSpecification { then: verifyAll { - new File(testProjectDir.root, 'build/docs/asciidocEpub/epub3.epub').exists() + new File(projectDir, 'build/docs/asciidocEpub/epub3.epub').exists() !result.output.contains('invalid reference to anchor') !result.output.contains('invalid reference to unknown anchor') } } File getBuildFile(String extraContent) { - File buildFile = testProjectDir.newFile('build.gradle') - buildFile << """ -plugins { - id 'org.asciidoctor.jvm.epub' -} - -${offlineRepositories} - -${extraContent} -""" - buildFile + writeGroovyBuildFile('org.asciidoctor.jvm.epub', extraContent) } } \ No newline at end of file diff --git a/jvm-epub/src/intTest/groovy/org/asciidoctor/gradle/jvm/epub/internal/FunctionalSpecification.groovy b/jvm-epub/src/intTest/groovy/org/asciidoctor/gradle/jvm/epub/internal/FunctionalSpecification.groovy index 5f5105dac..82e761855 100644 --- a/jvm-epub/src/intTest/groovy/org/asciidoctor/gradle/jvm/epub/internal/FunctionalSpecification.groovy +++ b/jvm-epub/src/intTest/groovy/org/asciidoctor/gradle/jvm/epub/internal/FunctionalSpecification.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,15 +16,15 @@ package org.asciidoctor.gradle.jvm.epub.internal import org.apache.commons.io.FileUtils +import org.asciidoctor.gradle.testfixtures.FunctionalTestFixture import org.asciidoctor.gradle.testfixtures.FunctionalTestSetup import org.gradle.testkit.runner.GradleRunner import org.gradle.util.VersionNumber -import org.junit.Rule -import org.junit.rules.TemporaryFolder import org.ysb33r.grolifant.api.core.OperatingSystem import spock.lang.Specification +import spock.lang.TempDir -class FunctionalSpecification extends Specification { +class FunctionalSpecification extends Specification implements FunctionalTestFixture { @SuppressWarnings('LineLength') static @@ -34,24 +34,25 @@ class FunctionalSpecification extends Specification { static final OperatingSystem OS = OperatingSystem.current() - @Rule - TemporaryFolder testProjectDir + @TempDir + File testProjectDir - @Rule - TemporaryFolder alternateProjectDir + void setup() { + projectDir.mkdirs() + } GradleRunner getGradleRunner(List taskNames = ['asciidoctor']) { GradleRunner.create() - .withProjectDir(testProjectDir.root) - .withArguments(taskNames) - .withPluginClasspath() - .forwardOutput() - .withDebug(true) + .withProjectDir(projectDir) + .withArguments(taskNames) + .withPluginClasspath() + .forwardOutput() + .withDebug(true) } @SuppressWarnings(['BuilderMethodWithSideEffects']) void createTestProject(String docGroup = 'epub3') { - FileUtils.copyDirectory(new File(TEST_PROJECTS_DIR, docGroup), testProjectDir.root) + FileUtils.copyDirectory(new File(TEST_PROJECTS_DIR, docGroup), projectDir) } @SuppressWarnings('LineLength') @@ -68,8 +69,8 @@ class FunctionalSpecification extends Specification { } } - static boolean isWindowsOr64bitOnlyMacOS() { - VersionNumber version = VersionNumber.parse(OS.version) - OS.windows || (OS.macOsX && version.major >= 10 && version.minor >= 15) - } +// static boolean isWindowsOr64bitOnlyMacOS() { +// VersionNumber version = VersionNumber.parse(OS.version) +// OS.windows || (OS.macOsX && version.major >= 10 && version.minor >= 15) +// } } \ No newline at end of file diff --git a/jvm-epub/src/main/groovy/org/asciidoctor/gradle/jvm/epub/AsciidoctorEpubTask.groovy b/jvm-epub/src/main/groovy/org/asciidoctor/gradle/jvm/epub/AsciidoctorEpubTask.groovy index 17b7be0e0..604ee2dd4 100644 --- a/jvm-epub/src/main/groovy/org/asciidoctor/gradle/jvm/epub/AsciidoctorEpubTask.groovy +++ b/jvm-epub/src/main/groovy/org/asciidoctor/gradle/jvm/epub/AsciidoctorEpubTask.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,20 +19,19 @@ import groovy.transform.CompileDynamic import groovy.transform.CompileStatic import org.asciidoctor.gradle.base.AsciidoctorExecutionException import org.asciidoctor.gradle.base.Transform -import org.asciidoctor.gradle.base.process.ProcessMode import org.asciidoctor.gradle.internal.ExecutorConfiguration import org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.Input import org.gradle.api.tasks.util.PatternSet -import org.gradle.util.GradleVersion import org.gradle.workers.WorkerExecutor import org.ysb33r.grolifant.api.core.Version import javax.inject.Inject import java.util.regex.Matcher -/** Builds EPUB documents using the epub3 backend. +/** + * Builds EPUB documents using the epub3 backend. * * @author Schalk W. Cronjé * @author Gary Hale @@ -44,22 +43,21 @@ import java.util.regex.Matcher class AsciidoctorEpubTask extends AbstractAsciidoctorTask { public static final String EPUB3 = 'epub3' - private static final String BACKEND = 'epub3' private static final String EBOOK_FORMAT_ATTR = 'ebook-format' - private final Set ebookFormats = [] @Inject AsciidoctorEpubTask(WorkerExecutor we) { super(we) - configuredOutputOptions.backends = [BACKEND] + outputOptions.backends = [BACKEND] copyNoResources() - inProcess = JAVA_EXEC +// inProcess = JAVA_EXEC } - /** The eBook formats that needs to be generated. + /** + * The eBook formats that needs to be generated. * * @return eBook formats that needs to be generated. */ @@ -68,7 +66,8 @@ class AsciidoctorEpubTask extends AbstractAsciidoctorTask { this.ebookFormats } - /** Resets the list of formats that needs to be generated + /** + * Resets the list of formats that needs to be generated * * Any format supported by asciidoctorj-epub can be listed here. * This method will overide any {@code ebook-format} that is set via {@link #attributes}. @@ -82,7 +81,8 @@ class AsciidoctorEpubTask extends AbstractAsciidoctorTask { this.ebookFormats.addAll(Transform.toSet(formats) { String it -> it.toLowerCase() } as Set) } - /** Adds aditional eBook formats + /** + * Adds aditional eBook formats * * @param formats List of formats. The plugin does not verify whether the eBook format * is valid. @@ -91,29 +91,31 @@ class AsciidoctorEpubTask extends AbstractAsciidoctorTask { this.ebookFormats.addAll(formats*.toLowerCase() as List) } - /** The default pattern set for secondary sources. + /** + * The default pattern set for secondary sources. * * @return {@link #getDefaultSourceDocumentPattern} + `*docinfo*`. */ @Override - protected PatternSet getDefaultSecondarySourceDocumentPattern() { + PatternSet getDefaultSecondarySourceDocumentPattern() { defaultSourceDocumentPattern } - /** Returns all of the executor configurations for this task + /** + * Returns all of the executor configurations for this task * * @return Executor configurations */ @Override protected Map getExecutorConfigurations( - File workingSourceDir, - Set sourceFiles, - Optional lang + File workingSourceDir, + Set sourceFiles, + Optional lang ) { Map executorConfigurations = super.getExecutorConfigurations( - workingSourceDir, - sourceFiles, - lang + workingSourceDir, + sourceFiles, + lang ) final Closure backendName = { String fmt -> @@ -147,23 +149,23 @@ class AsciidoctorEpubTask extends AbstractAsciidoctorTask { } } - /** Selects a final process mode. - * - * Selects JAVA_EXEC on any Gradle version that has classpath ;eakage issues. - * - * @return Process mode to use for execution. - */ - @Override - protected ProcessMode getFinalProcessMode() { - if (GradleVersion.current() <= LAST_GRADLE_WITH_CLASSPATH_LEAKAGE) { - if (inProcess != JAVA_EXEC) { - logger.warn 'EPUB processing on this version of Gradle will fail due to classpath issues. ' + - 'Switching to JAVA_EXEC instead.' - } - return JAVA_EXEC - } - super.finalProcessMode - } +// /** Selects a final process mode. +// * +// * Selects JAVA_EXEC on any Gradle version that has classpath ;eakage issues. +// * +// * @return Process mode to use for execution. +// */ +// @Override +// protected ProcessMode getFinalProcessMode() { +// if (GradleVersion.current() <= LAST_GRADLE_WITH_CLASSPATH_LEAKAGE) { +// if (inProcess != JAVA_EXEC) { +// logger.warn 'EPUB processing on this version of Gradle will fail due to classpath issues. ' + +// 'Switching to JAVA_EXEC instead.' +// } +// return JAVA_EXEC +// } +// super.finalProcessMode +// } @CompileDynamic private Version getVersion(Matcher matcher) { diff --git a/jvm-epub/src/main/groovy/org/asciidoctor/gradle/jvm/epub/AsciidoctorJEpubPlugin.groovy b/jvm-epub/src/main/groovy/org/asciidoctor/gradle/jvm/epub/AsciidoctorJEpubPlugin.groovy index b47a8e7a5..34b2b50a5 100644 --- a/jvm-epub/src/main/groovy/org/asciidoctor/gradle/jvm/epub/AsciidoctorJEpubPlugin.groovy +++ b/jvm-epub/src/main/groovy/org/asciidoctor/gradle/jvm/epub/AsciidoctorJEpubPlugin.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,11 +21,11 @@ import org.asciidoctor.gradle.jvm.AsciidoctorJExtension import org.gradle.api.Action import org.gradle.api.Plugin import org.gradle.api.Project -import org.ysb33r.grolifant.api.v4.TaskProvider import static org.asciidoctor.gradle.base.AsciidoctorUtils.setConvention -/** Provides additional conventions for building EPUBs. +/** + * Provides additional conventions for building EPUBs. * *
      *
    • Creates a task called {@code asciidoctorEpub}. @@ -41,23 +41,21 @@ import static org.asciidoctor.gradle.base.AsciidoctorUtils.setConvention class AsciidoctorJEpubPlugin implements Plugin { void apply(Project project) { - project.with { - apply plugin: AsciidoctorJBasePlugin - extensions.getByType(AsciidoctorJExtension).modules.epub.use() + project.pluginManager.apply(AsciidoctorJBasePlugin) + project.extensions.getByType(AsciidoctorJExtension).modules.epub.use() - Action epubDefaults = new Action() { - @Override - void execute(AsciidoctorEpubTask task) { - task.group = AsciidoctorJBasePlugin.TASK_GROUP - task.description = 'Convert AsciiDoc files to EPUB3 formats' - setConvention(project, task.sourceDirProperty, + Action epubDefaults = new Action() { + @Override + void execute(AsciidoctorEpubTask task) { + task.group = AsciidoctorJBasePlugin.TASK_GROUP + task.description = 'Convert AsciiDoc files to EPUB3 formats' + setConvention(project, task.sourceDirProperty, project.layout.projectDirectory.dir('src/docs/asciidoc')) - setConvention(task.outputDirProperty, + setConvention(task.outputDirProperty, task.project.layout.buildDirectory.dir('docs/asciidocEpub')) - } } - - TaskProvider.registerTask(project, 'asciidoctorEpub', AsciidoctorEpubTask, epubDefaults) } + + project.tasks.register('asciidoctorEpub', AsciidoctorEpubTask, epubDefaults) } } diff --git a/jvm-epub/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.jvm.epub.properties b/jvm-epub/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.jvm.epub.properties deleted file mode 100644 index 278786f73..000000000 --- a/jvm-epub/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.jvm.epub.properties +++ /dev/null @@ -1,17 +0,0 @@ -# -# Copyright 2013-2023 the original author or authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://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. -# - -implementation-class=org.asciidoctor.gradle.jvm.epub.AsciidoctorJEpubPlugin \ No newline at end of file diff --git a/jvm-leanpub/build.gradle b/jvm-leanpub/build.gradle index 7b723a1f9..e5da5f53d 100644 --- a/jvm-leanpub/build.gradle +++ b/jvm-leanpub/build.gradle @@ -1,23 +1,24 @@ -dependencies { - compile project(':asciidoctor-gradle-jvm') +agProject { + withOfflineTestConfigurations() - intTestOfflineRepo "org.asciidoctor:asciidoctorj:${compileOnlyAsciidoctorJVersion}" - intTestOfflineRepo "org.asciidoctor:asciidoctor-leanpub-markdown:${downloadOnlyLeanpubVersion}" -} + configurePlugin 'org.asciidoctor.jvm.leanpub', + 'AsciidoctorJ Leanpub Plugin', + 'Asciidoctor task for creating content suitable for Leanpub', + 'org.asciidoctor.gradle.jvm.leanpub.AsciidoctorJLeanpubPlugin', + ['asciidoctorj', 'leanpub', 'markdown', 'markuva'] + + configurePlugin 'org.asciidoctor.jvm.leanpub.dropbox-copy', + 'Dropbox support for AsciidoctorJ Leanpub Plugin', + 'Provides local Dropbox support for Asciidoctor Leanpub plugin', + 'org.asciidoctor.gradle.jvm.leanpub.AsciidoctorJLeanpubDropboxCopyPlugin', + ['asciidoctorj', 'leanpub', 'markdown', 'markuva', 'dropbox'] -intTest { - systemProperties TEST_PROJECTS_DIR: file('src/intTest/projects') } -configurePlugin 'org.asciidoctor.jvm.leanpub', - 'AsciidoctorJ Leanpub Plugin', - 'Asciidoctor task for creating content suitable for Leanpub', - ['asciidoctorj', 'leanpub', 'markdown', 'markuva'] +dependencies { + implementation project(':asciidoctor-gradle-jvm') -configurePlugin 'org.asciidoctor.jvm.leanpub.dropbox-copy', - 'Dropbox support for AsciidoctorJ Leanpub Plugin', - 'Provides local Dropbox support for Asciidoctor Leanpub plugin', - ['asciidoctorj', 'leanpub', 'markdown', 'markuva', 'dropbox'] + intTestOfflineRepo "org.asciidoctor:asciidoctorj:${compileOnlyAsciidoctorJVersion}" + intTestOfflineRepo "org.asciidoctor:asciidoctor-leanpub-markdown:${downloadOnlyLeanpubVersion}" +} -// TODO: Re-enable this after leanpub artifact appears on MavenCentral -gradleTest.enabled = false \ No newline at end of file diff --git a/jvm-leanpub/src/intTest/groovy/org/asciidoctor/gradle/jvm/leanpub/AsciidoctorLeanpubTaskFunctionalSpec.groovy b/jvm-leanpub/src/intTest/groovy/org/asciidoctor/gradle/jvm/leanpub/AsciidoctorLeanpubTaskFunctionalSpec.groovy index 32c8b579c..77e393450 100644 --- a/jvm-leanpub/src/intTest/groovy/org/asciidoctor/gradle/jvm/leanpub/AsciidoctorLeanpubTaskFunctionalSpec.groovy +++ b/jvm-leanpub/src/intTest/groovy/org/asciidoctor/gradle/jvm/leanpub/AsciidoctorLeanpubTaskFunctionalSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,49 +36,22 @@ class AsciidoctorLeanpubTaskFunctionalSpec extends FunctionalSpecification { then: verifyAll { - new File(testProjectDir.root, 'build/docs/asciidocLeanpub/manuscript/Book.txt').exists() + new File(projectDir, 'build/docs/asciidocLeanpub/manuscript/Book.txt').exists() } where: processMode << ProcessGenerator.get() } -// File getSingleFormatBuildFile(final String format) { -// getBuildFile( """ -// -// asciidoctorEpub { -// sourceDir 'src/docs/asciidoc' -// ebookFormats ${format} -// -// kindlegen { -// agreeToTermsOfUse = true -// } -// -// asciidoctorj { -// jrubyVersion = '${JRUBY_TEST_VERSION}' -// } -// -// sources { -// include 'epub3.adoc' -// } -// } -// """) -// } - File getBuildFile(String extraContent, boolean withDropbox = false) { - File buildFile = testProjectDir.newFile('build.gradle') - buildFile << """ -plugins { - id 'org.asciidoctor.jvm.leanpub' - - ${withDropbox ? "id 'org.asciidoctor.jvm.leanpub.dropbox-copy'" : ''} -} - -${offlineRepositories} - -${extraContent} -""" - buildFile + if (withDropbox) { + writeGroovyBuildFile( + ['org.asciidoctor.jvm.leanpub', 'org.asciidoctor.jvm.leanpub.dropbox-copy'], + extraContent + ) + } else { + getJvmConvertGroovyBuildFile(extraContent) + } } } \ No newline at end of file diff --git a/jvm-leanpub/src/intTest/groovy/org/asciidoctor/gradle/jvm/leanpub/internal/FunctionalSpecification.groovy b/jvm-leanpub/src/intTest/groovy/org/asciidoctor/gradle/jvm/leanpub/internal/FunctionalSpecification.groovy index f13d3aacf..68b0b7424 100644 --- a/jvm-leanpub/src/intTest/groovy/org/asciidoctor/gradle/jvm/leanpub/internal/FunctionalSpecification.groovy +++ b/jvm-leanpub/src/intTest/groovy/org/asciidoctor/gradle/jvm/leanpub/internal/FunctionalSpecification.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,72 +17,44 @@ package org.asciidoctor.gradle.jvm.leanpub.internal import groovy.transform.CompileStatic import org.apache.commons.io.FileUtils -import org.asciidoctor.gradle.testfixtures.DslType +import org.asciidoctor.gradle.testfixtures.FunctionalTestFixture import org.asciidoctor.gradle.testfixtures.FunctionalTestSetup import org.gradle.testkit.runner.GradleRunner -import org.junit.Rule -import org.junit.rules.TemporaryFolder -import org.ysb33r.grolifant.api.core.OperatingSystem import spock.lang.Specification +import spock.lang.TempDir import static org.asciidoctor.gradle.testfixtures.DslType.GROOVY_DSL -import static org.asciidoctor.gradle.testfixtures.DslType.KOTLIN_DSL -import static org.asciidoctor.gradle.testfixtures.FunctionalTestSetup.getOfflineRepositoriesGroovyDsl -import static org.asciidoctor.gradle.testfixtures.FunctionalTestSetup.getOfflineRepositoriesKotlinDsl -class FunctionalSpecification extends Specification { +class FunctionalSpecification extends Specification implements FunctionalTestFixture { public static final String TEST_PROJECTS_DIR = System.getProperty( - 'TEST_PROJECTS_DIR', - './src/intTest/projects' + 'TEST_PROJECTS_DIR', + './src/intTest/projects' ) public static final String TEST_REPO_DIR = FunctionalTestSetup.offlineRepo.absolutePath -// public static final OperatingSystem OS = OperatingSystem.current() - @Rule - TemporaryFolder testProjectDir = new TemporaryFolder() + @TempDir + File testProjectDir + + void setup() { + projectDir.mkdirs() + } @CompileStatic GradleRunner getGradleRunner(List taskNames = ['asciidoctor']) { - FunctionalTestSetup.getGradleRunner(GROOVY_DSL, testProjectDir.root, taskNames) + FunctionalTestSetup.getGradleRunner(GROOVY_DSL, projectDir, taskNames) } @SuppressWarnings(['BuilderMethodWithSideEffects']) void createTestProject(String docGroup = 'leanpub') { - FileUtils.copyDirectory(new File(TEST_PROJECTS_DIR, docGroup), testProjectDir.root) + FileUtils.copyDirectory(new File(TEST_PROJECTS_DIR, docGroup), projectDir) } - @CompileStatic - String getOfflineRepositories(DslType dslType = GROOVY_DSL) { - dslType == GROOVY_DSL ? getOfflineRepositoriesGroovyDsl(new File(TEST_REPO_DIR)) : - getOfflineRepositoriesKotlinDsl(new File(TEST_REPO_DIR)) + File getJvmConvertGroovyBuildFile(String extraContent) { + writeGroovyBuildFile('org.asciidoctor.jvm.leanpub', extraContent) } - File getJvmConvertGroovyBuildFile(String extraContent, String plugin = 'org.asciidoctor.jvm.leanpub') { - File buildFile = testProjectDir.newFile('build.gradle') - buildFile << """ - plugins { - id '${plugin}' - } - - ${offlineRepositories} - - ${extraContent} - """ - buildFile - } - - File getJvmConvertKotlinBuildFile(String extraContent, String plugin = 'org.asciidoctor.jvm.leanpub') { - File buildFile = testProjectDir.newFile('build.gradle.kts') - buildFile << """ - plugins { - id ("${plugin}") - } - - ${getOfflineRepositories(KOTLIN_DSL)} - - ${extraContent} - """ - buildFile + File getJvmConvertKotlinBuildFile(String extraContent) { + writeKotlinBuildFile('org.asciidoctor.jvm.leanpub', extraContent) } } \ No newline at end of file diff --git a/jvm-leanpub/src/main/groovy/org/asciidoctor/gradle/jvm/leanpub/AsciidoctorJLeanpubDropboxCopyPlugin.groovy b/jvm-leanpub/src/main/groovy/org/asciidoctor/gradle/jvm/leanpub/AsciidoctorJLeanpubDropboxCopyPlugin.groovy index 9df4298e2..9be6aa331 100644 --- a/jvm-leanpub/src/main/groovy/org/asciidoctor/gradle/jvm/leanpub/AsciidoctorJLeanpubDropboxCopyPlugin.groovy +++ b/jvm-leanpub/src/main/groovy/org/asciidoctor/gradle/jvm/leanpub/AsciidoctorJLeanpubDropboxCopyPlugin.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,8 @@ package org.asciidoctor.gradle.jvm.leanpub import groovy.transform.CompileStatic -import org.gradle.api.Action import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.api.Task -import org.ysb33r.grolifant.api.v4.TaskProvider /** Adds a task to copy Leanpub task output to a Dropbox folder. * @@ -30,28 +27,20 @@ import org.ysb33r.grolifant.api.v4.TaskProvider */ @CompileStatic class AsciidoctorJLeanpubDropboxCopyPlugin implements Plugin { - public final static String LEANPUB_TASK_NAME = AsciidoctorJLeanpubPlugin.TASK_NAME public final static String COPY_TASK_NAME = "copy${LEANPUB_TASK_NAME.capitalize()}ToDropbox" @Override void apply(Project project) { - project.apply plugin: AsciidoctorJLeanpubPlugin - Action copyConfig = new Action() { - @Override - void execute(DropboxCopyTask dropboxCopyTask) { - dropboxCopyTask.with { - dependsOn(LEANPUB_TASK_NAME) - sourceDir = { - Task task = project.tasks.getByName(LEANPUB_TASK_NAME.toString()) - new File( - ((AsciidoctorLeanpubTask) task).outputDir, - 'manuscript' - ) - } - } - } + project.pluginManager.apply(AsciidoctorJLeanpubPlugin) + final leanpubTask = project.tasks.named(LEANPUB_TASK_NAME, AsciidoctorLeanpubTask) + final leanpubSourceDir = leanpubTask.map { + new File(it.outputDir, 'manuscript') + } + + project.tasks.register(COPY_TASK_NAME, DropboxCopyTask) { t -> + t.dependsOn(leanpubTask) + t.sourceDir = leanpubSourceDir } - TaskProvider.registerTask(project, COPY_TASK_NAME, DropboxCopyTask, copyConfig) } } diff --git a/jvm-leanpub/src/main/groovy/org/asciidoctor/gradle/jvm/leanpub/AsciidoctorJLeanpubPlugin.groovy b/jvm-leanpub/src/main/groovy/org/asciidoctor/gradle/jvm/leanpub/AsciidoctorJLeanpubPlugin.groovy index c08203805..a1bedad98 100644 --- a/jvm-leanpub/src/main/groovy/org/asciidoctor/gradle/jvm/leanpub/AsciidoctorJLeanpubPlugin.groovy +++ b/jvm-leanpub/src/main/groovy/org/asciidoctor/gradle/jvm/leanpub/AsciidoctorJLeanpubPlugin.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,6 @@ import org.asciidoctor.gradle.jvm.AsciidoctorJExtension import org.gradle.api.Action import org.gradle.api.Plugin import org.gradle.api.Project -import org.ysb33r.grolifant.api.v4.TaskProvider import static org.asciidoctor.gradle.base.AsciidoctorUtils.setConvention @@ -43,23 +42,22 @@ class AsciidoctorJLeanpubPlugin implements Plugin { public final static String TASK_NAME = 'asciidoctorLeanpub' void apply(Project project) { - project.with { - apply plugin: AsciidoctorJBasePlugin + project.pluginManager.apply(AsciidoctorJBasePlugin) - Action leanpubDefaults = new Action() { - @Override - void execute(AsciidoctorLeanpubTask task) { - task.group = AsciidoctorJBasePlugin.TASK_GROUP - task.description = 'Convert AsciiDoc files to Leanpub-structured Markdown' - setConvention(task.project, task.sourceDirProperty, - project.layout.projectDirectory.dir('src/docs/asciidoc')) - setConvention(task.outputDirProperty, - task.project.layout.buildDirectory.dir('docs/asciidocLeanpub')) - } + Action leanpubDefaults = new Action() { + @Override + void execute(AsciidoctorLeanpubTask task) { + task.group = AsciidoctorJBasePlugin.TASK_GROUP + task.description = 'Convert AsciiDoc files to Leanpub-structured Markdown' + setConvention(task.project, task.sourceDirProperty, + project.layout.projectDirectory.dir('src/docs/asciidoc')) + setConvention(task.outputDirProperty, + task.project.layout.buildDirectory.dir('docs/asciidocLeanpub')) } - - TaskProvider.registerTask(project, TASK_NAME, AsciidoctorLeanpubTask, leanpubDefaults) - extensions.getByType(AsciidoctorJExtension).modules.leanpub.use() } + + project.tasks.register(TASK_NAME, AsciidoctorLeanpubTask, leanpubDefaults) + project.extensions.getByType(AsciidoctorJExtension).modules.leanpub.use() } } + diff --git a/jvm-leanpub/src/main/groovy/org/asciidoctor/gradle/jvm/leanpub/AsciidoctorLeanpubTask.groovy b/jvm-leanpub/src/main/groovy/org/asciidoctor/gradle/jvm/leanpub/AsciidoctorLeanpubTask.groovy index 7b7f7f820..5953cbd77 100644 --- a/jvm-leanpub/src/main/groovy/org/asciidoctor/gradle/jvm/leanpub/AsciidoctorLeanpubTask.groovy +++ b/jvm-leanpub/src/main/groovy/org/asciidoctor/gradle/jvm/leanpub/AsciidoctorLeanpubTask.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,12 +47,13 @@ class AsciidoctorLeanpubTask extends AbstractAsciidoctorTask { AsciidoctorLeanpubTask(WorkerExecutor we) { super(we) - configuredOutputOptions.backends = [BACKEND] + outputOptions.backends = [BACKEND] copyNoResources() asciidoctorj.options.put('doctype', 'book') } - /** The style used to format colists. + /** + * The style used to format colists. * * @return Colist style */ @@ -74,7 +75,8 @@ class AsciidoctorLeanpubTask extends AbstractAsciidoctorTask { this.colistPrefix = val } - /** A task may add some default attributes. + /** + * A task may add some default attributes. * * If the user specifies any of the attributes, then these attributes will not be utilised. * @@ -85,11 +87,13 @@ class AsciidoctorLeanpubTask extends AbstractAsciidoctorTask { * @return A collection of default attributes. */ @Override - protected Map getTaskSpecificDefaultAttributes(File workingSourceDir) { - Map attrs = super.getTaskSpecificDefaultAttributes(workingSourceDir) -// attrs.put 'front-cover-image', getFrontCoverImage() - attrs.put 'leanpub-colist-style', getColistStyle() - attrs.put 'leanpub-colist-prefix', getColistPrefix() + Map getTaskSpecificDefaultAttributes(File workingSourceDir) { + Map attrs = super.getTaskSpecificDefaultAttributes(workingSourceDir) + attrs.putAll([ + // 'front-cover-image': getFrontCoverImage(), + 'leanpub-colist-style' : colistStyle, + 'leanpub-colist-prefix': colistPrefix + ]) attrs } } diff --git a/jvm-leanpub/src/main/groovy/org/asciidoctor/gradle/jvm/leanpub/DropboxCopyTask.groovy b/jvm-leanpub/src/main/groovy/org/asciidoctor/gradle/jvm/leanpub/DropboxCopyTask.groovy index 688da5b3e..45cfdf365 100644 --- a/jvm-leanpub/src/main/groovy/org/asciidoctor/gradle/jvm/leanpub/DropboxCopyTask.groovy +++ b/jvm-leanpub/src/main/groovy/org/asciidoctor/gradle/jvm/leanpub/DropboxCopyTask.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,9 +26,10 @@ import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.PathSensitive import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.TaskAction -import org.ysb33r.grolifant.api.v4.StringUtils +import org.ysb33r.grolifant.api.core.ProjectOperations -/** Copies Leanpub Asciidoctor task output to a Dropbox folder. +/** + * Copies Leanpub Asciidoctor task output to a Dropbox folder. * * @author Schalk W. Cronjé * @@ -37,6 +38,7 @@ import org.ysb33r.grolifant.api.v4.StringUtils @CompileStatic class DropboxCopyTask extends DefaultTask { + private final ProjectOperations projectOperations private Object dropboxRoot = "${System.getProperty('user.home')}/Dropbox" private Object bookPath private Object sourceDir @@ -50,13 +52,17 @@ class DropboxCopyTask extends DefaultTask { } } + DropboxCopyTask() { + this.projectOperations = ProjectOperations.find(project) + } + /** Root directory on filesystem where Dropbox synchronises all files. * * @return Path to Dropbox root on local filesystem. */ @Internal File getDropboxRoot() { - project.file(this.dropboxRoot) + projectOperations.fsOperations.file(this.dropboxRoot) } /** Override location of Dropbox root @@ -75,7 +81,7 @@ class DropboxCopyTask extends DefaultTask { */ @Input String getBookPath() { - this.bookPath != null ? StringUtils.stringize(this.bookPath) : null + this.bookPath != null ? projectOperations.stringTools.stringize(this.bookPath) : null } /** Sets the relative path of the Leanpub book in Dropbox. @@ -95,7 +101,7 @@ class DropboxCopyTask extends DefaultTask { @InputDirectory @PathSensitive(PathSensitivity.RELATIVE) File getSourceDir() { - project.file(this.sourceDir) + projectOperations.fsOperations.file(this.sourceDir) } void setSourceDir(Object s) { @@ -108,11 +114,11 @@ class DropboxCopyTask extends DefaultTask { */ @OutputDirectory File getDestinationDir() { - project.file("${getDropboxRoot()}/${getBookPath()}/manuscript") + projectOperations.fsOperations.file("${getDropboxRoot()}/${getBookPath()}/manuscript") } @TaskAction void copy() { - project.copy(copySpec) + projectOperations.copy(copySpec) } } diff --git a/jvm-leanpub/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.jvm.leanpub.dropbox-copy.properties b/jvm-leanpub/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.jvm.leanpub.dropbox-copy.properties deleted file mode 100644 index 7262db0de..000000000 --- a/jvm-leanpub/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.jvm.leanpub.dropbox-copy.properties +++ /dev/null @@ -1,17 +0,0 @@ -# -# Copyright 2013-2023 the original author or authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://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. -# - -implementation-class=org.asciidoctor.gradle.jvm.leanpub.AsciidoctorJLeanpubDropboxCopyPlugin \ No newline at end of file diff --git a/jvm-leanpub/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.jvm.leanpub.properties b/jvm-leanpub/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.jvm.leanpub.properties deleted file mode 100644 index aa3f3cf02..000000000 --- a/jvm-leanpub/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.jvm.leanpub.properties +++ /dev/null @@ -1,17 +0,0 @@ -# -# Copyright 2013-2023 the original author or authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://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. -# - -implementation-class=org.asciidoctor.gradle.jvm.leanpub.AsciidoctorJLeanpubPlugin \ No newline at end of file diff --git a/jvm-leanpub/src/test/groovy/org/asciidoctor/gradle/jvm/leanpub/AsciidoctorJLeanpubPluginSpec.groovy b/jvm-leanpub/src/test/groovy/org/asciidoctor/gradle/jvm/leanpub/AsciidoctorJLeanpubPluginSpec.groovy index 709f0b18e..31c53e54b 100644 --- a/jvm-leanpub/src/test/groovy/org/asciidoctor/gradle/jvm/leanpub/AsciidoctorJLeanpubPluginSpec.groovy +++ b/jvm-leanpub/src/test/groovy/org/asciidoctor/gradle/jvm/leanpub/AsciidoctorJLeanpubPluginSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/jvm-leanpub/src/test/groovy/org/asciidoctor/gradle/jvm/leanpub/DropboxCopyTaskSpec.groovy b/jvm-leanpub/src/test/groovy/org/asciidoctor/gradle/jvm/leanpub/DropboxCopyTaskSpec.groovy index 730993ced..fba6170bd 100644 --- a/jvm-leanpub/src/test/groovy/org/asciidoctor/gradle/jvm/leanpub/DropboxCopyTaskSpec.groovy +++ b/jvm-leanpub/src/test/groovy/org/asciidoctor/gradle/jvm/leanpub/DropboxCopyTaskSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/jvm-pdf/build.gradle b/jvm-pdf/build.gradle index 2cdf9ad39..4cef309e6 100644 --- a/jvm-pdf/build.gradle +++ b/jvm-pdf/build.gradle @@ -1,22 +1,28 @@ -dependencies { - compile project(':asciidoctor-gradle-jvm') +agProject { + withOfflineTestConfigurations() + + configurePlugin( + 'org.asciidoctor.jvm.pdf', + 'AsciidoctorJ PDF Conversion Plugin', + 'Simplifies conversion of asciidoc documents to PDF', + 'org.asciidoctor.gradle.jvm.pdf.AsciidoctorJPdfPlugin', + ['asciidoctorj', 'pdf'] + ) +} +dependencies { + implementation project(':asciidoctor-gradle-jvm') intTestOfflineRepo "org.asciidoctor:asciidoctorj:${compileOnlyAsciidoctorJVersion}" intTestOfflineRepo "org.asciidoctor:asciidoctorj-pdf:${downloadOnlyPdfVersion}" } -test { +tasks.named('test', Test) { systemProperties TEST_THEMES_DIR: file('src/test/resources/themes') } -intTest { - systemProperties TEST_PROJECTS_DIR: file('src/intTest/projects') +tasks.named('intTest', Test) { + maxParallelForks = 2 + forkEvery = 1 + maxHeapSize = '2048m' } -configurePlugin 'org.asciidoctor.jvm.pdf', - 'AsciidoctorJ PDF Conversion Plugin', - 'Simplifies conversion of asciidoc documents to PDF', - ['asciidoctorj', 'pdf'] - - -gradleTest.gradleArguments '-s' \ No newline at end of file diff --git a/jvm-pdf/src/gradleTest/complex-jvm-setup/build.gradle b/jvm-pdf/src/gradleTest/complex-jvm-setup/build.gradle index 0d61ef1f5..f09ad7438 100644 --- a/jvm-pdf/src/gradleTest/complex-jvm-setup/build.gradle +++ b/jvm-pdf/src/gradleTest/complex-jvm-setup/build.gradle @@ -15,7 +15,7 @@ asciidoctorj { } asciidoctorPdf { - inProcess OUT_OF_PROCESS + executionMode = OUT_OF_PROCESS logDocuments true sourceDir 'src/docs/asciidoc' diff --git a/jvm-pdf/src/gradleTest/complex-jvm-setup/build.gradle.kts b/jvm-pdf/src/gradleTest/complex-jvm-setup/build.gradle.kts index 4151fcfb1..d154c6d64 100644 --- a/jvm-pdf/src/gradleTest/complex-jvm-setup/build.gradle.kts +++ b/jvm-pdf/src/gradleTest/complex-jvm-setup/build.gradle.kts @@ -4,11 +4,10 @@ import org.asciidoctor.gradle.base.process.ProcessMode // tag::using-two-plugins-three-backends[] plugins { id("org.asciidoctor.jvm.pdf") -// id("com.gradle.build-scan") version "1.16" } repositories { - jcenter() + mavenCentral() } asciidoctorj { @@ -17,7 +16,7 @@ asciidoctorj { } tasks.named("asciidoctorPdf") { - inProcess = ProcessMode.OUT_OF_PROCESS + setExecutionMode("OUT_OF_PROCESS") logDocuments = true setSourceDir("src/docs/asciidoc") diff --git a/jvm-pdf/src/intTest/groovy/org/asciidoctor/gradle/jvm/pdf/AsciidoctorPdfTaskCachingFunctionalSpec.groovy b/jvm-pdf/src/intTest/groovy/org/asciidoctor/gradle/jvm/pdf/AsciidoctorPdfTaskCachingFunctionalSpec.groovy index 634450f78..7b4bf03f5 100644 --- a/jvm-pdf/src/intTest/groovy/org/asciidoctor/gradle/jvm/pdf/AsciidoctorPdfTaskCachingFunctionalSpec.groovy +++ b/jvm-pdf/src/intTest/groovy/org/asciidoctor/gradle/jvm/pdf/AsciidoctorPdfTaskCachingFunctionalSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,16 @@ package org.asciidoctor.gradle.jvm.pdf import org.asciidoctor.gradle.jvm.pdf.internal.FunctionalSpecification -import org.asciidoctor.gradle.testfixtures.CachingTest +import org.asciidoctor.gradle.testfixtures.BuildScanFixture +import org.asciidoctor.gradle.testfixtures.CachingTestFixture import spock.lang.PendingFeature /** AsciidoctorPdfTaskCachingFunctionalSpec * * @author Gary Hale */ -class AsciidoctorPdfTaskCachingFunctionalSpec extends FunctionalSpecification implements CachingTest { +class AsciidoctorPdfTaskCachingFunctionalSpec extends FunctionalSpecification + implements CachingTestFixture, BuildScanFixture { static final String DEFAULT_TASK = 'asciidoctorPdf' static final String DEFAULT_OUTPUT_FILE = 'build/docs/asciidocPdf/sample.pdf' @@ -54,7 +56,8 @@ class AsciidoctorPdfTaskCachingFunctionalSpec extends FunctionalSpecification im outputFileInRelocatedDirectory.exists() } - @PendingFeature // TODO: Come back and fix caching + @PendingFeature + // TODO: Come back and fix caching void "PDF task is not cached when pdf-specific inputs change"() { given: getBuildFile(""" @@ -80,8 +83,8 @@ class AsciidoctorPdfTaskCachingFunctionalSpec extends FunctionalSpecification im when: file('src/docs/themes/pdf-theme').mkdirs() file('src/docs/themes/pdf-theme/basic-theme.yml').text = - file('src/docs/asciidoc/pdf-theme/basic-theme.yml').text - .replace('333333', '333334') + file('src/docs/asciidoc/pdf-theme/basic-theme.yml').text + .replace('333333', '333334') changeBuildConfigurationTo(""" pdfThemes { @@ -109,18 +112,9 @@ class AsciidoctorPdfTaskCachingFunctionalSpec extends FunctionalSpecification im } File getBuildFile(String extraContent) { - File buildFile = testProjectDir.newFile('build.gradle') - buildFile << """ - plugins { - id 'org.asciidoctor.jvm.pdf' - } - - ${ -> scan ? buildScanConfiguration : '' } - ${offlineRepositories} - - ${extraContent} - """ - buildFile + writeGroovyBuildFile('org.asciidoctor.jvm.pdf', extraContent).withWriterAppend { w -> + w.println(performBuildScan ? buildScanConfiguration : '') + } } String getDefaultTask() { diff --git a/jvm-pdf/src/intTest/groovy/org/asciidoctor/gradle/jvm/pdf/AsciidoctorPdfTaskFunctionalSpec.groovy b/jvm-pdf/src/intTest/groovy/org/asciidoctor/gradle/jvm/pdf/AsciidoctorPdfTaskFunctionalSpec.groovy index f9767a95c..1d7b29678 100644 --- a/jvm-pdf/src/intTest/groovy/org/asciidoctor/gradle/jvm/pdf/AsciidoctorPdfTaskFunctionalSpec.groovy +++ b/jvm-pdf/src/intTest/groovy/org/asciidoctor/gradle/jvm/pdf/AsciidoctorPdfTaskFunctionalSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,7 +55,7 @@ asciidoctorPdf { then: verifyAll { - new File(testProjectDir.root, DEFAULT_OUTPUT_FILE).exists() + new File(projectDir, DEFAULT_OUTPUT_FILE).exists() } where: @@ -91,7 +91,7 @@ asciidoctorPdf { combination.compatible ? runner.build() : runner.buildAndFail() then: - combination.compatible && new File(testProjectDir.root, DEFAULT_OUTPUT_FILE).exists() || !combination.compatible + combination.compatible && new File(projectDir, DEFAULT_OUTPUT_FILE).exists() || !combination.compatible where: combination << PdfBackendJRubyAsciidoctorJCombinationGenerator.get() @@ -103,7 +103,7 @@ asciidoctorPdf { asciidoctorPdf { sourceDir 'src/docs/asciidoc' - inProcess = JAVA_EXEC + executionMode = JAVA_EXEC } """) @@ -112,7 +112,7 @@ asciidoctorPdf { then: verifyAll { - new File(testProjectDir.root, DEFAULT_OUTPUT_FILE).exists() + new File(projectDir, DEFAULT_OUTPUT_FILE).exists() } } @@ -137,7 +137,7 @@ asciidoctorPdf { then: verifyAll { - new File(testProjectDir.root, DEFAULT_OUTPUT_FILE).exists() + new File(projectDir, DEFAULT_OUTPUT_FILE).exists() } } @@ -156,6 +156,7 @@ asciidoctorPdf { sourceDir 'src/docs/asciidoc' fontsDirs 'src/docs/asciidoc/pdf-theme', file('src/docs/asciidoc/path') fontsDirs 'src/docs/asciidoc/pdf-theme-path' + executionMode = JAVA_EXEC } """) @@ -164,7 +165,7 @@ asciidoctorPdf { then: verifyAll { - new File(testProjectDir.root, DEFAULT_OUTPUT_FILE).exists() + new File(projectDir, DEFAULT_OUTPUT_FILE).exists() } } @@ -182,15 +183,16 @@ asciidoctorPdf { theme 'basic' sourceDir 'src/docs/asciidoc' fontsDirs 'src/docs/asciidoc/pdf-theme' + executionMode = JAVA_EXEC } - """) + """.stripIndent()) when: getGradleRunner([DEFAULT_TASK, '-i']).build() then: verifyAll { - new File(testProjectDir.root, DEFAULT_OUTPUT_FILE).exists() + new File(projectDir, DEFAULT_OUTPUT_FILE).exists() } } @@ -198,22 +200,23 @@ asciidoctorPdf { @Unroll void 'can apply a task configuration rule to set source and output directory (Gradle #gradleVersion)'() { given: - File newSourceDir = new File(testProjectDir.root, 'src/asciidoc') - assert new File(testProjectDir.root, 'src/docs/asciidoc').renameTo(newSourceDir) - testProjectDir.newFile('build.gradle') << """ + File newSourceDir = new File(projectDir, 'src/asciidoc') + assert new File(projectDir, 'src/docs/asciidoc').renameTo(newSourceDir) + buildFile.text = """ plugins { id 'org.asciidoctor.jvm.pdf' apply false } - tasks.withType(org.asciidoctor.gradle.jvm.pdf.AsciidoctorPdfTask) { + tasks.withType(org.asciidoctor.gradle.jvm.pdf.AsciidoctorPdfTask).configureEach { sourceDir = 'src/asciidoc' outputDir = "\${buildDir}/output" + executionMode = JAVA_EXEC } apply plugin: 'org.asciidoctor.jvm.pdf' ${offlineRepositories} - """ + """.stripIndent() when: getGradleRunner([DEFAULT_TASK, '-s']) @@ -222,11 +225,11 @@ asciidoctorPdf { then: verifyAll { - new File(testProjectDir.root, 'build/output/sample.pdf').exists() + new File(projectDir, 'build/output/sample.pdf').exists() } where: - gradleVersion << [latestMinimumOrThis('6.0.1'), latestMinimumOrThis('6.9.1'), GradleTestVersions.MAX_VERSION] + gradleVersion << [latestMinimumOrThis('7.0.1'), GradleTestVersions.MAX_VERSION] } @Issue('https://github.com/asciidoctor/asciidoctor-gradle-plugin/issues/579') @@ -250,17 +253,7 @@ asciidoctorPdf { } File getBuildFile(String extraContent) { - File buildFile = testProjectDir.newFile('build.gradle') - buildFile << """ -plugins { - id 'org.asciidoctor.jvm.pdf' -} - -${offlineRepositories} - -${extraContent} -""" - buildFile + writeGroovyBuildFile('org.asciidoctor.jvm.pdf', extraContent) } String getResolutionStrategy(final String asciidoctorjVer, final String jrubyVer) { diff --git a/jvm-pdf/src/intTest/groovy/org/asciidoctor/gradle/jvm/pdf/internal/FunctionalSpecification.groovy b/jvm-pdf/src/intTest/groovy/org/asciidoctor/gradle/jvm/pdf/internal/FunctionalSpecification.groovy index 3b2a0384e..bcd4c0b81 100644 --- a/jvm-pdf/src/intTest/groovy/org/asciidoctor/gradle/jvm/pdf/internal/FunctionalSpecification.groovy +++ b/jvm-pdf/src/intTest/groovy/org/asciidoctor/gradle/jvm/pdf/internal/FunctionalSpecification.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,18 +18,15 @@ package org.asciidoctor.gradle.jvm.pdf.internal import groovy.transform.CompileStatic import org.apache.commons.io.FileUtils import org.asciidoctor.gradle.testfixtures.DslType +import org.asciidoctor.gradle.testfixtures.FunctionalTestFixture import org.asciidoctor.gradle.testfixtures.FunctionalTestSetup import org.gradle.testkit.runner.GradleRunner -import org.junit.Rule -import org.junit.rules.TemporaryFolder import spock.lang.Specification +import spock.lang.TempDir import static org.asciidoctor.gradle.testfixtures.DslType.GROOVY_DSL -import static org.asciidoctor.gradle.testfixtures.DslType.KOTLIN_DSL -import static org.asciidoctor.gradle.testfixtures.FunctionalTestSetup.getOfflineRepositoriesGroovyDsl -import static org.asciidoctor.gradle.testfixtures.FunctionalTestSetup.getOfflineRepositoriesKotlinDsl -class FunctionalSpecification extends Specification { +class FunctionalSpecification extends Specification implements FunctionalTestFixture { @SuppressWarnings('LineLength') static @@ -37,54 +34,24 @@ class FunctionalSpecification extends Specification { static final String TEST_REPO_DIR = FunctionalTestSetup.offlineRepo.absolutePath - @Rule - TemporaryFolder testProjectDir + @TempDir + File testProjectDir - @Rule - TemporaryFolder alternateProjectDir + File testKitDir + + void setup() { + testKitDir = new File(testProjectDir, ".testkit-${UUID.randomUUID()}") + projectDir.mkdirs() + } @CompileStatic GradleRunner getGradleRunner(List taskNames = ['asciidoctor']) { - FunctionalTestSetup.getGradleRunner(GROOVY_DSL, testProjectDir.root, taskNames) + FunctionalTestSetup.getGradleRunner(GROOVY_DSL, projectDir, taskNames).withTestKitDir(testKitDir) } @SuppressWarnings(['BuilderMethodWithSideEffects']) void createTestProject(String docGroup = 'normal') { - FileUtils.copyDirectory(new File(TEST_PROJECTS_DIR, docGroup), testProjectDir.root) - } - - @CompileStatic - String getOfflineRepositories(DslType dslType = GROOVY_DSL) { - dslType == GROOVY_DSL ? getOfflineRepositoriesGroovyDsl(new File(TEST_REPO_DIR)) : - getOfflineRepositoriesKotlinDsl(new File(TEST_REPO_DIR)) - } - - File getJvmConvertGroovyBuildFile(String extraContent, String plugin = 'org.asciidoctor.jvm.pdf') { - File buildFile = testProjectDir.newFile('build.gradle') - buildFile << """ - plugins { - id '${plugin}' - } - - ${offlineRepositories} - - ${extraContent} - """ - buildFile - } - - File getJvmConvertKotlinBuildFile(String extraContent, String plugin = 'org.asciidoctor.jvm.pdf') { - File buildFile = testProjectDir.newFile('build.gradle.kts') - buildFile << """ - plugins { - id ("${plugin}") - } - - ${getOfflineRepositories(KOTLIN_DSL)} - - ${extraContent} - """ - buildFile + FileUtils.copyDirectory(new File(TEST_PROJECTS_DIR, docGroup), projectDir) } String getDefaultProcessModeForAppveyor(final DslType dslType = GROOVY_DSL) { diff --git a/jvm-pdf/src/main/groovy/org/asciidoctor/gradle/jvm/pdf/AsciidoctorJPdfPlugin.groovy b/jvm-pdf/src/main/groovy/org/asciidoctor/gradle/jvm/pdf/AsciidoctorJPdfPlugin.groovy index a22d8a661..9e835d4d6 100644 --- a/jvm-pdf/src/main/groovy/org/asciidoctor/gradle/jvm/pdf/AsciidoctorJPdfPlugin.groovy +++ b/jvm-pdf/src/main/groovy/org/asciidoctor/gradle/jvm/pdf/AsciidoctorJPdfPlugin.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/jvm-pdf/src/main/groovy/org/asciidoctor/gradle/jvm/pdf/AsciidoctorPdfTask.groovy b/jvm-pdf/src/main/groovy/org/asciidoctor/gradle/jvm/pdf/AsciidoctorPdfTask.groovy index 2fd9e17a3..0345b4766 100644 --- a/jvm-pdf/src/main/groovy/org/asciidoctor/gradle/jvm/pdf/AsciidoctorPdfTask.groovy +++ b/jvm-pdf/src/main/groovy/org/asciidoctor/gradle/jvm/pdf/AsciidoctorPdfTask.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,19 +15,14 @@ */ package org.asciidoctor.gradle.jvm.pdf -import groovy.transform.CompileDynamic import groovy.transform.CompileStatic -import org.asciidoctor.gradle.base.process.ProcessMode import org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask -import org.asciidoctor.gradle.jvm.AsciidoctorJExtension import org.gradle.api.Project import org.gradle.api.UnknownDomainObjectException import org.gradle.api.file.FileCollection -import org.gradle.api.model.ReplacedBy @java.lang.SuppressWarnings('NoWildcardImports') import org.gradle.api.tasks.* import org.gradle.api.tasks.util.PatternSet -import org.gradle.util.GradleVersion import org.gradle.workers.WorkerExecutor import javax.inject.Inject @@ -48,61 +43,29 @@ import static org.ysb33r.grolifant.api.core.TaskInputFileOptions.OPTIONAL class AsciidoctorPdfTask extends AbstractAsciidoctorTask { private String theme - private final List fontDirs = [] + private final List pdfFontDirs = [] @Inject AsciidoctorPdfTask(WorkerExecutor we) { super(we) - configuredOutputOptions.backends = ['pdf'] + outputOptions.backends = ['pdf'] copyNoResources() projectOperations.tasks.inputFiles( inputs, - { -> fontDirs }, + { -> pdfFontDirs }, PathSensitivity.RELATIVE, IGNORE_EMPTY_DIRECTORIES, OPTIONAL ) } - /** @Deprecated Use{@link #getFontsDirs()} instead - * - * @return Pdf font directory as a file - * @throws {@link PdfFontDirException} if there are either multiple directories or no directory for pdf font - * */ - @Deprecated - @ReplacedBy('getFontsDirs') - File getFontsDir() { - if (this.fontDirs.size() > 1) { - throw new PdfFontDirException('There is more than 1 file in the fonts directory') - } - if (this.fontDirs.empty) { - throw new PdfFontDirException('No directory is specified') - } - this.project.file(this.fontDirs.first()) - } - - /** @Deprecated Use{@link #setFontsDirs(java.lang.Iterable)} instead and specify the single directory - * - * Specify a directory where to load custom fonts from. - * - * This will set the {@code pdf-fontsdir} attribute - * - * @param f Directory where custom fonts can be found. anything convertible with {@link Project#file} - * can be used. - */ - @SuppressWarnings('UnnecessarySetter') - @Deprecated - void setFontsDir(Object f) { - setFontsDirs([f]) - } - /** Returns the directories or single directory for the fonts * * @return Directories for the pdf fonts * */ @Internal FileCollection getFontsDirs() { - projectOperations.fsOperations.files(this.fontDirs) + projectOperations.fsOperations.files(this.pdfFontDirs) } /** Specify a directory or directories where to load custom fonts from. @@ -113,8 +76,8 @@ class AsciidoctorPdfTask extends AbstractAsciidoctorTask { * can be used. */ void setFontsDirs(Iterable paths) { - this.fontDirs.clear() - this.fontDirs.addAll(paths) + this.pdfFontDirs.clear() + this.pdfFontDirs.addAll(paths) } /** Add files paths for the custom fonts @@ -123,7 +86,7 @@ class AsciidoctorPdfTask extends AbstractAsciidoctorTask { * */ @SuppressWarnings('UnnecessarySetter') void fontsDirs(Object... f) { - this.fontDirs.addAll(f.toList()) + this.pdfFontDirs.addAll(f.toList()) } /** Set the theme to be used from the {@code pdfThemes} extension. @@ -156,32 +119,33 @@ class AsciidoctorPdfTask extends AbstractAsciidoctorTask { themeDescriptor?.themeName } - /** Selects a final process mode of PDF processing. - * - * If the system is running on Windows with a Gradle version which still has classpath leakage problems - * it will switch to using {@link #JAVA_EXEC}. - * - * @return Process mode to use for execution. - */ - @Override - protected ProcessMode getFinalProcessMode() { - if (GradleVersion.current() <= LAST_GRADLE_WITH_CLASSPATH_LEAKAGE) { - if (inProcess != AbstractAsciidoctorTask.JAVA_EXEC) { - logger.warn 'This version of Gradle leaks snakeyaml on to worker classpaths which breaks ' + - 'PDF processing. Switching to JAVA_EXEC instead.' - } - AbstractAsciidoctorTask.JAVA_EXEC - } else { - super.finalProcessMode - } - } - - /** The default pattern set for secondary sources. +// /** Selects a final process mode of PDF processing. +// * +// * If the system is running on Windows with a Gradle version which still has classpath leakage problems +// * it will switch to using {@link #JAVA_EXEC}. +// * +// * @return Process mode to use for execution. +// */ +// @Override +// protected ProcessMode getFinalProcessMode() { +// if (GradleVersion.current() <= LAST_GRADLE_WITH_CLASSPATH_LEAKAGE) { +// if (inProcess != AbstractAsciidoctorTask.JAVA_EXEC) { +// logger.warn 'This version of Gradle leaks snakeyaml on to worker classpaths which breaks ' + +// 'PDF processing. Switching to JAVA_EXEC instead.' +// } +// AbstractAsciidoctorTask.JAVA_EXEC +// } else { +// super.finalProcessMode +// } +// } + + /** + * The default pattern set for secondary sources. * * @return {@link #getDefaultSourceDocumentPattern} + `*docinfo*`. */ @Override - protected PatternSet getDefaultSecondarySourceDocumentPattern() { + PatternSet getDefaultSecondarySourceDocumentPattern() { PatternSet ps = defaultSourceDocumentPattern ps.include '*-theme.y*ml' ps @@ -197,32 +161,19 @@ class AsciidoctorPdfTask extends AbstractAsciidoctorTask { * * @return A collection of default attributes. */ - @SuppressWarnings('UnnecessaryGetter') @Override - protected Map getTaskSpecificDefaultAttributes(File workingSourceDir) { - Map attrs = super.getTaskSpecificDefaultAttributes(workingSourceDir) - - boolean useOldAttributes = pdfVersion.startsWith('1.5.0-alpha') - - FileCollection fonts = getFontsDirs() - if (!fonts?.empty) { - attrs['pdf-fontsdir'] = fonts.asPath - } - - File styles = themesDir - if (styles != null) { - attrs[useOldAttributes ? 'pdf-stylesdir' : 'pdf-themesdir'] = styles.absolutePath - } - - String selectedTheme = themeName - if (selectedTheme != null) { - attrs[useOldAttributes ? 'pdf-style' : 'pdf-theme'] = selectedTheme - } - + Map getTaskSpecificDefaultAttributes(File workingSourceDir) { + final attrs = super.getTaskSpecificDefaultAttributes(workingSourceDir) + final fonts = fontsDirs + attrs.putAll([ + 'pdf-fontsdir' : fonts.empty ? null : fonts.asPath, + 'pdf-themesdir': themesDir?.absolutePath, + 'pdf-theme' : themeName + ].findAll { k, v -> v != null }) attrs } - @CompileDynamic +// @CompileDynamic private AsciidoctorPdfThemesExtension.PdfThemeDescriptor getThemeDescriptor() { if (this.theme) { AsciidoctorPdfThemesExtension pdfThemes = project.extensions.getByType(AsciidoctorPdfThemesExtension) @@ -231,8 +182,4 @@ class AsciidoctorPdfTask extends AbstractAsciidoctorTask { null } } - - private String getPdfVersion() { - asciidoctorj.modules.pdf.version ?: project.extensions.getByType(AsciidoctorJExtension).modules.pdf.version - } } diff --git a/jvm-pdf/src/main/groovy/org/asciidoctor/gradle/jvm/pdf/AsciidoctorPdfThemesExtension.groovy b/jvm-pdf/src/main/groovy/org/asciidoctor/gradle/jvm/pdf/AsciidoctorPdfThemesExtension.groovy index a2d9d1b74..ac7177623 100644 --- a/jvm-pdf/src/main/groovy/org/asciidoctor/gradle/jvm/pdf/AsciidoctorPdfThemesExtension.groovy +++ b/jvm-pdf/src/main/groovy/org/asciidoctor/gradle/jvm/pdf/AsciidoctorPdfThemesExtension.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,11 +18,15 @@ package org.asciidoctor.gradle.jvm.pdf import groovy.transform.CompileStatic import org.asciidoctor.gradle.base.AbstractDownloadableComponent import org.gradle.api.Project -import org.ysb33r.grolifant.api.v4.StringUtils -/** Easy way to configure themes for Asciidoctor PDF either as local themes or +import java.util.concurrent.Callable + +/** + * Easy way to configure themes for Asciidoctor PDF either as local themes or * as downloadable. * + * @author Schalk W. Cronjé + * * @since 2.0 */ @CompileStatic @@ -30,8 +34,8 @@ class AsciidoctorPdfThemesExtension extends AbstractDownloadableComponent convertible(PdfLocalTheme theme) { return { -> - new PdfThemeDescriptor(StringUtils.stringize(theme.themeName), project.file(theme.themeDir)) + new PdfThemeDescriptor( + projectOperations.stringTools.stringize(theme.themeName), + projectOperations.fsOperations.file(theme.themeDir) + ) } } diff --git a/jvm-pdf/src/main/groovy/org/asciidoctor/gradle/jvm/pdf/PdfFontDirException.groovy b/jvm-pdf/src/main/groovy/org/asciidoctor/gradle/jvm/pdf/PdfFontDirException.groovy index e7dd3c568..2ac2b5558 100644 --- a/jvm-pdf/src/main/groovy/org/asciidoctor/gradle/jvm/pdf/PdfFontDirException.groovy +++ b/jvm-pdf/src/main/groovy/org/asciidoctor/gradle/jvm/pdf/PdfFontDirException.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/jvm-pdf/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.jvm.pdf.properties b/jvm-pdf/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.jvm.pdf.properties deleted file mode 100644 index 7d607e1df..000000000 --- a/jvm-pdf/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.jvm.pdf.properties +++ /dev/null @@ -1,17 +0,0 @@ -# -# Copyright 2013-2023 the original author or authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://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. -# - -implementation-class=org.asciidoctor.gradle.jvm.pdf.AsciidoctorJPdfPlugin \ No newline at end of file diff --git a/jvm-pdf/src/test/groovy/org/asciidoctor/gradle/jvm/pdf/AsciidoctorPdfThemeExtensionSpec.groovy b/jvm-pdf/src/test/groovy/org/asciidoctor/gradle/jvm/pdf/AsciidoctorPdfThemeExtensionSpec.groovy index d5d465636..07c66a50b 100644 --- a/jvm-pdf/src/test/groovy/org/asciidoctor/gradle/jvm/pdf/AsciidoctorPdfThemeExtensionSpec.groovy +++ b/jvm-pdf/src/test/groovy/org/asciidoctor/gradle/jvm/pdf/AsciidoctorPdfThemeExtensionSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,11 +19,10 @@ import org.gradle.api.Project import org.gradle.api.UnknownDomainObjectException import org.gradle.testfixtures.ProjectBuilder import org.ysb33r.grolifant.api.core.ProjectOperations -import org.ysb33r.grolifant.api.v4.FileUtils import spock.lang.Specification /** - * @uathor Schalk W.Cronjé + * @author Schalk W.Cronjé */ class AsciidoctorPdfThemeExtensionSpec extends Specification { @@ -106,7 +105,8 @@ class AsciidoctorPdfThemeExtensionSpec extends Specification { } File determineUnpackedDir(String cacheSubDir, String pattern) { - File baseDir = project.file("${project.buildDir}/${cacheSubDir}/foo/bar/${pattern}") - FileUtils.listDirs(baseDir)[0] + File baseDir = project.file("${project.buildDir}/cloud-archives/${cacheSubDir}/foo/bar/${pattern}") + final x = projectOperations.fsOperations.listDirs(baseDir)[0] + x } } \ No newline at end of file diff --git a/jvm/build.gradle b/jvm/build.gradle index a278c6603..49e77d4a5 100644 --- a/jvm/build.gradle +++ b/jvm/build.gradle @@ -1,3 +1,5 @@ +import org.ysb33r.gradle.gradletest.GradleTest + /* * Copyright 2013-2019 the original author or authors. * @@ -14,11 +16,28 @@ * limitations under the License. */ -configurations { - intTestOfflineRepo.extendsFrom compileOnly - intTestOfflineRepo2 +agProject { + withOfflineTestConfigurations() + + configurePlugin( + 'org.asciidoctor.jvm.base', + 'AsciidoctorJ Base Plugin', + 'Base plugin for all AsciidoctorJ tasks & extensions. Provides the asciidoctorj project extension', + 'org.asciidoctor.gradle.jvm.AsciidoctorJBasePlugin', + ['asciidoctorj'] + ) + + configurePlugin( + 'org.asciidoctor.jvm.convert', + 'AsciidoctorJ General Purpose Document Conversion Plugin', + 'Provides asciidoctor task and conventions using the asciidoctorj engine', + 'org.asciidoctor.gradle.jvm.AsciidoctorJPlugin', + ['asciidoctorj', 'html5', 'docbook'] + ) } +apply from: "${rootDir}/gradle/remote-tests.gradle" + dependencies { compileOnly "org.asciidoctor:asciidoctorj:${compileOnlyAsciidoctorJVersion}" compileOnly "org.asciidoctor:asciidoctorj-groovy-dsl:${compileOnlyGroovyDslVersion}", { @@ -26,7 +45,8 @@ dependencies { } api project(':asciidoctor-gradle-base') - + implementation("org.ysb33r.gradle:grolifant-rawhide:${agProject.versionOf('grolifant')}") + runtimeOnly "org.asciidoctor:asciidoctorj-api:${compileOnlyAsciidoctorJVersion}" // These three are used in the compatibility tests for extensions intTestOfflineRepo "org.codehaus.groovy:groovy:${GroovySystem.version}" intTestOfflineRepo "org.codehaus.groovy:groovy-ant:${GroovySystem.version}" @@ -39,92 +59,42 @@ dependencies { testRuntimeOnly "org.asciidoctor:asciidoctorj-groovy-dsl:${compileOnlyGroovyDslVersion}", { exclude module: 'groovy-all' } -} -configurePlugin 'org.asciidoctor.jvm.base', - 'AsciidoctorJ Base Plugin', - 'Base plugin for all AsciidoctorJ tasks & extensions. Provides the asciidoctorj project extension.', - ['asciidoctorj'] + remoteTestImplementation localGroovy() + remoteTestImplementation gradleTestKit() + remoteTestImplementation "org.asciidoctor:asciidoctorj:${compileOnlyAsciidoctorJVersion}" + remoteTestImplementation project(':testfixtures-jvm') + remoteTestRuntimeOnly "org.asciidoctor:asciidoctorj-groovy-dsl:${compileOnlyGroovyDslVersion}", { + exclude module: 'groovy-all' + } +} -configurePlugin 'org.asciidoctor.jvm.convert', - 'AsciidoctorJ General Purpose Document Conversion Plugin', - 'Provides asciidoctor task and conventions', - ['asciidoctorj', 'html5', 'docbook'] +generateModuleVersions { + basename = 'asciidoctorj-extension' + propertyNames ~/^asciidoctorj$/, ~/^asciidoctorj\./ +} -test { - systemProperties ROOT_PROJECT_DIR: rootProject.projectDir.absolutePath +tasks.named('test', Test) { + systemProperties ROOT_PROJECT_DIR: rootDir.absolutePath } -intTest { +tasks.named('intTest', Test) { systemProperties TEST_PROJECTS_DIR: file('src/intTest/projects') + maxParallelForks = 4 + forkEvery = 7 } // Compile the extensions compatibility test with the same version of // AsciidoctorJ & Groovy version that is used in the project -gradleTest { +tasks.named('gradleTest', GradleTest) { systemProperties ASCIIDOCTORJ_VERSION: compileOnlyAsciidoctorJVersion systemProperties GROOVY_VERSION: GroovySystem.version gradleArguments '-i', '-s' } -// Adding a third test set so that remote execution can be tested using a separate classpath -// and conditions -sourceSets { - remoteTest { - compileClasspath = sourceSets.main.output + configurations.remoteTestCompile - runtimeClasspath = output + compileClasspath + configurations.remoteTestRuntime - } -} - -generateModuleVersions { - basename = 'asciidoctorj-extension' - propertyNames ~/^asciidoctorj$/, ~/^asciidoctorj\./ -} - -dependencies { - remoteTestCompile "org.asciidoctor:asciidoctorj:${compileOnlyAsciidoctorJVersion}" - remoteTestCompile project(':testfixtures-jvm') - remoteTestRuntime "org.asciidoctor:asciidoctorj-groovy-dsl:${compileOnlyGroovyDslVersion}", { - exclude module: 'groovy-all' - } -} - -task remoteTest(type: Test) { - description = "Run tests for the remote-execution code" - group = "verification" - - mustRunAfter "test" - inputs.files sourceSets.main.output.classesDirs - inputs.files sourceSets.main.output.resourcesDir - - testClassesDirs = sourceSets.remoteTest.output.classesDirs - classpath = sourceSets.remoteTest.runtimeClasspath - - dependsOn ':testfixtures-offline-repo:buildOfflineRepositories' - systemProperties OFFLINE_REPO: offlineRepoRoot.absolutePath -} - -check { +tasks.named('check') { dependsOn remoteTest } -pluginManager.withPlugin('jacoco') { - jacocoTestReport { - executionData remoteTest - mustRunAfter remoteTest - } -} -// This makes it easier to edit the projects in IntelliJ -//sourceSets { -// gradleTestProjectExtension { -// groovy { -// srcDirs = ['src/gradleTest/extension-in-subproject/extension/src/main/groovy'] -// } -// } -//} -// -//configurations { -// gradleTestProjectExtensionCompileOnly.extendsFrom compileOnly -//} diff --git a/jvm/src/gradleTest/complex-jvm-setup/build.gradle.kts b/jvm/src/gradleTest/complex-jvm-setup/build.gradle.kts index 2b9f354b9..fa9fd541a 100644 --- a/jvm/src/gradleTest/complex-jvm-setup/build.gradle.kts +++ b/jvm/src/gradleTest/complex-jvm-setup/build.gradle.kts @@ -8,7 +8,7 @@ plugins { } repositories { - jcenter() + mavenCentral() } asciidoctorj { diff --git a/jvm/src/gradleTest/extension-in-subproject/build.gradle b/jvm/src/gradleTest/extension-in-subproject/build.gradle index ba961d6d9..2e06e0138 100644 --- a/jvm/src/gradleTest/extension-in-subproject/build.gradle +++ b/jvm/src/gradleTest/extension-in-subproject/build.gradle @@ -19,11 +19,18 @@ allprojects { } task runGradleTest { - dependsOn ':extension:build', ':docs-using-configuration:asciidoctor', ':docs-using-configuration-in-process:asciidoctor' - dependsOn ':docs-using-project-direct:asciidoctor' + dependsOn ':extension:build' - doLast { - assert file("${project(':docs-using-configuration').buildDir}/docs/asciidoc/sample.html").text.contains('and write this in lowercase') - assert file("${project(':docs-using-configuration-in-process').buildDir}/docs/asciidoc/sample.html").text.contains('and write this in lowercase') + [ + // TODO: Fix supplying of asciidoc extensions via configurations +// ':docs-using-configuration', +// ':docs-using-configuration-in-process', + ':docs-using-project-direct' + ].each { p -> + dependsOn "${p}:asciidoctor" + + doLast { + assert file("${project(p).buildDir}/docs/asciidoc/sample.html").text.contains('and write this in lowercase') + } } } \ No newline at end of file diff --git a/jvm/src/gradleTest/extension-in-subproject/docs-using-configuration-in-process/build.gradle b/jvm/src/gradleTest/extension-in-subproject/docs-using-configuration-in-process/build.gradle index 532525a39..5c4072dbf 100644 --- a/jvm/src/gradleTest/extension-in-subproject/docs-using-configuration-in-process/build.gradle +++ b/jvm/src/gradleTest/extension-in-subproject/docs-using-configuration-in-process/build.gradle @@ -1,7 +1,12 @@ -apply plugin: 'org.asciidoctor.jvm.convert' +plugins { + id 'org.asciidoctor.jvm.convert' +} configurations { - asciidocExt + asciidocExt { + canBeResolved = true + canBeConsumed = false + } } dependencies { @@ -12,17 +17,7 @@ asciidoctorj { logLevel 'DEBUG' } -asciidoctor { +tasks.named('asciidoctor') { configurations 'asciidocExt' - inProcess IN_PROCESS + executionMode = 'CLASSPATH' } - - -// Gradle leak unnecessary JARs onto the classpath. Since this project is now -// built with Gradle 5.x, we are swapping back to JAVA_EXEC if testing against -// Gradle 4.x -if (gradle.gradleVersion.startsWith('4.')) { - asciidoctor { - inProcess JAVA_EXEC - } -} \ No newline at end of file diff --git a/jvm/src/gradleTest/extension-in-subproject/docs-using-configuration/build.gradle b/jvm/src/gradleTest/extension-in-subproject/docs-using-configuration/build.gradle index 564e1bdda..4eb4fdf36 100644 --- a/jvm/src/gradleTest/extension-in-subproject/docs-using-configuration/build.gradle +++ b/jvm/src/gradleTest/extension-in-subproject/docs-using-configuration/build.gradle @@ -1,7 +1,12 @@ -apply plugin : 'org.asciidoctor.jvm.convert' +plugins { + id 'org.asciidoctor.jvm.convert' +} configurations { - asciidocExt + asciidocExt { + canBeResolved = true + canBeConsumed = false + } } dependencies { @@ -14,5 +19,5 @@ asciidoctorj { asciidoctor { configurations 'asciidocExt' - inProcess JAVA_EXEC + executionMode = JAVA_EXEC } diff --git a/jvm/src/gradleTest/extension-in-subproject/docs-using-project-direct/build.gradle b/jvm/src/gradleTest/extension-in-subproject/docs-using-project-direct/build.gradle index c3046d1f6..35784b2e8 100644 --- a/jvm/src/gradleTest/extension-in-subproject/docs-using-project-direct/build.gradle +++ b/jvm/src/gradleTest/extension-in-subproject/docs-using-project-direct/build.gradle @@ -1,4 +1,6 @@ -apply plugin : 'org.asciidoctor.jvm.convert' +plugins { + id 'org.asciidoctor.jvm.convert' +} repositories { mavenCentral() @@ -9,5 +11,5 @@ asciidoctorj { } asciidoctor { - inProcess JAVA_EXEC + executionMode = 'JAVA_EXEC' } diff --git a/jvm/src/intTest/groovy/org/asciidoctor/gradle/internal/FunctionalSpecification.groovy b/jvm/src/intTest/groovy/org/asciidoctor/gradle/internal/FunctionalSpecification.groovy index 3885c0b74..220094faf 100644 --- a/jvm/src/intTest/groovy/org/asciidoctor/gradle/internal/FunctionalSpecification.groovy +++ b/jvm/src/intTest/groovy/org/asciidoctor/gradle/internal/FunctionalSpecification.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,18 +18,16 @@ package org.asciidoctor.gradle.internal import groovy.transform.CompileStatic import org.apache.commons.io.FileUtils import org.asciidoctor.gradle.testfixtures.DslType +import org.asciidoctor.gradle.testfixtures.FunctionalTestFixture import org.asciidoctor.gradle.testfixtures.FunctionalTestSetup import org.gradle.testkit.runner.GradleRunner -import org.junit.Rule -import org.junit.rules.TemporaryFolder import spock.lang.Specification +import spock.lang.TempDir import static org.asciidoctor.gradle.testfixtures.DslType.GROOVY_DSL import static org.asciidoctor.gradle.testfixtures.DslType.KOTLIN_DSL -import static org.asciidoctor.gradle.testfixtures.FunctionalTestSetup.getOfflineRepositoriesGroovyDsl -import static org.asciidoctor.gradle.testfixtures.FunctionalTestSetup.getOfflineRepositoriesKotlinDsl -class FunctionalSpecification extends Specification { +class FunctionalSpecification extends Specification implements FunctionalTestFixture { public static final String TEST_PROJECTS_DIR = System.getProperty( 'TEST_PROJECTS_DIR', @@ -38,31 +36,34 @@ class FunctionalSpecification extends Specification { public static final String TEST_REPO_DIR = FunctionalTestSetup.offlineRepo.absolutePath - @Rule - TemporaryFolder testProjectDir + @TempDir + File testProjectDir - @Rule - TemporaryFolder alternateProjectDir + File testKitDir + + void setup() { + projectDir.mkdirs() + testKitDir = new File(testProjectDir, ".testkit-${UUID.randomUUID()}") + } + + void cleanup() { + if (testKitDir && testKitDir.exists()) { + testKitDir.deleteDir() + } + } @CompileStatic GradleRunner getGradleRunner(List taskNames = ['asciidoctor']) { - FunctionalTestSetup.getGradleRunner(GROOVY_DSL, testProjectDir.root, taskNames) + getGroovyGradleRunner(taskNames).withTestKitDir(testKitDir) } @SuppressWarnings(['BuilderMethodWithSideEffects']) void createTestProject(String docGroup = 'normal') { File srcDir = new File(TEST_PROJECTS_DIR, docGroup).absoluteFile - FileUtils.copyDirectory(srcDir, testProjectDir.root) - } - - @CompileStatic - String getOfflineRepositories(DslType dslType = GROOVY_DSL) { - dslType == GROOVY_DSL ? getOfflineRepositoriesGroovyDsl(new File(TEST_REPO_DIR)) : - getOfflineRepositoriesKotlinDsl(new File(TEST_REPO_DIR)) + FileUtils.copyDirectory(srcDir, projectDir) } File getJvmConvertGroovyBuildFile(String extraContent, String plugin = 'org.asciidoctor.jvm.convert') { - File buildFile = testProjectDir.newFile('build.gradle') buildFile << """ plugins { id '${plugin}' @@ -76,8 +77,7 @@ class FunctionalSpecification extends Specification { } File getJvmConvertKotlinBuildFile(String extraContent, String plugin = 'org.asciidoctor.jvm.convert') { - File buildFile = testProjectDir.newFile('build.gradle.kts') - buildFile << """ + buildFileKts << """ plugins { id ("${plugin}") } @@ -86,7 +86,7 @@ class FunctionalSpecification extends Specification { ${extraContent} """ - buildFile + buildFileKts } String getDefaultProcessModeForAppveyor(final DslType dslType = GROOVY_DSL) { diff --git a/jvm/src/intTest/groovy/org/asciidoctor/gradle/jvm/AsciidoctorTaskCachingFunctionalSpec.groovy b/jvm/src/intTest/groovy/org/asciidoctor/gradle/jvm/AsciidoctorTaskCachingFunctionalSpec.groovy index 019f490b2..74062f778 100644 --- a/jvm/src/intTest/groovy/org/asciidoctor/gradle/jvm/AsciidoctorTaskCachingFunctionalSpec.groovy +++ b/jvm/src/intTest/groovy/org/asciidoctor/gradle/jvm/AsciidoctorTaskCachingFunctionalSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,19 +16,23 @@ package org.asciidoctor.gradle.jvm import org.asciidoctor.gradle.internal.FunctionalSpecification -import org.asciidoctor.gradle.testfixtures.CachingTest +import org.asciidoctor.gradle.testfixtures.BuildScanFixture +import org.asciidoctor.gradle.testfixtures.CachingTestFixture +import org.asciidoctor.gradle.testfixtures.FunctionalTestFixture import spock.lang.Issue import static org.asciidoctor.gradle.testfixtures.AsciidoctorjTestVersions.SERIES_20 import static org.asciidoctor.gradle.testfixtures.JRubyTestVersions.AJ20_ABSOLUTE_MINIMUM import static org.asciidoctor.gradle.testfixtures.JRubyTestVersions.AJ20_SAFE_MAXIMUM -/** AsciidoctorTaskCachingFunctionalSpec +/** + * AsciidoctorTaskCachingFunctionalSpec * * @author Eric Haag * @author Gary Hale */ -class AsciidoctorTaskCachingFunctionalSpec extends FunctionalSpecification implements CachingTest { +class AsciidoctorTaskCachingFunctionalSpec extends FunctionalSpecification + implements CachingTestFixture, FunctionalTestFixture, BuildScanFixture { public static final String DEFAULT_TASK = 'asciidoctor' public static final String DEFAULT_OUTPUT_FILE = 'build/docs/asciidoc/html5/sample.html' public static final String DOCBOOK_OUTPUT_FILE = 'build/docs/asciidoc/docbook/sample.xml' @@ -67,36 +71,36 @@ class AsciidoctorTaskCachingFunctionalSpec extends FunctionalSpecification imple fileInRelocatedDirectory(DOCBOOK_OUTPUT_FILE).exists() } - @Issue('https://github.com/asciidoctor/asciidoctor-gradle-plugin/issues/671') - void "asciidoctor task is cacheable and relocatable when gemPaths is configured"() { - given: - getBuildFile(""" - asciidoctorj { - gemPaths 'gems1', 'gems2' - } - - asciidoctor { - sourceDir 'src/docs/asciidoc' - - outputOptions { - backends 'html5', 'docbook' - } - } - """) - - when: - assertDefaultTaskExecutes() - - then: - outputFile.exists() - - when: - assertDefaultTaskIsCachedAndRelocatable() - - then: - outputFile.exists() - outputFileInRelocatedDirectory.exists() - } +// @Issue('https://github.com/asciidoctor/asciidoctor-gradle-plugin/issues/671') +// void "asciidoctor task is cacheable and relocatable when gemPaths is configured"() { +// given: +// getBuildFile(""" +// asciidoctorj { +// gemPaths 'gems1', 'gems2' +// } +// +// asciidoctor { +// sourceDir 'src/docs/asciidoc' +// +// outputOptions { +// backends 'html5', 'docbook' +// } +// } +// """) +// +// when: +// assertDefaultTaskExecutes() +// +// then: +// outputFile.exists() +// +// when: +// assertDefaultTaskIsCachedAndRelocatable() +// +// then: +// outputFile.exists() +// outputFileInRelocatedDirectory.exists() +// } void "Asciidoctor task is cached when only output directory is changed"() { given: @@ -285,7 +289,7 @@ class AsciidoctorTaskCachingFunctionalSpec extends FunctionalSpecification imple File getBuildFile(String extraContent) { getJvmConvertGroovyBuildFile(""" - ${scan ? buildScanConfiguration : ''} + ${performBuildScan ? buildScanConfiguration : ''} asciidoctorj { jrubyVersion = '${AJ20_SAFE_MAXIMUM}' diff --git a/jvm/src/intTest/groovy/org/asciidoctor/gradle/jvm/AsciidoctorTaskFunctionalSpec.groovy b/jvm/src/intTest/groovy/org/asciidoctor/gradle/jvm/AsciidoctorTaskFunctionalSpec.groovy index d27d3f279..198bb87ef 100644 --- a/jvm/src/intTest/groovy/org/asciidoctor/gradle/jvm/AsciidoctorTaskFunctionalSpec.groovy +++ b/jvm/src/intTest/groovy/org/asciidoctor/gradle/jvm/AsciidoctorTaskFunctionalSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -71,12 +71,12 @@ class AsciidoctorTaskFunctionalSpec extends FunctionalSpecification { then: 'u content is generated as HTML and XML' verifyAll { - new File(testProjectDir.root, 'build/docs/asciidoc/html5/sample.html').exists() || !compatible - new File(testProjectDir.root, 'build/docs/asciidoc/html5/subdir/sample2.html').exists() || !compatible - new File(testProjectDir.root, 'build/docs/asciidoc/docbook/sample.xml').exists() || !compatible - !new File(testProjectDir.root, 'build/docs/asciidoc/docinfo/docinfo.xml').exists() || !compatible - !new File(testProjectDir.root, 'build/docs/asciidoc/docinfo/sample-docinfo.xml').exists() || !compatible - !new File(testProjectDir.root, 'build/docs/asciidoc/html5/subdir/_include.html').exists() || !compatible + new File(buildDir, 'docs/asciidoc/html5/sample.html').exists() || !compatible + new File(buildDir, 'docs/asciidoc/html5/subdir/sample2.html').exists() || !compatible + new File(buildDir, 'docs/asciidoc/docbook/sample.xml').exists() || !compatible + !new File(buildDir, 'docs/asciidoc/docinfo/docinfo.xml').exists() || !compatible + !new File(buildDir, 'docs/asciidoc/docinfo/sample-docinfo.xml').exists() || !compatible + !new File(buildDir, 'docs/asciidoc/html5/subdir/_include.html').exists() || !compatible } where: @@ -142,7 +142,7 @@ class AsciidoctorTaskFunctionalSpec extends FunctionalSpecification { } asciidoctor { - inProcess = ${processMode} + executionMode = ${processMode} outputOptions { backends 'html5' @@ -172,7 +172,7 @@ class AsciidoctorTaskFunctionalSpec extends FunctionalSpecification { when: getGradleRunner(DEFAULT_ARGS).build() - String sample2 = new File(testProjectDir.root, 'build/docs/asciidoc/subdir/sample2.html').text + String sample2 = new File(buildDir, 'docs/asciidoc/subdir/sample2.html').text then: sample2.contains('gradle-relative-srcdir = [..]') @@ -205,7 +205,7 @@ class AsciidoctorTaskFunctionalSpec extends FunctionalSpecification { given: getBuildFile(''' asciidoctor { - inProcess = JAVA_EXEC + executionMode = JAVA_EXEC outputOptions { backends = ['html5', 'abc', 'xyz'] } @@ -236,7 +236,7 @@ class AsciidoctorTaskFunctionalSpec extends FunctionalSpecification { getGradleRunner(DEFAULT_ARGS).withDebug(true).build() then: - new File(testProjectDir.root, 'build/docs/asciidoc/sample.html').text + new File(buildDir, 'docs/asciidoc/sample.html').text .contains('') } diff --git a/jvm/src/intTest/groovy/org/asciidoctor/gradle/jvm/ExtensionsFunctionalSpec.groovy b/jvm/src/intTest/groovy/org/asciidoctor/gradle/jvm/ExtensionsFunctionalSpec.groovy index edbd54381..d4e3d43e8 100644 --- a/jvm/src/intTest/groovy/org/asciidoctor/gradle/jvm/ExtensionsFunctionalSpec.groovy +++ b/jvm/src/intTest/groovy/org/asciidoctor/gradle/jvm/ExtensionsFunctionalSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,33 +47,33 @@ class ExtensionsFunctionalSpec extends FunctionalSpecification { given: getBuildFile( model.processMode, model.version, """ -asciidoctor { - asciidoctorj { - docExtensions { - block(name: "BIG", contexts: [":paragraph"]) { - parent, reader, attributes -> - def upperLines = reader.readLines()*.toUpperCase() - .inject("") {a, b -> a + '\\\\n' + b} - - createBlock(parent, "paragraph", [upperLines], attributes, [:]) - } - block("small") { - parent, reader, attributes -> - def lowerLines = reader.readLines()*.toLowerCase() - .inject("") {a, b -> a + '\\\\n' + b} - - createBlock(parent, "paragraph", [lowerLines], attributes, [:]) + asciidoctor { + asciidoctorj { + docExtensions { + block(name: "BIG", contexts: [":paragraph"]) { + parent, reader, attributes -> + def upperLines = reader.readLines()*.toUpperCase() + .inject("") {a, b -> a + '\\\\n' + b} + + createBlock(parent, "paragraph", [upperLines], attributes, [:]) + } + block("small") { + parent, reader, attributes -> + def lowerLines = reader.readLines()*.toLowerCase() + .inject("") {a, b -> a + '\\\\n' + b} + + createBlock(parent, "paragraph", [lowerLines], attributes, [:]) + } + } + } } - } - } -} -""") + """.stripIndent()) GradleRunner runner = getGradleRunner(DEFAULT_ARGS) when: runner.build() - File resultFile = new File(testProjectDir.root, "build/docs/asciidoc/${ASCIIDOC_INLINE_EXTENSIONS_FILE.replaceFirst('asciidoc', 'html')}") + File resultFile = new File(buildDir, "docs/asciidoc/${ASCIIDOC_INLINE_EXTENSIONS_FILE.replaceFirst('asciidoc', 'html')}") then: 'content is generated as HTML and XML' resultFile.exists() @@ -121,7 +121,7 @@ block('small') { when: runner.build() - File resultFile = new File(testProjectDir.root, "build/docs/asciidoc/${ASCIIDOC_INLINE_EXTENSIONS_FILE.replaceFirst('asciidoc', 'html')}") + File resultFile = new File(buildDir, "docs/asciidoc/${ASCIIDOC_INLINE_EXTENSIONS_FILE.replaceFirst('asciidoc', 'html')}") then: 'content is generated as HTML and XML' resultFile.exists() @@ -152,7 +152,7 @@ asciidoctor { when: runner.build() - File resultFile = new File(testProjectDir.root, "build/docs/asciidoc/${ASCIIDOC_INLINE_EXTENSIONS_FILE.replaceFirst('asciidoc', 'html')}") + File resultFile = new File(buildDir, "docs/asciidoc/${ASCIIDOC_INLINE_EXTENSIONS_FILE.replaceFirst('asciidoc', 'html')}") then: 'content is generated as HTML and XML' resultFile.exists() @@ -181,7 +181,7 @@ asciidoctor { ''') GradleRunner runner = getGradleRunner(DEFAULT_ARGS) - File outputFile = new File(testProjectDir.root, 'build/docs/asciidoc/inlineextensions.html') + File outputFile = new File(buildDir, 'docs/asciidoc/inlineextensions.html') when: BuildResult firstInvocationResult = runner.build() @@ -192,7 +192,7 @@ asciidoctor { outputFile.text.startsWith('Hi, Mom') when: - new File(testProjectDir.root, 'src/docs/asciidoc/inlineextensions.asciidoc') << 'changes' + new File(projectDir, 'src/docs/asciidoc/inlineextensions.asciidoc') << 'changes' final BuildResult secondInvocationResult = getGradleRunner(DEFAULT_ARGS).build() then: @@ -255,8 +255,8 @@ asciidoctor { when: runner.build() File resultFile = new File( - testProjectDir.root, - 'build/docs/asciidoc/' + ASCIIDOC_INLINE_EXTENSIONS_FILE.replaceFirst('asciidoc', 'html') + buildDir, + 'docs/asciidoc/' + ASCIIDOC_INLINE_EXTENSIONS_FILE.replaceFirst('asciidoc', 'html') ) then: 'content is generated as HTML and XML' @@ -290,7 +290,7 @@ asciidoctor { ${configureGlobally ? versionConfig : ''} asciidoctor { - inProcess ${processMode} + executionMode ${processMode} sourceDir 'src/docs/asciidoc' sources { include '${ASCIIDOC_INLINE_EXTENSIONS_FILE}' diff --git a/jvm/src/intTest/groovy/org/asciidoctor/gradle/jvm/MultiLanguageFunctionalSpec.groovy b/jvm/src/intTest/groovy/org/asciidoctor/gradle/jvm/MultiLanguageFunctionalSpec.groovy index 133141637..a60be893d 100644 --- a/jvm/src/intTest/groovy/org/asciidoctor/gradle/jvm/MultiLanguageFunctionalSpec.groovy +++ b/jvm/src/intTest/groovy/org/asciidoctor/gradle/jvm/MultiLanguageFunctionalSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,10 +55,10 @@ class MultiLanguageFunctionalSpec extends FunctionalSpecification { then: verifyAll { - new File(testProjectDir.root, 'build/docs/asciidoc/en/sample.html').exists() - new File(testProjectDir.root, 'build/docs/asciidoc/es/sample.html').exists() - new File(testProjectDir.root, 'build/docs/asciidoc/en/images/fake.txt').exists() - new File(testProjectDir.root, 'build/docs/asciidoc/es/images/fake.txt').exists() + new File(buildDir, 'docs/asciidoc/en/sample.html').exists() + new File(buildDir, 'docs/asciidoc/es/sample.html').exists() + new File(buildDir, 'docs/asciidoc/en/images/fake.txt').exists() + new File(buildDir, 'docs/asciidoc/es/images/fake.txt').exists() } } @@ -83,12 +83,12 @@ class MultiLanguageFunctionalSpec extends FunctionalSpecification { then: verifyAll { - new File(testProjectDir.root, 'build/docs/asciidoc/en/html5/sample.html').exists() - new File(testProjectDir.root, 'build/docs/asciidoc/es/html5/sample.html').exists() - new File(testProjectDir.root, 'build/docs/asciidoc/en/html5/images/fake.txt').exists() - new File(testProjectDir.root, 'build/docs/asciidoc/es/html5/images/fake.txt').exists() - new File(testProjectDir.root, 'build/docs/asciidoc/en/docbook/sample.xml').exists() - new File(testProjectDir.root, 'build/docs/asciidoc/es/docbook/sample.xml').exists() + new File(buildDir, 'docs/asciidoc/en/html5/sample.html').exists() + new File(buildDir, 'docs/asciidoc/es/html5/sample.html').exists() + new File(buildDir, 'docs/asciidoc/en/html5/images/fake.txt').exists() + new File(buildDir, 'docs/asciidoc/es/html5/images/fake.txt').exists() + new File(buildDir, 'docs/asciidoc/en/docbook/sample.xml').exists() + new File(buildDir, 'docs/asciidoc/es/docbook/sample.xml').exists() } } @@ -113,10 +113,10 @@ class MultiLanguageFunctionalSpec extends FunctionalSpecification { then: verifyAll { - new File(testProjectDir.root, 'build/docs/asciidoc/en/html5/sample.html').exists() - new File(testProjectDir.root, 'build/docs/asciidoc/es/html5/sample.html').exists() - new File(testProjectDir.root, 'build/docs/asciidoc/en/docbook/sample.xml').exists() - new File(testProjectDir.root, 'build/docs/asciidoc/es/docbook/sample.xml').exists() + new File(buildDir, 'docs/asciidoc/en/html5/sample.html').exists() + new File(buildDir, 'docs/asciidoc/es/html5/sample.html').exists() + new File(buildDir, 'docs/asciidoc/en/docbook/sample.xml').exists() + new File(buildDir, 'docs/asciidoc/es/docbook/sample.xml').exists() } } @@ -144,9 +144,9 @@ class MultiLanguageFunctionalSpec extends FunctionalSpecification { then: verifyAll { - new File(testProjectDir.root, 'build/docs/asciidoc/en/sample.html').text + new File(buildDir, 'docs/asciidoc/en/sample.html').text .contains('') - new File(testProjectDir.root, 'build/docs/asciidoc/es/sample.html').text + new File(buildDir, 'docs/asciidoc/es/sample.html').text .contains('') } } @@ -165,7 +165,7 @@ class MultiLanguageFunctionalSpec extends FunctionalSpecification { sourceDir 'src/docs/asciidoc' - attributes docinfo: 'shared' + attributes docinfo: 'shared', description: 'asciidoctor-docinfo-test' baseDirFollowsSourceDir() useIntermediateWorkDir() } @@ -176,10 +176,10 @@ class MultiLanguageFunctionalSpec extends FunctionalSpecification { then: verifyAll { - new File(testProjectDir.root, 'build/docs/asciidoc/en/sample.html').text - .contains('') - new File(testProjectDir.root, 'build/docs/asciidoc/es/sample.html').text - .contains('') + new File(buildDir, 'docs/asciidoc/en/sample.html').text + .contains('') + new File(buildDir, 'docs/asciidoc/es/sample.html').text + .contains('') } } @@ -219,16 +219,16 @@ class MultiLanguageFunctionalSpec extends FunctionalSpecification { then: verifyAll { - new File(testProjectDir.root, 'build/docs/asciidoc/en/sample.html').exists() - new File(testProjectDir.root, 'build/docs/asciidoc/es/sample.html').exists() - new File(testProjectDir.root, 'build/docs/asciidoc/en/foo/fake-en.txt').exists() - new File(testProjectDir.root, 'build/docs/asciidoc/en/foo/fake-common.txt').exists() - new File(testProjectDir.root, 'build/docs/asciidoc/es/foo/fake-es.txt').exists() - new File(testProjectDir.root, 'build/docs/asciidoc/es/foo/fake-common.txt').exists() - !new File(testProjectDir.root, 'build/docs/asciidoc/en/images/fake.txt').exists() - !new File(testProjectDir.root, 'build/docs/asciidoc/es/images/fake.txt').exists() - !new File(testProjectDir.root, 'build/docs/asciidoc/en/foo/fake-es.txt').exists() - !new File(testProjectDir.root, 'build/docs/asciidoc/es/foo/fake-en.txt').exists() + new File(buildDir, 'docs/asciidoc/en/sample.html').exists() + new File(buildDir, 'docs/asciidoc/es/sample.html').exists() + new File(buildDir, 'docs/asciidoc/en/foo/fake-en.txt').exists() + new File(buildDir, 'docs/asciidoc/en/foo/fake-common.txt').exists() + new File(buildDir, 'docs/asciidoc/es/foo/fake-es.txt').exists() + new File(buildDir, 'docs/asciidoc/es/foo/fake-common.txt').exists() + !new File(buildDir, 'docs/asciidoc/en/images/fake.txt').exists() + !new File(buildDir, 'docs/asciidoc/es/images/fake.txt').exists() + !new File(buildDir, 'docs/asciidoc/en/foo/fake-es.txt').exists() + !new File(buildDir, 'docs/asciidoc/es/foo/fake-en.txt').exists() } } diff --git a/jvm/src/intTest/groovy/org/asciidoctor/gradle/jvm/RelativeIncludeFunctionalSpec.groovy b/jvm/src/intTest/groovy/org/asciidoctor/gradle/jvm/RelativeIncludeFunctionalSpec.groovy index b14536ed5..05e341e58 100644 --- a/jvm/src/intTest/groovy/org/asciidoctor/gradle/jvm/RelativeIncludeFunctionalSpec.groovy +++ b/jvm/src/intTest/groovy/org/asciidoctor/gradle/jvm/RelativeIncludeFunctionalSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,7 +41,7 @@ class RelativeIncludeFunctionalSpec extends FunctionalSpecification { getGradleRunner(DEFAULT_ARGS).withDebug(true).build() then: - new File(testProjectDir.root, 'build/docs/asciidoc/nested/sample.html').text + new File(buildDir, 'docs/asciidoc/nested/sample.html').text .contains('This is from _nested-include.adoc file.') } } diff --git a/jvm/src/intTest/groovy/org/asciidoctor/gradle/jvm/RequiresFunctionalSpec.groovy b/jvm/src/intTest/groovy/org/asciidoctor/gradle/jvm/RequiresFunctionalSpec.groovy index bf15b7fb0..f12d79d36 100644 --- a/jvm/src/intTest/groovy/org/asciidoctor/gradle/jvm/RequiresFunctionalSpec.groovy +++ b/jvm/src/intTest/groovy/org/asciidoctor/gradle/jvm/RequiresFunctionalSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,7 +43,7 @@ class RequiresFunctionalSpec extends FunctionalSpecification { when: final BuildResult firstInvocationResult = getGradleRunner(DEFAULT_ARGS).build() - File outputFolder = new File(testProjectDir.root, 'build/docs/asciidoc') + File outputFolder = new File(buildDir, 'docs/asciidoc') File outputFile = new File(outputFolder, 'ditaa.html') then: @@ -52,7 +52,7 @@ class RequiresFunctionalSpec extends FunctionalSpecification { outputFolder.listFiles().findAll { it.name.endsWith(imageFileExt) }.size() == 1 when: - new File(testProjectDir.root, "src/docs/asciidoc/${DITAA}") << 'changes' + new File(projectDir, "src/docs/asciidoc/${DITAA}") << 'changes' final BuildResult secondInvocationResult = getGradleRunner(['clean'] + DEFAULT_ARGS).build() then: @@ -91,7 +91,7 @@ class RequiresFunctionalSpec extends FunctionalSpecification { when: final BuildResult firstInvocationResult = getGradleRunner(['-i'] + DEFAULT_ARGS).build() - File outputFolder = new File(testProjectDir.root, 'build/docs/asciidoc') + File outputFolder = new File(buildDir, 'docs/asciidoc') File outputFile = new File(outputFolder, 'ditaa.html') then: @@ -100,7 +100,7 @@ class RequiresFunctionalSpec extends FunctionalSpecification { outputFolder.listFiles().findAll { it.name.endsWith(imageFileExt) }.size() == 1 when: - new File(testProjectDir.root, "src/docs/asciidoc/${DITAA}") << 'changes' + new File(projectDir, "src/docs/asciidoc/${DITAA}") << 'changes' final BuildResult secondInvocationResult = getGradleRunner(['clean'] + DEFAULT_ARGS).build() then: diff --git a/jvm/src/intTest/groovy/org/asciidoctor/gradle/jvm/ResourcesFunctionalSpec.groovy b/jvm/src/intTest/groovy/org/asciidoctor/gradle/jvm/ResourcesFunctionalSpec.groovy index 5c7ca6641..cef77c9f8 100644 --- a/jvm/src/intTest/groovy/org/asciidoctor/gradle/jvm/ResourcesFunctionalSpec.groovy +++ b/jvm/src/intTest/groovy/org/asciidoctor/gradle/jvm/ResourcesFunctionalSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,6 @@ */ package org.asciidoctor.gradle.jvm -@java.lang.SuppressWarnings('NoWildcardImports') import org.asciidoctor.gradle.internal.FunctionalSpecification import org.gradle.testkit.runner.GradleRunner import spock.lang.Timeout @@ -23,8 +22,8 @@ import spock.lang.Unroll class ResourcesFunctionalSpec extends FunctionalSpecification { - static final List DEFAULT_ARGS = ['asciidoctor', '-s'] - static final String ASCIIDOC_FILE = 'sample.asciidoc' + public static final List DEFAULT_ARGS = ['asciidoctor', '-s'] + public static final String ASCIIDOC_FILE = 'sample.asciidoc' void setup() { createTestProject('resources') @@ -32,7 +31,6 @@ class ResourcesFunctionalSpec extends FunctionalSpecification { @Timeout(value = 90) @Unroll - @SuppressWarnings('LineLength') void 'When resources are not specified, copy all images to destination with intermediate workdir=#intermediate)'() { given: getBuildFile(""" @@ -41,7 +39,7 @@ class ResourcesFunctionalSpec extends FunctionalSpecification { } """) GradleRunner runner = getGradleRunner(DEFAULT_ARGS) - File buildDir = new File(testProjectDir.root, 'build/docs/asciidoc') + File buildDir = new File(buildDir, 'docs/asciidoc') File imagesDir = new File(buildDir, 'images') when: @@ -71,7 +69,7 @@ class ResourcesFunctionalSpec extends FunctionalSpecification { } ''') GradleRunner runner = getGradleRunner(DEFAULT_ARGS) - File buildDir = new File(testProjectDir.root, 'build/docs/asciidoc') + File buildDir = new File(buildDir, 'docs/asciidoc') File imagesDir = new File(buildDir, 'images') File extraDir = new File(buildDir, 'images2') @@ -103,7 +101,7 @@ class ResourcesFunctionalSpec extends FunctionalSpecification { } ''') GradleRunner runner = getGradleRunner(DEFAULT_ARGS) - File buildDir = new File(testProjectDir.root, 'build/docs/asciidoc') + File buildDir = new File(buildDir, 'docs/asciidoc') File imagesDir = new File(buildDir, 'images') when: @@ -128,7 +126,7 @@ class ResourcesFunctionalSpec extends FunctionalSpecification { } ''') GradleRunner runner = getGradleRunner(DEFAULT_ARGS) - File buildDir = new File(testProjectDir.root, 'build/docs/asciidoc') + File buildDir = new File(buildDir, 'docs/asciidoc') File htmlImagesDir = new File(buildDir, 'html5/images') File docbookImagesDir = new File(buildDir, 'docbook/images') @@ -155,7 +153,7 @@ class ResourcesFunctionalSpec extends FunctionalSpecification { } ''') GradleRunner runner = getGradleRunner(DEFAULT_ARGS) - File buildDir = new File(testProjectDir.root, 'build/docs/asciidoc') + File buildDir = new File(buildDir, 'docs/asciidoc') File imagesDir = new File(buildDir, 'images') when: @@ -169,7 +167,6 @@ class ResourcesFunctionalSpec extends FunctionalSpecification { } } - // 'Resources can be copied on a per-backend-basis' @Timeout(value = 90) void 'Resources can be copied on a per-backend-basis'() { given: @@ -182,7 +179,7 @@ class ResourcesFunctionalSpec extends FunctionalSpecification { } ''') GradleRunner runner = getGradleRunner(DEFAULT_ARGS) - File buildDir = new File(testProjectDir.root, 'build/docs/asciidoc') + File buildDir = new File(buildDir, 'docs/asciidoc') File htmlImagesDir = new File(buildDir, 'html5/images') File docbookImagesDir = new File(buildDir, 'docbook/images') diff --git a/jvm/src/intTest/groovy/org/asciidoctor/gradle/jvm/WarningsAsErrorsFunctionalSpec.groovy b/jvm/src/intTest/groovy/org/asciidoctor/gradle/jvm/WarningsAsErrorsFunctionalSpec.groovy index 62a5ce77a..f9cd9748a 100644 --- a/jvm/src/intTest/groovy/org/asciidoctor/gradle/jvm/WarningsAsErrorsFunctionalSpec.groovy +++ b/jvm/src/intTest/groovy/org/asciidoctor/gradle/jvm/WarningsAsErrorsFunctionalSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,7 +37,7 @@ class WarningsAsErrorsFunctionalSpec extends FunctionalSpecification { } asciidoctor { - inProcess ${model.processMode} + executionMode = ${model.processMode} sources { include 'sample.adoc' } @@ -66,7 +66,7 @@ class WarningsAsErrorsFunctionalSpec extends FunctionalSpecification { } asciidoctor { - inProcess ProcessMode.${model.processMode} + executionMode = ${model.processMode} sources "sample.adoc" } """) diff --git a/jvm/src/main/groovy/org/asciidoctor/gradle/internal/AsciidoctorExecutorFactory.groovy b/jvm/src/main/groovy/org/asciidoctor/gradle/internal/AsciidoctorExecutorFactory.groovy new file mode 100644 index 000000000..85d67d991 --- /dev/null +++ b/jvm/src/main/groovy/org/asciidoctor/gradle/internal/AsciidoctorExecutorFactory.groovy @@ -0,0 +1,39 @@ +/* + * Copyright 2013-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://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.asciidoctor.gradle.internal + +import org.asciidoctor.gradle.remote.AsciidoctorWorkerExecutor +import org.ysb33r.grolifant.api.remote.worker.WorkerAppExecutor +import org.ysb33r.grolifant.api.remote.worker.WorkerAppExecutorFactory + +/** + * Creates an executor for running Asciidoctor conversions inside a worker. + * + * @author Schalk W. Cronjé + * + * @since 4.0 + */ +class AsciidoctorExecutorFactory implements WorkerAppExecutorFactory, Serializable { + /** + * Creates an executor that can work with a set of parameters. + * + * @return Executor. + */ + @Override + WorkerAppExecutor createExecutor() { + new AsciidoctorWorkerExecutor() + } +} diff --git a/jvm/src/main/groovy/org/asciidoctor/gradle/internal/AsciidoctorWorkerParameterFactory.groovy b/jvm/src/main/groovy/org/asciidoctor/gradle/internal/AsciidoctorWorkerParameterFactory.groovy new file mode 100644 index 000000000..54974c2f8 --- /dev/null +++ b/jvm/src/main/groovy/org/asciidoctor/gradle/internal/AsciidoctorWorkerParameterFactory.groovy @@ -0,0 +1,50 @@ +/* + * Copyright 2013-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://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.asciidoctor.gradle.internal + +import groovy.transform.CompileStatic +import org.ysb33r.grolifant.api.core.jvm.worker.WorkerAppParameterFactory +import org.ysb33r.grolifant.api.core.jvm.worker.WorkerExecSpec + +import java.util.concurrent.Callable + +/** + * Creates worker parameters using Grolifant worker setup. + * + * @author Schalk W. Cronjé + * + * @since 4.0 + */ +@CompileStatic +class AsciidoctorWorkerParameterFactory implements WorkerAppParameterFactory { + + private final Callable>> populator + + AsciidoctorWorkerParameterFactory( + Callable>> populator + ) { + this.populator = populator + } + + /** + * @param execSpec Java execution specification that can be used for setting worker execution details. + * @return A parameter configuration. + */ + @Override + AsciidoctorWorkerParameters createAndConfigure(WorkerExecSpec execSpec) { + new AsciidoctorWorkerParameters(asciidoctorConfigurations: populator.call()) + } +} diff --git a/jvm/src/main/groovy/org/asciidoctor/gradle/internal/AsciidoctorWorkerParameters.groovy b/jvm/src/main/groovy/org/asciidoctor/gradle/internal/AsciidoctorWorkerParameters.groovy new file mode 100644 index 000000000..55d529322 --- /dev/null +++ b/jvm/src/main/groovy/org/asciidoctor/gradle/internal/AsciidoctorWorkerParameters.groovy @@ -0,0 +1,41 @@ +/* + * Copyright 2013-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://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.asciidoctor.gradle.internal + +import org.ysb33r.grolifant.api.remote.worker.SerializableWorkerAppParameters + +/** + * Parameters for serializing ASciidoctor jobs to workers. + * + * @author Schalk W> Cronjé + * + * @since 4.0 + */ +class AsciidoctorWorkerParameters implements SerializableWorkerAppParameters { + + /** + * Whether to attempt conversions in parallel inside the worker. + * + * Reserved for future use. + */ + Boolean runParallelInWorker = false + + /** + * Map of executor configuration keyed by language. + * If there are no languages defined, the key of only entry will be an empty string. + */ + Map> asciidoctorConfigurations +} diff --git a/jvm/src/main/groovy/org/asciidoctor/gradle/jvm/AsciidoctorJModules.groovy b/jvm/src/main/groovy/org/asciidoctor/gradle/internal/DefaultAsciidoctorJModules.groovy similarity index 57% rename from jvm/src/main/groovy/org/asciidoctor/gradle/jvm/AsciidoctorJModules.groovy rename to jvm/src/main/groovy/org/asciidoctor/gradle/internal/DefaultAsciidoctorJModules.groovy index c4e3f8f6b..d18b3d25d 100644 --- a/jvm/src/main/groovy/org/asciidoctor/gradle/jvm/AsciidoctorJModules.groovy +++ b/jvm/src/main/groovy/org/asciidoctor/gradle/internal/DefaultAsciidoctorJModules.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,20 +13,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.asciidoctor.gradle.jvm +package org.asciidoctor.gradle.internal +import groovy.transform.CompileStatic import org.asciidoctor.gradle.base.AsciidoctorModuleDefinition import org.asciidoctor.gradle.base.ModuleNotFoundException +import org.asciidoctor.gradle.jvm.AsciidoctorJExtension +import org.asciidoctor.gradle.jvm.AsciidoctorJModules import org.gradle.api.Action -import org.ysb33r.grolifant.api.v4.StringUtils +import org.ysb33r.grolifant.api.core.ProjectOperations -import static org.ysb33r.grolifant.api.v4.ClosureUtils.configureItem +import static org.ysb33r.grolifant.api.core.ClosureUtils.configureItem -/** Define versions for standard AsciidoctorJ modules. +/** + * Implementation of {@Link AsciidoctorJModules}. * - * @since 2.2.0 + * @since 4.0 + * + * @author Schalk W. Cronjé */ -class AsciidoctorJModules { +@CompileStatic +class DefaultAsciidoctorJModules implements AsciidoctorJModules { private final Map index = new TreeMap() @@ -35,17 +42,26 @@ class AsciidoctorJModules { private final AsciidoctorModuleDefinition diagram private final AsciidoctorModuleDefinition leanpub private final AsciidoctorModuleDefinition groovyDsl - - AsciidoctorJModules(AsciidoctorJExtension asciidoctorjs, Map defaultVersions) { - this.pdf = Module.of('pdf', defaultVersions['asciidoctorj.pdf']) - this.epub = Module.of('epub', defaultVersions['asciidoctorj.epub']) - this.leanpub = Module.of('leanpub', defaultVersions['asciidoctorj.leanpub']) - this.diagram = Module.of('diagram', defaultVersions['asciidoctorj.diagram']) { value -> + private Action updater + + // TODO: Remove this warning - it is only there temporarily. + @SuppressWarnings('UnusedMethodParameter') + DefaultAsciidoctorJModules( + ProjectOperations po, + AsciidoctorJExtension asciidoctorjs, + Map defaultVersions + ) { + final defaultUpdater = { value -> owner.updateNow() } + this.pdf = Module.of(po, 'pdf', defaultVersions['asciidoctorj.pdf'], defaultUpdater) + this.epub = Module.of(po, 'epub', defaultVersions['asciidoctorj.epub'], defaultUpdater) + this.leanpub = Module.of(po, 'leanpub', defaultVersions['asciidoctorj.leanpub'], defaultUpdater) + this.diagram = Module.of(po, 'diagram', defaultVersions['asciidoctorj.diagram']) { value -> if (value != null) { + owner.updateNow() asciidoctorjs.requires('asciidoctor-diagram') } } - this.groovyDsl = Module.of('groovyDsl', defaultVersions['asciidoctorj.groovydsl']) + this.groovyDsl = Module.of(po, 'groovyDsl', defaultVersions['asciidoctorj.groovydsl'], defaultUpdater) [pdf, epub, diagram, groovyDsl, leanpub].each { index[it.name] = it @@ -92,25 +108,52 @@ class AsciidoctorJModules { this.groovyDsl } + /** + * Registers a callback for when modules are activated or changed after initial creation. + * + * @param callback Callback + */ + void onUpdate(Action callback) { + this.updater = callback + } + + void updateNow() { + if (updater) { + updater.execute(this) + } + } + private static class Module implements AsciidoctorModuleDefinition { final String name private Optional version = Optional.empty() private final Object defaultVersion private final Action setAction + private final ProjectOperations projectOperations - static Module of(final String name, final Object defaultVersion) { - new Module(name, defaultVersion) + static Module of(final ProjectOperations po, final String name, final Object defaultVersion) { + new Module(po, name, defaultVersion) } - static Module of(final String name, final Object defaultVersion, Closure setAction) { - new Module(name, defaultVersion, setAction as Action) + static Module of( + final ProjectOperations po, + final String name, + final Object defaultVersion, + Closure setAction + ) { + new Module(po, name, defaultVersion, setAction as Action) } - private Module(final String name, final Object defaultVersion, Action setAction = null) { + private Module( + ProjectOperations po, + final String name, + final Object defaultVersion, + Action setAction = null + ) { if (defaultVersion == null) { throw new ModuleNotFoundException("Default version for ${name} cannot be null") } + this.projectOperations = po this.name = name this.defaultVersion = defaultVersion this.setAction = setAction @@ -136,7 +179,7 @@ class AsciidoctorJModules { @Override String getVersion() { - this.version.present ? StringUtils.stringize(this.version.get()) : null + this.version.present ? projectOperations.stringTools.stringize(this.version.get()) : null } /** Whether the component has been allocated a version. diff --git a/jvm/src/main/groovy/org/asciidoctor/gradle/internal/ExecutorConfiguration.groovy b/jvm/src/main/groovy/org/asciidoctor/gradle/internal/ExecutorConfiguration.groovy index 66cb94863..fd3c7d62b 100644 --- a/jvm/src/main/groovy/org/asciidoctor/gradle/internal/ExecutorConfiguration.groovy +++ b/jvm/src/main/groovy/org/asciidoctor/gradle/internal/ExecutorConfiguration.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,7 +38,6 @@ class ExecutorConfiguration implements Serializable, Cloneable { List fatalMessagePatterns String backendName - String gemPath boolean logDocuments boolean copyResources @@ -66,7 +65,6 @@ File locations: baseDir = ${baseDir} JRuby: - GEMPATH = ${gemPath} requires = ${requires} Asciidoctor: diff --git a/jvm/src/main/groovy/org/asciidoctor/gradle/internal/ExecutorConfigurationContainer.groovy b/jvm/src/main/groovy/org/asciidoctor/gradle/internal/ExecutorConfigurationContainer.groovy index d0391ff33..2e642f54d 100644 --- a/jvm/src/main/groovy/org/asciidoctor/gradle/internal/ExecutorConfigurationContainer.groovy +++ b/jvm/src/main/groovy/org/asciidoctor/gradle/internal/ExecutorConfigurationContainer.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,9 @@ import groovy.transform.CompileStatic /** Contains a number of executor configurations. * - * @since 2.0.0* @author Schalk W. Cronjé + * @since 2.0.0 + * + * @author Schalk W. Cronjé */ @CompileStatic class ExecutorConfigurationContainer implements Serializable { diff --git a/jvm/src/main/groovy/org/asciidoctor/gradle/internal/ExecutorLogLevel.groovy b/jvm/src/main/groovy/org/asciidoctor/gradle/internal/ExecutorLogLevel.groovy index 99ce0cae1..c6283d11f 100644 --- a/jvm/src/main/groovy/org/asciidoctor/gradle/internal/ExecutorLogLevel.groovy +++ b/jvm/src/main/groovy/org/asciidoctor/gradle/internal/ExecutorLogLevel.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/jvm/src/main/groovy/org/asciidoctor/gradle/internal/ExecutorUtils.groovy b/jvm/src/main/groovy/org/asciidoctor/gradle/internal/ExecutorUtils.groovy index ff91dfbdb..a873ff08a 100644 --- a/jvm/src/main/groovy/org/asciidoctor/gradle/internal/ExecutorUtils.groovy +++ b/jvm/src/main/groovy/org/asciidoctor/gradle/internal/ExecutorUtils.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/jvm/src/main/groovy/org/asciidoctor/gradle/internal/JavaExecUtils.groovy b/jvm/src/main/groovy/org/asciidoctor/gradle/internal/JavaExecUtils.groovy index 47b3ad210..9d69b824d 100644 --- a/jvm/src/main/groovy/org/asciidoctor/gradle/internal/JavaExecUtils.groovy +++ b/jvm/src/main/groovy/org/asciidoctor/gradle/internal/JavaExecUtils.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ import org.gradle.api.Task import org.gradle.api.file.FileCollection import org.gradle.api.invocation.Gradle import org.gradle.util.GradleVersion -import org.ysb33r.grolifant.api.v4.FileUtils +import org.ysb33r.grolifant.api.core.ProjectOperations import java.util.regex.Pattern @@ -56,11 +56,14 @@ class JavaExecUtils { * @param asciidoctorClasspath External asciidoctor dependencies * @param addInternalGuava Set to {@code true} to add internal Guava to classpath * @return A computed classpath that can be given to an external Java process. + * + * @deprecated */ + @Deprecated static FileCollection getJavaExecClasspath( - final Project project, - final FileCollection asciidoctorClasspath, - boolean addInternalGuava = false + final Project project, + final FileCollection asciidoctorClasspath, + boolean addInternalGuava = false ) { File entryPoint = getClassLocation(AsciidoctorJavaExec) File groovyJar = getClassLocation(GroovyObject) @@ -70,21 +73,50 @@ class JavaExecUtils { addInternalGuava ? project.files(fc, getInternalGuavaLocation(project.gradle)) : fc } + /** Get the classpath that needs to be passed to the external Java process. + * + * @param project Current Gradle project + * @param asciidoctorClasspath External asciidoctor dependencies + * @param addInternalGuava Set to {@code true} to add internal Guava to classpath + * @return A computed classpath that can be given to an external Java process. + */ + static FileCollection getJavaExecClasspath( + final ProjectOperations po, + final FileCollection asciidoctorClasspath, + boolean addInternalGuava = false + ) { + File entryPoint = getClassLocation(AsciidoctorJavaExec) + File groovyJar = getClassLocation(GroovyObject) + + final fc = po.fsOperations.emptyFileCollection() + fc.from(entryPoint, groovyJar) + + if (addInternalGuava) { + fc.from(getInternalGuavaLocation(po)) + } + fc + asciidoctorClasspath + } + /** The file to which execution configuration data can be serialised to. * * @param task Task for which execution data will be serialised. * @return File that will (eventually) contain the execution data. */ static File getExecConfigurationDataFile(final Task task) { - task.project.file("${task.project.buildDir}/tmp/${FileUtils.toSafeFileName(task.name)}.javaexec-data") + final fso = ProjectOperations.find(task.project).fsOperations + task.project.file("${task.project.buildDir}/tmp/${fso.toSafeFileName(task.name)}.javaexec-data") } - /** Serializes execution configuration data. + /** + * Serializes execution configuration data. * * @param task Task for which execution data will be serialised. * @param executorConfigurations Executor configuration to be serialised * @return File that the execution data was written to. + * + * @deprecated */ + @Deprecated static File writeExecConfigurationData(final Task task, Iterable executorConfigurations) { log.debug("Executor configurations: ${executorConfigurations}") File execConfigurationData = getExecConfigurationDataFile(task) @@ -93,7 +125,27 @@ class JavaExecUtils { execConfigurationData } - /** Returns the location of the local Groovy Jar that is used by Gradle. + /** + * Serializes execution configuration data. + * + * @param execConfigurationData File to be use for serialization data. + * @param executorConfigurations Executor configuration to be serialised + * @return File that the execution data was written to. + * + * @since 4.0 + */ + static void writeExecConfigurationData( + final File execConfigurationData, + Iterable executorConfigurations + ) { + log.debug("Executor configurations: ${executorConfigurations}") + execConfigurationData.parentFile.mkdirs() + ExecutorConfigurationContainer.toFile(execConfigurationData, executorConfigurations) + execConfigurationData + } + + /** + * Returns the location of the local Groovy Jar that is used by Gradle. * * @return Location on filesystem where the Groovy Jar is located. */ @@ -107,6 +159,29 @@ class JavaExecUtils { * @return Return Guava location. Never {@code null} * @throw InternalGuavaLocationException */ + static File getInternalGuavaLocation(ProjectOperations po) { + File[] files = new File(po.gradleUserHomeDir.get(), 'lib').listFiles(INTERNAL_GUAVA_PATTERN) + + if (!files) { + throw new InternalGuavaLocationException('Cannot locate a Guava JAR in the Gradle distribution') + } else if (files.size() > 1) { + throw new InternalGuavaLocationException( + "Found more than one Guava JAR in the Gradle distribution: ${files*.name}" + ) + } + files[0] + } + + /** Locate the internal Guava JAR from the Gradle distribution + * + * @param gradle Gradle instance + * @return Return Guava location. Never {@code null} + * @throw InternalGuavaLocationException + * + * @deprecated + */ + @Deprecated + @SuppressWarnings('DuplicateStringLiteral') static File getInternalGuavaLocation(Gradle gradle) { File[] files = new File(gradle.gradleHomeDir, 'lib').listFiles(INTERNAL_GUAVA_PATTERN) @@ -114,7 +189,7 @@ class JavaExecUtils { throw new InternalGuavaLocationException('Cannot locate a Guava JAR in the Gradle distribution') } else if (files.size() > 1) { throw new InternalGuavaLocationException( - "Found more than one Guava JAR in the Gradle distribution: ${files*.name}" + "Found more than one Guava JAR in the Gradle distribution: ${files*.name}" ) } files[0] diff --git a/jvm/src/main/groovy/org/asciidoctor/gradle/jvm/AbstractAsciidoctorTask.groovy b/jvm/src/main/groovy/org/asciidoctor/gradle/jvm/AbstractAsciidoctorTask.groovy index e52a331b3..a9f306320 100644 --- a/jvm/src/main/groovy/org/asciidoctor/gradle/jvm/AbstractAsciidoctorTask.groovy +++ b/jvm/src/main/groovy/org/asciidoctor/gradle/jvm/AbstractAsciidoctorTask.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,89 +15,115 @@ */ package org.asciidoctor.gradle.jvm -import groovy.transform.CompileDynamic import groovy.transform.CompileStatic -import groovy.transform.PackageScope -import org.asciidoctor.gradle.base.AbstractAsciidoctorBaseTask import org.asciidoctor.gradle.base.AsciidoctorAttributeProvider +import org.asciidoctor.gradle.base.AsciidoctorTaskBaseDirConfiguration +import org.asciidoctor.gradle.base.AsciidoctorTaskFileOperations +import org.asciidoctor.gradle.base.AsciidoctorTaskMethods +import org.asciidoctor.gradle.base.AsciidoctorTaskOutputOptions +import org.asciidoctor.gradle.base.AsciidoctorTaskWorkspacePreparation import org.asciidoctor.gradle.base.Transform +import org.asciidoctor.gradle.base.internal.DefaultAsciidoctorBaseDirConfiguration +import org.asciidoctor.gradle.base.internal.DefaultAsciidoctorFileOperations +import org.asciidoctor.gradle.base.internal.DefaultAsciidoctorOutputOptions +import org.asciidoctor.gradle.base.internal.DefaultAsciidoctorWorkspacePreparation import org.asciidoctor.gradle.base.internal.Workspace import org.asciidoctor.gradle.base.log.Severity import org.asciidoctor.gradle.base.process.ProcessMode +import org.asciidoctor.gradle.internal.AsciidoctorExecutorFactory +import org.asciidoctor.gradle.internal.AsciidoctorWorkerParameterFactory +import org.asciidoctor.gradle.internal.AsciidoctorWorkerParameters import org.asciidoctor.gradle.internal.ExecutorConfiguration -import org.asciidoctor.gradle.internal.ExecutorConfigurationContainer import org.asciidoctor.gradle.internal.ExecutorUtils import org.asciidoctor.gradle.internal.JavaExecUtils -import org.asciidoctor.gradle.remote.AsciidoctorJExecuter import org.asciidoctor.gradle.remote.AsciidoctorJavaExec -import org.asciidoctor.gradle.remote.AsciidoctorRemoteExecutionException import org.gradle.api.Action -import org.gradle.api.GradleException import org.gradle.api.artifacts.Configuration +import org.gradle.api.artifacts.ConfigurationContainer import org.gradle.api.artifacts.Dependency -import org.gradle.api.artifacts.DependencyResolveDetails import org.gradle.api.file.FileCollection +import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider import org.gradle.api.tasks.Classpath import org.gradle.api.tasks.Input import org.gradle.api.tasks.Internal -import org.gradle.api.tasks.TaskAction -import org.gradle.process.JavaExecSpec +import org.gradle.api.tasks.Nested +import org.gradle.api.tasks.TaskProvider +import org.gradle.api.tasks.bundling.Jar import org.gradle.process.JavaForkOptions -import org.gradle.util.GradleVersion -import org.gradle.workers.ClassLoaderWorkerSpec -import org.gradle.workers.ProcessWorkerSpec -import org.gradle.workers.WorkAction -import org.gradle.workers.WorkParameters -import org.gradle.workers.WorkQueue import org.gradle.workers.WorkerExecutor +import org.ysb33r.grolifant.api.core.jvm.ExecutionMode +import org.ysb33r.grolifant.api.core.jvm.JavaForkOptionsWithEnvProvider +import org.ysb33r.grolifant.api.core.jvm.worker.WorkerAppParameterFactory +import org.ysb33r.grolifant.api.core.runnable.AbstractJvmModelExecTask +import org.ysb33r.grolifant.api.remote.worker.WorkerAppExecutorFactory + +import java.util.function.Function -import static org.asciidoctor.gradle.base.AsciidoctorUtils.executeDelegatingClosure import static org.asciidoctor.gradle.base.AsciidoctorUtils.getClassLocation +import static org.asciidoctor.gradle.base.internal.AsciidoctorAttributes.evaluateProviders +import static org.asciidoctor.gradle.base.internal.AsciidoctorAttributes.prepareAttributes import static org.asciidoctor.gradle.base.internal.AsciidoctorAttributes.resolveAsCacheable import static org.asciidoctor.gradle.base.internal.AsciidoctorAttributes.resolveAsSerializable -import static org.asciidoctor.gradle.base.internal.ConfigurationUtils.asConfiguration -import static org.asciidoctor.gradle.base.internal.ConfigurationUtils.asConfigurations +import static org.asciidoctor.gradle.internal.JavaExecUtils.getExecConfigurationDataFile import static org.gradle.api.tasks.PathSensitivity.RELATIVE -import static org.ysb33r.grolifant.api.core.LegacyLevel.PRE_6_4 -/** Base class for all AsciidoctorJ tasks. +/** + * Base class for all AsciidoctorJ tasks. * * @author Schalk W. Cronjé * @author Manuel Prinz * * @since 2.0.0 */ -@SuppressWarnings('MethodCount') @CompileStatic -class AbstractAsciidoctorTask extends AbstractAsciidoctorBaseTask { +@SuppressWarnings('MethodCount') +class AbstractAsciidoctorTask extends AbstractJvmModelExecTask + implements AsciidoctorTaskMethods { - public final static ProcessMode IN_PROCESS = ProcessMode.IN_PROCESS - public final static ProcessMode OUT_OF_PROCESS = ProcessMode.OUT_OF_PROCESS - public final static ProcessMode JAVA_EXEC = ProcessMode.JAVA_EXEC + public final static ExecutionMode CLASSPATH = ExecutionMode.CLASSPATH + public final static ExecutionMode IN_PROCESS = ExecutionMode.CLASSPATH + public final static ExecutionMode OUT_OF_PROCESS = ExecutionMode.OUT_OF_PROCESS + public final static ExecutionMode JAVA_EXEC = ExecutionMode.JAVA_EXEC public final static Severity FATAL = Severity.FATAL public final static Severity ERROR = Severity.ERROR public final static Severity WARN = Severity.WARN public final static Severity INFO = Severity.INFO - protected final static GradleVersion LAST_GRADLE_WITH_CLASSPATH_LEAKAGE = GradleVersion.version(('6.99')) - protected final AsciidoctorJExtension asciidoctorj - private ProcessMode inProcess = JAVA_EXEC + private ExecutionMode inProcess private Severity failureLevel = Severity.FATAL - private final WorkerExecutor worker private final List asciidocConfigurations = [] + private final File rootDir + private final File projectDir + private final File execConfigurationDataFile + private final Function, Configuration> detachedConfigurationCreator + private final Property jvmClasspath + private final List> gemJarProviders = [] + + @Delegate + private final DefaultAsciidoctorFileOperations asciidoctorTaskFileOperations - @PackageScope - final org.ysb33r.grolifant.api.v4.JavaForkOptions javaForkOptions = - new org.ysb33r.grolifant.api.v4.JavaForkOptions() + @Delegate + private final AsciidoctorTaskWorkspacePreparation workspacePreparation + + @Delegate + private final DefaultAsciidoctorOutputOptions asciidoctorOutputOptions + + @Delegate + private final AsciidoctorTaskBaseDirConfiguration baseDirConfiguration /** Set how AsciidoctorJ should be run. * * @param mode {@link #IN_PROCESS}, {@link #OUT_OF_PROCESS} or {@link #JAVA_EXEC}. + * + * @deprecated Use {@link #setExecutionMode} instead. */ + @Deprecated void setInProcess(ProcessMode mode) { - this.inProcess = mode + logger.warn "Use 'setExecutionMode' instead of 'setInProcess(ProcessMode)'" + executionMode = mode.executionMode } /** Set how AsciidoctorJ should be run. @@ -105,19 +131,13 @@ class AbstractAsciidoctorTask extends AbstractAsciidoctorBaseTask { * @param mode Case-insensitive string from of {@link #IN_PROCESS}, {@link #OUT_OF_PROCESS} or {@link #JAVA_EXEC}. * * @since 3.0 - */ - void setInProcess(String mode) { - this.inProcess = ProcessMode.valueOf(mode.toUpperCase(Locale.US)) - } - - /** Run Asciidoctor conversions in or out of process * - * Valid options are {@link #IN_PROCESS}, {@link #OUT_OF_PROCESS} and {@link #JAVA_EXEC}. - * The default mode is {@link #JAVA_EXEC}. + * @deprecated Use {@link #setExecutionMode} instead. */ - @Internal - ProcessMode getInProcess() { - this.inProcess + @Deprecated + void setInProcess(String mode) { + logger.warn "Use 'setExecutionMode' instead of 'setInProcess(String)'" + executionMode = ProcessMode.valueOf(mode.toUpperCase(Locale.US)).executionMode } /** Set the minimum logging level that will fail the task. @@ -163,6 +183,7 @@ class AbstractAsciidoctorTask extends AbstractAsciidoctorBaseTask { * * When {@link #inProcess} {@code ==} {@link #JAVA_EXEC} this option is ignored. */ + // TODO: parallelMode is currently ignored in 4.0 @Internal boolean parallelMode = true @@ -170,20 +191,26 @@ class AbstractAsciidoctorTask extends AbstractAsciidoctorBaseTask { * * These options are ignored if {@link #inProcess} {@code ==} {@link #IN_PROCESS}. * - * @param configurator Closure that configures a {@link org.ysb33r.grolifant.api.v4.JavaForkOptions} instance. + * @param configurator Closure that configures a {@link JavaForkOptions} instance. + * + * @deprecated Use {@link #jvm} instead. */ - void forkOptions(@DelegatesTo(org.ysb33r.grolifant.api.v4.JavaForkOptions) Closure configurator) { - executeDelegatingClosure(this.javaForkOptions, configurator) + @Deprecated + void forkOptions(@DelegatesTo(JavaForkOptionsWithEnvProvider) Closure configurator) { + jvm(configurator) } /** Set fork options for {@link #JAVA_EXEC} and {@link #OUT_OF_PROCESS} modes. * * These options are ignored if {@link #inProcess} {@code ==} {@link #IN_PROCESS}. * - * @param configurator Action that configures a {@link org.ysb33r.grolifant.api.v4.JavaForkOptions} instance. + * @param configurator Action that configures a {@link JavaForkOptions} instance. + * + * @deprecated Use {@link #jvm} instead. */ - void forkOptions(Action configurator) { - configurator.execute(this.javaForkOptions) + @Deprecated + void forkOptions(Action configurator) { + jvm(configurator) } /** Returns all of the Asciidoctor options. @@ -275,12 +302,13 @@ class AbstractAsciidoctorTask extends AbstractAsciidoctorBaseTask { @Classpath @SuppressWarnings('Instanceof') FileCollection getConfigurations() { - FileCollection precompiledExtensions = findDependenciesInExtensions() + final precompiledExtensions = findDependenciesInExtensions() FileCollection fc = this.asciidocConfigurations.inject(asciidoctorj.configuration) { FileCollection seed, Object it -> - seed + asConfiguration(project, it) + seed + projectOperations.configurations.asConfiguration(it) } - precompiledExtensions ? fc + precompiledExtensions : fc + final gjp = projectOperations.fsOperations.files([gemJarProviders, fc]) + precompiledExtensions ? gjp + precompiledExtensions : gjp } /** Override any existing configurations except the ones available via the {@code asciidoctorj} task extension. @@ -318,44 +346,103 @@ class AbstractAsciidoctorTask extends AbstractAsciidoctorBaseTask { */ @Override Set getReportableConfigurations() { - ([asciidoctorj.configuration] + asConfigurations(project, asciidocConfigurations)).toSet() + ([asciidoctorj.configuration] + projectOperations.configurations.asConfigurations(asciidocConfigurations)) + .toSet() } /** - * Additional locations to consider when GEMs are loaded by AsciidoctorJ or JRuby. + * Adds a Jar of GEMs to the classpath. * - * @return The additional GEM locations. + * @param gemJar Provider to a {@link Jar} task which contain GEMs * * @since 4.0 */ - @Internal - String getGemPath() { - asciidoctorj.asGemPath() - } - - @SuppressWarnings('UnnecessaryGetter') - @TaskAction - void processAsciidocSources() { - validateConditions() - - languagesAsOptionals.each { Optional lang -> - Workspace workspace = lang.present ? prepareWorkspace(lang.get()) : prepareWorkspace() - Set sourceFiles = workspace.sourceTree.files - - if (finalProcessMode != JAVA_EXEC) { - runWithWorkers( - workspace.workingSourceDir, - sourceFiles, - lang - ) - } else { - runWithJavaExec( - workspace.workingSourceDir, - sourceFiles, - lang - ) + void withGemJar(TaskProvider gemJar) { + dependsOn(gemJar) + this.gemJarProviders.add(gemJar.map { it.archiveFile.get().asFile }) + } + + /** + * Adds a Jar of GEMs to the classpath. + * + * @param gemJar name of a {@link Jar} task which contain GEMs. + * + * @since 4.0 + */ + void withGemJar(String taskName) { + dependsOn(taskName) + final gemJar = project.tasks.named(taskName, Jar) + this.gemJarProviders.add(gemJar.map { it.archiveFile.get().asFile }) + } + + /** + * Adds a directory to the classpath. THe directory will contain unpacked GEMs. + * + * @param gemPath A provider to a directory containing unpakcing GEMs. + * + * @param builtBy THe name of the task that prepares this directory. + * + * @since 4.0 + */ + void withGemPath(Provider gemPath, String builtBy) { + dependsOn(builtBy) + this.gemJarProviders.add(gemPath) + } + + /** + * Sets the execution mode. + *

      + * This allows for JVM tasks to be executed either on a worker queue inside the JVM using an isolated classpath, + * ouside the JVM in a separate process, OR using a classic {@code javaexec}. + * + *

      + * If nothing is set, the default is {@link ExecutionMode#JAVA_EXEC}. + * + * @param em Execution mode. + * @throw UnsupportedConfigurationException is mode is illegal within context. + */ + @Override + void setExecutionMode(ExecutionMode em) { + super.setExecutionMode(em) + inProcess = em + + if (em == JAVA_EXEC) { + runnerSpec { + setArgs([getExecConfigurationDataFile(this).absolutePath]) + } + } + } + + void setExecutionMode(String s) { + executionMode = ExecutionMode.valueOf(s.toUpperCase(Locale.US)) + } + + @Override + void exec() { + checkForInvalidSourceDocuments() + checkForIncompatiblePathRoots(baseDirStrategy) + + if (executionMode == JAVA_EXEC) { + entrypoint { + classpath(JavaExecUtils.getJavaExecClasspath( + projectOperations, + configurations, + false // asciidoctorj.injectInternalGuavaJar + )) + } + + final mapping = prepareWorkspaceAndLoadExecutorConfigurations() + + JavaExecUtils.writeExecConfigurationData( + execConfigurationDataFile, + mapping.values().flatten() as List + ) + } else { + entrypoint { + classpath(configurations) } } + super.exec() } /** Initialises the core an Asciidoctor task @@ -365,31 +452,81 @@ class AbstractAsciidoctorTask extends AbstractAsciidoctorBaseTask { */ @SuppressWarnings('ThisReferenceEscapesConstructor') protected AbstractAsciidoctorTask(WorkerExecutor we) { - super() - this.worker = we - this.asciidoctorj = extensions.create(AsciidoctorJExtension.NAME, AsciidoctorJExtension, this) + super(we) - addInputProperty 'required-ruby-modules', { AsciidoctorJExtension aj -> aj.requires } - .curry(this.asciidoctorj) + this.asciidoctorTaskFileOperations = new DefaultAsciidoctorFileOperations(this, 'AsciidoctorJ') + this.workspacePreparation = new DefaultAsciidoctorWorkspacePreparation( + projectOperations, + this.asciidoctorTaskFileOperations, + this.asciidoctorTaskFileOperations + ) + this.asciidoctorOutputOptions = new DefaultAsciidoctorOutputOptions( + projectOperations, + name, + asciidoctorTaskFileOperations + ) + this.baseDirConfiguration = new DefaultAsciidoctorBaseDirConfiguration(project, this) + this.asciidoctorj = extensions.create(AsciidoctorJExtension.NAME, AsciidoctorJExtension, this) + this.projectDir = project.projectDir + this.rootDir = project.rootDir + this.jvmClasspath = project.objects.property(FileCollection) + this.execConfigurationDataFile = getExecConfigurationDataFile(this) + this.detachedConfigurationCreator = { ConfigurationContainer c, List deps -> + final cfg = c.detachedConfiguration(deps.toArray() as Dependency[]) + cfg.canBeConsumed = false + cfg.canBeResolved = true + cfg + }.curry(project.configurations) as Function, Configuration> + + inputs.files(this.asciidoctorj.configuration) + inputs.files { gemJarProviders }.withPathSensitivity(RELATIVE) + inputs.property 'backends', { -> backends() } + inputs.property 'asciidoctorj-version', { -> asciidoctorj.version } + inputs.property 'jruby-version', { -> asciidoctorj.jrubyVersion ?: '' } + execSpec = new AsciidoctorJvmExecSpec(projectOperations) + entrypoint { + mainClass = AsciidoctorJavaExec.canonicalName + } - addInputProperty 'asciidoctor-version', { AsciidoctorJExtension aj -> aj.version } - .curry(this.asciidoctorj) + executionMode = IN_PROCESS + } - addOptionalInputProperty 'jruby-version', { AsciidoctorJExtension aj -> aj.jrubyVersion } - .curry(this.asciidoctorj) + /** + * The Ascidoctor execution mode on the JVM. + * + * @return THe configured execution mode. + * + * @since 4.0 + */ + @Internal + protected ExecutionMode getExecutionMode() { + this.inProcess + } - inputs.files { asciidoctorj.gemPaths } - .withPathSensitivity(RELATIVE) + /** + * Create a worker app executor factory. + * + * @return Instance of an execution factory. + * + * @since 4.0 + */ + @Override + protected WorkerAppExecutorFactory createExecutorFactory() { + new AsciidoctorExecutorFactory() } - /** Name of implementation engine. + /** + * Create a worker app parameter factory. + * + * @return Instance of a parameter factory. * - * @return Always{@code AsciidoctorJ} + * @since 4.0 */ @Override - @Internal - protected String getEngineName() { - 'AsciidoctorJ' + protected WorkerAppParameterFactory createParameterFactory() { + new AsciidoctorWorkerParameterFactory({ -> + owner.prepareWorkspaceAndLoadExecutorConfigurations() + }) } /** @@ -407,7 +544,7 @@ class AbstractAsciidoctorTask extends AbstractAsciidoctorBaseTask { final Set sourceFiles, Optional lang ) { - configuredOutputOptions.backends.collectEntries { String activeBackend -> + backends().collectEntries { String activeBackend -> [ "backend=${activeBackend}".toString(), getExecutorConfigurationFor(activeBackend, workingSourceDir, sourceFiles, lang) @@ -424,22 +561,21 @@ class AbstractAsciidoctorTask extends AbstractAsciidoctorBaseTask { * for generating documentation. * @return Executor configuration */ - @SuppressWarnings('UnnecessaryGetter') + @SuppressWarnings(['UnnecessaryGetter', 'LineLength']) protected ExecutorConfiguration getExecutorConfigurationFor( final String backendName, final File workingSourceDir, final Set sourceFiles, Optional lang ) { - Optional> copyResources = getCopyResourcesForBackends() new ExecutorConfiguration( sourceDir: workingSourceDir, sourceTree: sourceFiles, - outputDir: lang.present ? getOutputDirFor(backendName, lang.get()) : getOutputDirFor(backendName), + outputDir: lang.present ? getOutputDirForBackend(backendName, lang.get()) : getOutputDirForBackend(backendName), baseDir: lang.present ? getBaseDir(lang.get()) : getBaseDir(), - projectDir: project.projectDir, - rootDir: project.rootProject.projectDir, + projectDir: this.projectDir, + rootDir: this.rootDir, options: resolveAsSerializable(evaluateProviders(options), projectOperations.stringTools), failureLevel: failureLevel.level, attributes: resolveAsSerializable( @@ -448,10 +584,9 @@ class AbstractAsciidoctorTask extends AbstractAsciidoctorBaseTask { ), backendName: backendName, logDocuments: logDocuments, - gemPath: gemPath, fatalMessagePatterns: asciidoctorj.fatalWarnings, asciidoctorExtensions: (asciidoctorJExtensions.findAll { !(it instanceof Dependency) }), - requires: requires, + requires: asciidoctorj.requires, copyResources: copyResources.present && (copyResources.get().empty || backendName in copyResources.get()), executorLogLevel: ExecutorUtils.getExecutorLogLevel(asciidoctorj.logLevel), @@ -468,167 +603,79 @@ class AbstractAsciidoctorTask extends AbstractAsciidoctorBaseTask { asciidoctorj.docExtensions } - /** Configure Java fork options prior to execution. - * - * The default method will copy anything configured via {@link #forkOptions(Closure c)} or - * {@link #forkOptions(Action c)} to the provided {@link JavaForkOptions}. - * - * @param pfo Fork options to be configured. - */ - protected void configureForkOptions(JavaForkOptions pfo) { - this.javaForkOptions.copyTo(pfo) + @Nested + protected AsciidoctorTaskFileOperations getAsciidoctorTaskFileOperations() { + this.asciidoctorTaskFileOperations } - /** Allow a task to enhance additional '{@code requires}' - * - * The default implementation will add a special script to deal with verbose mode. - * - * @return The final set of '{@code requires}' - */ - @Internal - protected List getRequires() { - asciidoctorj.requires + @Nested + protected AsciidoctorTaskWorkspacePreparation getWorkspacePreparation() { + this.workspacePreparation } - /** Selects a final process mode. - * - * Some incompatibilities can cause certain process mode to fail given a combination of factors. - * - * Task implementations can override this method to select a safe process mode, than the one provided by the - * build script author. The default implementation will simply return whatever what was configured, except in the - * case for Gradle 4.3 or older in which case it will always return {@link #JAVA_EXEC}. - * - * @return Process mode to use for execution. - */ - @Internal - protected ProcessMode getFinalProcessMode() { - if (inProcess != JAVA_EXEC && GradleVersion.current() < GradleVersion.version(('4.3'))) { - logger.warn('Gradle API classpath leakage will cause issues with Gradle < 4.3. ' + - 'Switching to JAVA_EXEC instead.') - JAVA_EXEC - } else { - this.inProcess - } - } - - private Map runWithWorkers( - final File workingSourceDir, - final Set sourceFiles, - Optional lang - ) { - FileCollection asciidoctorClasspath = configurations - logger.info "Running AsciidoctorJ with workers. Classpath = ${asciidoctorClasspath.files}" - - Map executorConfigurations = getExecutorConfigurations( - workingSourceDir, - sourceFiles, - lang - ) - - if (parallelMode) { - WorkQueue queue = getWorkQueue(asciidoctorClasspath) - executorConfigurations.each { String configName, ExecutorConfiguration executorConfiguration -> - copyResourcesByBackend(executorConfiguration, lang) - queue.submit(AsciidoctorJExecuterWorker) { params -> - params.extensionConfigurationContainer = - new ExecutorConfigurationContainer(executorConfiguration) - } - } - } else { - copyResourcesByBackend(executorConfigurations.values(), lang) - getWorkQueue(asciidoctorClasspath).submit(AsciidoctorJExecuterWorker) { params -> - params.extensionConfigurationContainer = - new ExecutorConfigurationContainer(executorConfigurations.values()) - } - } - executorConfigurations + @Nested + protected AsciidoctorTaskOutputOptions getAsciidoctorOutputOptions() { + this.asciidoctorOutputOptions } - private WorkQueue getWorkQueue(FileCollection asciidoctorClasspath) { - IN_PROCESS ? - worker.classLoaderIsolation(configureClassloaderIsolatedWorker(asciidoctorClasspath)) : - worker.processIsolation(configureProcessIsolatedWorker(asciidoctorClasspath)) + @Nested + protected AsciidoctorTaskBaseDirConfiguration getBaseDirConfiguration() { + this.baseDirConfiguration } - private Closure configureClassloaderIsolatedWorker(FileCollection asciidoctorClasspath) { - return { ClassLoaderWorkerSpec spec -> - spec.classpath.from(asciidoctorClasspath) + private static List ifNoGroovyAddLocal(final List deps) { + if (deps.find { + it.name == 'groovy-all' || it.name == 'groovy' + }) { + [] + } else { + [JavaExecUtils.localGroovy] } } - private Closure configureProcessIsolatedWorker(FileCollection asciidoctorClasspath) { - return { ProcessWorkerSpec spec -> - spec.classpath.from(asciidoctorClasspath) - configureForkOptions(spec.forkOptions) + private Map prepareWorkspacesByLanguage() { + languagesAsOptionals.collectEntries { Optional lang -> + Workspace workspace = prepareWorkspace(lang) + [lang.orElse(''), workspace] } } - private Map runWithJavaExec( - final File workingSourceDir, - final Set sourceFiles, - Optional lang - ) { - FileCollection javaExecClasspath = JavaExecUtils.getJavaExecClasspath( - project, - configurations, - asciidoctorj.injectInternalGuavaJar - ) - Map executorConfigurations = getExecutorConfigurations( - workingSourceDir, - sourceFiles, - lang - ) - File execConfigurationData = JavaExecUtils.writeExecConfigurationData(this, executorConfigurations.values()) - copyResourcesByBackend(executorConfigurations.values(), lang) - - logger.debug("Serialised AsciidoctorJ configuration to ${execConfigurationData}") - logger.info "Running AsciidoctorJ instance with classpath ${javaExecClasspath.files}" - - try { - project.javaexec { JavaExecSpec jes -> - configureForkOptions(jes) - logger.debug "Running AsciidoctorJ instance with environment: ${jes.environment}" - jes.with { - setExecClass(jes, AsciidoctorJavaExec.canonicalName) - classpath = javaExecClasspath - args execConfigurationData.absolutePath - } - } - } catch (GradleException e) { - throw new AsciidoctorRemoteExecutionException( - 'Remote Asciidoctor process failed to complete successfully', - e - ) - } + private Map> prepareWorkspaceAndLoadExecutorConfigurations() { + final sourcesByLang = prepareWorkspacesByLanguage() + final mapping = sourcesByLang.collectEntries { lang, workspace -> + final byLang = Optional.ofNullable(lang) + List loadedConfigurations = getExecutorConfigurations( + workspace.workingSourceDir, + workspace.sourceTree.files, + byLang + ).values().toList() + copyResourcesByExecutorConfiguration(loadedConfigurations, byLang) + [lang, loadedConfigurations] + } as Map> - executorConfigurations + mapping } - /** Attempt to use the mainClass property if we're running a recent enough Gradle version, - * else revert to the old property. - * - * The main property will be removed from JavaExecSpec in Gradle 8.0 and replaced by - * the mainClass property that was added in 6.4. - */ - @CompileDynamic - private void setExecClass(JavaExecSpec jes, String execClass) { - if (PRE_6_4) { - jes.main = execClass + private List> getLanguagesAsOptionals() { + if (this.languages.empty) { + [Optional.empty() as Optional] } else { - jes.mainClass = execClass + Transform.toList(this.languages) { String it -> + Optional.of(it) + } } } - private void copyResourcesByBackend( + private void copyResourcesByExecutorConfiguration( Iterable executorConfigurations, Optional lang ) { for (ExecutorConfiguration ec : executorConfigurations) { - copyResourcesByBackend(ec, lang) + copyResourcesByExecutorConfiguration(ec, lang) } } - private void copyResourcesByBackend( + private void copyResourcesByExecutorConfiguration( ExecutorConfiguration ec, Optional lang ) { @@ -654,44 +701,29 @@ class AbstractAsciidoctorTask extends AbstractAsciidoctorBaseTask { } if (deps.empty && closurePaths.empty) { - null + projectOperations.fsOperations.emptyFileCollection() } else if (closurePaths.empty) { jrubyLessConfiguration(deps) } else if (deps.empty) { - project.files(closurePaths) + projectOperations.fsOperations.files(closurePaths) } else { - jrubyLessConfiguration(deps) + project.files(closurePaths) + jrubyLessConfiguration(deps) + projectOperations.fsOperations.files(closurePaths) } } - private List ifNoGroovyAddLocal(final List deps) { - if (deps.find { - it.name == 'groovy-all' || it.name == 'groovy' - }) { - [] - } else { - [JavaExecUtils.localGroovy] - } - } - - @CompileDynamic - private Configuration jrubyLessConfiguration(List deps) { - Configuration cfg = project.configurations.detachedConfiguration(deps.toArray() as Dependency[]) - cfg.resolutionStrategy.eachDependency { DependencyResolveDetails dsr -> - dsr.with { - if (target.name == 'jruby' && target.group == 'org.jruby') { - useTarget "org.jruby:jruby:${target.version}" - } - } - } + // TODO: Try to do this without a detached configuration + private FileCollection jrubyLessConfiguration(List deps) { + Configuration cfg = detachedConfigurationCreator.apply(deps) + asciidoctorj.loadJRubyResolutionStrategy(cfg) cfg } private Map preparePreserialisedAttributes(final File workingSourceDir, Optional lang) { prepareAttributes( - workingSourceDir, + projectOperations.stringTools, attributes, - lang.present ? asciidoctorj.getAttributesForLang(lang.get()) : [:], + (lang.present ? asciidoctorj.getAttributesForLang(lang.get()) : [:]), + getTaskSpecificDefaultAttributes(workingSourceDir) as Map, attributeProviders, lang ) @@ -703,16 +735,17 @@ class AbstractAsciidoctorTask extends AbstractAsciidoctorBaseTask { } as List } - @SuppressWarnings('AbstractClassWithoutAbstractMethod') - abstract static class AsciidoctorJExecuterWorker implements WorkAction { - static interface Params extends WorkParameters { - ExecutorConfigurationContainer getExtensionConfigurationContainer() - void setExtensionConfigurationContainer(ExecutorConfigurationContainer container) - } - - @Override - void execute() { - new AsciidoctorJExecuter(parameters.extensionConfigurationContainer).run() - } - } +// @SuppressWarnings('AbstractClassWithoutAbstractMethod') +// abstract static class AsciidoctorJExecuterWorker implements WorkAction { +// static interface Params extends WorkParameters { +// ExecutorConfigurationContainer getExtensionConfigurationContainer() +// +// void setExtensionConfigurationContainer(ExecutorConfigurationContainer container) +// } +// +// @Override +// void execute() { +// new AsciidoctorJExecuter(parameters.extensionConfigurationContainer).run() +// } +// } } diff --git a/jvm/src/main/groovy/org/asciidoctor/gradle/jvm/AsciidoctorJBasePlugin.groovy b/jvm/src/main/groovy/org/asciidoctor/gradle/jvm/AsciidoctorJBasePlugin.groovy index a290ae428..07041b21b 100644 --- a/jvm/src/main/groovy/org/asciidoctor/gradle/jvm/AsciidoctorJBasePlugin.groovy +++ b/jvm/src/main/groovy/org/asciidoctor/gradle/jvm/AsciidoctorJBasePlugin.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/jvm/src/main/groovy/org/asciidoctor/gradle/jvm/AsciidoctorJExtension.groovy b/jvm/src/main/groovy/org/asciidoctor/gradle/jvm/AsciidoctorJExtension.groovy index 4403be9db..8d986c72a 100644 --- a/jvm/src/main/groovy/org/asciidoctor/gradle/jvm/AsciidoctorJExtension.groovy +++ b/jvm/src/main/groovy/org/asciidoctor/gradle/jvm/AsciidoctorJExtension.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,21 +20,23 @@ import groovy.transform.CompileStatic import org.asciidoctor.gradle.base.AbstractImplementationEngineExtension import org.asciidoctor.gradle.base.ModuleNotFoundException import org.asciidoctor.gradle.base.Transform +import org.asciidoctor.gradle.internal.DefaultAsciidoctorJModules import org.asciidoctor.gradle.internal.JavaExecUtils @java.lang.SuppressWarnings('NoWildcardImports') import org.gradle.api.* @java.lang.SuppressWarnings('NoWildcardImports') import org.gradle.api.artifacts.* import org.gradle.api.artifacts.dsl.DependencyHandler -import org.gradle.api.file.FileCollection import org.gradle.api.logging.LogLevel -import org.gradle.util.GradleVersion -import org.ysb33r.grolifant.api.core.OperatingSystem +import java.util.concurrent.Callable +import java.util.function.BiConsumer +import java.util.function.BiFunction +import java.util.function.Function import java.util.regex.Pattern -import static org.ysb33r.grolifant.api.v4.ClosureUtils.configureItem -import static org.ysb33r.grolifant.api.v4.StringUtils.stringize +import static groovy.lang.Closure.DELEGATE_FIRST +import static org.ysb33r.grolifant.api.core.ClosureUtils.configureItem /** Extension for configuring AsciidoctorJ. * @@ -60,41 +62,36 @@ class AsciidoctorJExtension extends AbstractImplementationEngineExtension { private static final String ASCIIDOCTORJ_LEANPUB_DEPENDENCY = "${ASCIIDOCTORJ_GROUP}:asciidoctor-leanpub-markdown" private static final String JRUBY_COMPLETE_DEPENDENCY = JavaExecUtils.JRUBY_COMPLETE_DEPENDENCY private static final String ASCIIDOCTOR_DEPENDENCY_PROPERTY_NAME = 'asciidoctorj' - private static final OperatingSystem OS = OperatingSystem.current() - private static final boolean GUAVA_REQUIRED_FOR_EXTERNALS = GradleVersion.current() >= GradleVersion.version('4.8') + private static final String CONFIGURATION_NAME = "__\$\$${NAME}\$\$__" - private Object version - private Optional jrubyVersion + // TODO: Kill this off +// private static final boolean GUAVA_REQUIRED_FOR_EXTERNALS = !LegacyLevel.PRE_4_8 - private Boolean injectGuavaJar + private static final BiConsumer> DRD_VERSION_RESOLVER = { + DependencyResolveDetails drd, Callable versionResolver -> + drd.useVersion(versionResolver.call()) + } as BiConsumer> private final LogLevel defaultLogLevel private final Map options = [:] private final List jrubyRequires = [] private final List asciidoctorExtensions = [] - private final List gemPaths = [] - private final List> resolutionsStrategies = [] - private final List> configurationCallbacks = [] +// private final List gemPaths = [] private final List warningsAsErrors = [] - private final AsciidoctorJModules modules - - @Deprecated - // We need to find a better solution than the curretn detached configuration usage. - private final ConfigurationContainer configurations - - @Deprecated - // We need to find a better way than how we are creasting dependencies atm. - private final DependencyHandler dependencies - + private final DefaultAsciidoctorJModules modules + private final Configuration publicConfiguration + private final Configuration privateConfiguration + private final BiFunction dependencyCreator + private final Function projectDependency + private Object version + private Optional jrubyVersion private boolean onlyTaskOptions = false - private boolean onlyTaskRequires = false private boolean onlyTaskExtensions = false - private boolean onlyTaskGems = false private boolean onlyTaskWarnings = false - private boolean onlyTaskResolutionStrategies = false - private boolean onlyTaskConfigurationCallbacks = false - private LogLevel logLevel +// private Boolean injectGuavaJar + private boolean onlyTaskRequires = false +// private boolean onlyTaskGems = false /** Attach extension to a project. * @@ -103,16 +100,29 @@ class AsciidoctorJExtension extends AbstractImplementationEngineExtension { @SuppressWarnings('ThisReferenceEscapesConstructor') AsciidoctorJExtension(Project project) { super(project, 'asciidoctorj-extension') + + String privateName = "${CONFIGURATION_NAME}_d" + String publicName = "${CONFIGURATION_NAME}_r" + projectOperations.configurations.createLocalRoleFocusedConfiguration(privateName, publicName) + this.privateConfiguration = project.configurations.getByName(privateName) + this.publicConfiguration = project.configurations.getByName(publicName) + loadStandardPublicConfigurationResolutionStrategy() + + this.dependencyCreator = createDependencyLoader(project.dependencies, this.privateConfiguration) + this.projectDependency = createProjectDependencyLoader(project.dependencies) this.version = defaultVersionMap[ASCIIDOCTOR_DEPENDENCY_PROPERTY_NAME] - this.modules = new AsciidoctorJModules(this, defaultVersionMap) + this.modules = new DefaultAsciidoctorJModules(projectOperations, this, defaultVersionMap) this.defaultLogLevel = project.logging.level if (this.version == null) { throw new ModuleNotFoundException('Default version for AsciidoctorJ must be defined. ' + 'Please report a bug at https://github.com/asciidoctor/asciidoctor-gradle-plugin/issues' ) } - this.configurations = project.configurations - this.dependencies = project.dependencies +// this.configurations = project.configurations +// this.dependencies = project.dependencies + + this.modules.onUpdate { owner.updateConfiguration() } + updateConfiguration() } /** Attach extension to a task. @@ -122,9 +132,23 @@ class AsciidoctorJExtension extends AbstractImplementationEngineExtension { @SuppressWarnings('ThisReferenceEscapesConstructor') AsciidoctorJExtension(Task task) { super(task, NAME) - this.modules = new AsciidoctorJModules(this, defaultVersionMap) - this.configurations = task.project.configurations - this.dependencies = task.project.dependencies + String privateName = "__\$\$${NAME}_${task.name}\$\$__d" + String publicName = "__\$\$${NAME}_${task.name}\$\$__r" + projectOperations.configurations.createLocalRoleFocusedConfiguration(privateName, publicName) + this.privateConfiguration = task.project.configurations.getByName(privateName) + this.publicConfiguration = task.project.configurations.getByName(publicName) + loadStandardPublicConfigurationResolutionStrategy() + + Configuration projectConfiguration = task.project.configurations.findByName("${CONFIGURATION_NAME}_d") + if (projectConfiguration) { + this.publicConfiguration.extendsFrom(projectConfiguration) + } + + this.dependencyCreator = createDependencyLoader(task.project.dependencies, this.privateConfiguration) + this.projectDependency = createProjectDependencyLoader(task.project.dependencies) + this.modules = new DefaultAsciidoctorJModules(projectOperations, this, defaultVersionMap) + this.modules.onUpdate { owner.updateConfiguration() } + updateConfiguration() } /* ------------------------- @@ -157,7 +181,8 @@ class AsciidoctorJExtension extends AbstractImplementationEngineExtension { } } - /** Defines extensions to be registered. The given parameters should + /** + * Defines extensions to be registered. The given parameters should * either contain Asciidoctor Groovy DSL closures or files * with content conforming to the Asciidoctor Groovy DSL. * @@ -167,7 +192,8 @@ class AsciidoctorJExtension extends AbstractImplementationEngineExtension { addExtensions(exts as List) } - /** Clears the existing list of extensions and replace with a new set. + /** + * Clears the existing list of extensions and replace with a new set. * * If this is declared on a task extension all extention from the global * project extension will be ignored. @@ -235,56 +261,57 @@ class AsciidoctorJExtension extends AbstractImplementationEngineExtension { ~/include file not found/ } - /* ------------------------- - tag::extension-property[] - gemPaths:: One or more gem installation directories (separated by the system path separator). - Use `gemPaths` to append. Use `setGemPaths` or `gemPaths=['path1','path2']` to overwrite. - Use `asGemPath` to obtain a path string, separated by platform-specific separator. - Type: `FileCollection`, but any collection of objects convertible with `project.files` can be passed - Default: empty - end::extension-property[] - ------------------------- */ - - /** Returns the list of paths to be used for {@code GEM_HOME} - * - */ - FileCollection getGemPaths() { - if (!task || onlyTaskGems) { - projectOperations.files(this.gemPaths) - } else { - projectOperations.files(this.gemPaths) + extFromProject.gemPaths - } - } - - /** Sets a new list of GEM paths to be used. - * - * @param paths Paths resolvable by {@ocde project.files} - */ - void setGemPaths(Iterable paths) { - this.gemPaths.clear() - this.gemPaths.addAll(paths) - - if (task) { - this.onlyTaskGems = true - } - } - - /** Adds more paths for discovering GEMs. - * - * @param f Path objects that can be be converted with {@code project.file}. - */ - void gemPaths(Object... f) { - this.gemPaths.addAll(f) - } - - /** Returns the list of paths to be used for GEM installations in a format that is - * suitable for assignment to {@code GEM_HOME} - * - * Calling this will cause gemPath to be resolved immediately. - */ - String asGemPath() { - getGemPaths().files*.toString().join(OS.pathSeparator) - } +// /* ------------------------- +// tag::extension-property[] +// gemPaths:: One or more gem installation directories (separated by the system path separator). +// Use `gemPaths` to append. Use `setGemPaths` or `gemPaths=['path1','path2']` to overwrite. +// Use `asGemPath` to obtain a path string, separated by platform-specific separator. +// Type: `FileCollection`, but any collection of objects convertible with `project.files` can be passed +// Default: empty +// end::extension-property[] +// ------------------------- */ +// +// /** Returns the list of paths to be used for {@code GEM_HOME} +// * +// */ +// FileCollection getGemPaths() { +// if (!task || onlyTaskGems) { +// projectOperations.fsOperations.files(this.gemPaths) +// } else { +// projectOperations.fsOperations.files(this.gemPaths).from(extFromProject.gemPaths) +// } +// } +// +// /** Sets a new list of GEM paths to be used. +// * +// * @param paths Paths resolvable by {@ocde project.files} +// */ +// void setGemPaths(Iterable paths) { +// this.gemPaths.clear() +// this.gemPaths.addAll(paths) +// +// if (task) { +// this.onlyTaskGems = true +// } +// } +// +// /** Adds more paths for discovering GEMs. +// * +// * @param f Path objects that can be be converted with {@code project.file}. +// */ +// void gemPaths(Object... f) { +// this.gemPaths.addAll(f) +// } +// +// /** +// * Returns the list of paths to be used for GEM installations in a format that is +// * suitable for assignment to {@code GEM_HOME} +// * +// * Calling this will cause gemPath to be resolved immediately. +// */ +// String asGemPath() { +// getGemPaths().files*.toString().join(OS.pathSeparator) +// } /* ------------------------- tag::extension-property[] @@ -306,12 +333,12 @@ class AsciidoctorJExtension extends AbstractImplementationEngineExtension { String getJrubyVersion() { if (task) { if (this.jrubyVersion != null && this.jrubyVersion.present) { - stringize(this.jrubyVersion.get()) + projectOperations.stringTools.stringize(this.jrubyVersion.get()) } else { extFromProject.getJrubyVersion() } } else { - this.jrubyVersion?.present ? stringize(this.jrubyVersion.get()) : null + this.jrubyVersion?.present ? projectOperations.stringTools.stringize(this.jrubyVersion.get()) : null } } @@ -462,9 +489,11 @@ class AsciidoctorJExtension extends AbstractImplementationEngineExtension { * @since 1.5.0 */ List getRequires() { - stringizeList(this.jrubyRequires, onlyTaskRequires) { AsciidoctorJExtension it -> - it.requires - }.toList() + stringizeList( + this.jrubyRequires, + onlyTaskRequires, + x -> ((AsciidoctorJExtension) x).requires + ).toList() } /** Applies a new set of Ruby modules to be included, clearing any previous set. @@ -499,122 +528,24 @@ class AsciidoctorJExtension extends AbstractImplementationEngineExtension { end::extension-property[] ------------------------- */ - /** List of resolution strategies that will be applied to the Asciidoctorj group of dependencies. - * - * If called on a task, the project extensions's resolution strategies are returned ahead of the task-specific - * resolution strategies. - * - * If called on a task where {@link #clearResolutionStrategies} was called previously, only the task-specifc - * resolution strategies are returned. - * - * @return List of actions. Can be empty, but never {@code null}. - * - * @since 3.1.0 - */ - Iterable> getResolutionStrategies() { - if (task) { - if (onlyTaskResolutionStrategies) { - this.resolutionsStrategies - } else { - extFromProject.resolutionsStrategies + this.resolutionsStrategies - } - } else { - this.resolutionsStrategies - } - } - - /** Clears the current list of resolution strategies. - * - * If called on a task extension, all subsequent strategies added to the project extension will be ignored - * in task context. - */ - void clearResolutionStrategies() { - if (task) { - onlyTaskResolutionStrategies = true - } - this.resolutionsStrategies.clear() - } - - /** Adds a resolution strategy for resolving asciidoctorj related dependencies + /** + * Adds rules to the resolution strategy for resolving asciidoctorj related dependencies * * @param strategy Additional resolution strategy. Takes a {@link ResolutionStrategy} as parameter. */ void resolutionStrategy(Action strategy) { - this.resolutionsStrategies.add(strategy) + this.publicConfiguration.resolutionStrategy(strategy) } - /** Adds a resolution strategy for resolving asciidoctorj related dependencies + /** + * Adds rules to the resolution strategy for resolving asciidoctorj related dependencies * * @param strategy Additional resolution strategy. Takes a {@link ResolutionStrategy} as parameter. */ void resolutionStrategy(@DelegatesTo(ResolutionStrategy) Closure strategy) { - this.resolutionsStrategies.add(strategy as Action) - } - - /* ------------------------- - tag::extension-property[] - onConfiguration:: Additional actions to be performed when the detached configuration for the - {asciidoctorj-name} is created. - end::extension-property[] - ------------------------- */ - - /** List of callbacks that will be applied whent he asciidoctorj-related detached configuration is created. - * - * If called on a task, the project extensions's callbacks are returned ahead of the task-specific - * callbacks. - * - * If called on a task where {@link #clearConfigurationCallbacks} was called previously, only the task-specifc - * callbacks are returned. - * - * @return List of callbacks. Can be empty, but never {@code null}. - * - * @since 3.1.0 - */ - Iterable> getConfigurationCallbacks() { - if (task) { - if (onlyTaskConfigurationCallbacks) { - this.configurationCallbacks - } else { - extFromProject.configurationCallbacks + this.configurationCallbacks - } - } else { - this.configurationCallbacks - } - } - - /** Clears the current list of resolution strategies. - * - * If called on a task extension, all subsequent callbacks added to the project extension will be ignored - * in task context. - * - * @since 3.1.0 - */ - void clearConfigurationCallbacks() { - if (task) { - onlyTaskConfigurationCallbacks = true - } - - this.configurationCallbacks.clear() - } - - /** Adds a callback for when the asciidoctorj-related detached configuration is created. - * - * @param callback The detached configuration is passed to this {@code Action}. - * - * @since 3.1.0 - */ - void onConfiguration(Action callback) { - this.configurationCallbacks.add(callback) - } - - /** Adds a callback for when the asciidoctorj-related detached configuration is created. - * - * @param callback The detached configuration is passed to this closure}. - * - * @since 3.1.0 - */ - void onConfiguration(@DelegatesTo(Configuration) Closure callback) { - this.configurationCallbacks.add(callback as Action) + Closure cfg = (Closure) strategy.clone() + cfg.resolveStrategy = DELEGATE_FIRST + this.publicConfiguration.resolutionStrategy(cfg) } /* ------------------------- @@ -623,118 +554,75 @@ class AsciidoctorJExtension extends AbstractImplementationEngineExtension { end::extension-property[] ------------------------- */ - /** Version of AsciidoctorJ that should be used. + /** + * Version of AsciidoctorJ that should be used. * + * @return Asciidoctor version */ String getVersion() { if (task) { - this.version ? stringize(this.version) : extFromProject.getVersion() + this.version ? projectOperations.stringTools.stringize(this.version) : extFromProject.getVersion() } else { - stringize(this.version) + projectOperations.stringTools.stringize(this.version) } } /** Set a new version to use. * - * @param v New version to be used. Can be of anything that be be resolved by {@link stringize ( Object o )} + * @param v New version to be used. Can be of anything that be be resolved by + * {@link org.ysb33r.grolifant.api.core.StringTools#stringize ( Object o )} */ void setVersion(Object v) { this.version = v } - /** Whether the Guava JAR that ships with the Gradle distribution should be injected into the - * classpath for external AsciidoctorJ processes. - * - * If not set previously via {@link #setInjectInternalGuavaJar} then a default version depending of the version of - * the Gradle distribution will be used. - * - * @return {@code true} if JAR should be injected. +// /** Whether the Guava JAR that ships with the Gradle distribution should be injected into the +// * classpath for external AsciidoctorJ processes. +// * +// * If not set previously via {@link #setInjectInternalGuavaJar} then a default version depending of the version of +// * the Gradle distribution will be used. +// * +// * @return {@code true} if JAR should be injected. +// */ +// boolean getInjectInternalGuavaJar() { +// if (task) { +// this.injectGuavaJar == null ? extFromProject.injectInternalGuavaJar : this.injectGuavaJar +// } else { +// this.injectGuavaJar == null ? GUAVA_REQUIRED_FOR_EXTERNALS : this.injectGuavaJar +// } +// } + +// /** Whether the Guava JAR that ships with the Gradle distribution should be injected into the +// * classpath for external AsciidoctorJ processes. +// * +// * @param inject {@code true} if JAR should be injected. +// */ +// void setInjectInternalGuavaJar(boolean inject) { +// this.injectGuavaJar = inject +// } + + /** + * Returns a runConfiguration of the configured AsciidoctorJ dependencies. + * + * @return Resolvable configuration. */ - boolean getInjectInternalGuavaJar() { - if (task) { - this.injectGuavaJar == null ? extFromProject.injectInternalGuavaJar : this.injectGuavaJar - } else { - this.injectGuavaJar == null ? GUAVA_REQUIRED_FOR_EXTERNALS : this.injectGuavaJar - } + Configuration getConfiguration() { + this.publicConfiguration } - /** Whether the Guava JAR that ships with the Gradle distribution should be injected into the - * classpath for external AsciidoctorJ processes. + /** + * Loads a JRUBy resolution rule onto the given configuration. * - * @param inject {@code true} if JAR should be injected. - */ - void setInjectInternalGuavaJar(boolean inject) { - this.injectGuavaJar = inject - } - - /** Returns a runConfiguration of the configured AsciidoctorJ dependencies. + * @param cfg Configuration. * - * @return A non-attached runConfiguration. + * @since 4.0 */ - Configuration getConfiguration() { - final String gDslVer = finalGroovyDslVersion - final String pdfVer = finalPdfVersion - final String epubVer = finalEpubVersion - final String leanpubVer = finalLeanpubVersion - final String diagramVer = finalDiagramVersion - final String jrubyVer = getJrubyVersion() ?: minimumSafeJRubyVersion(getVersion()) - final String jrubyCompleteDep = "${JRUBY_COMPLETE_DEPENDENCY}:${jrubyVer}" - - List deps = [createDependency("${ASCIIDOCTORJ_CORE_DEPENDENCY}:${getVersion()}")] - - if (gDslVer != null) { - deps.add(createDependency("${ASCIIDOCTORJ_GROOVY_DSL_DEPENDENCY}:${gDslVer}")) - } - - if (pdfVer != null) { - deps.add(createDependency("${ASCIIDOCTORJ_PDF_DEPENDENCY}:${pdfVer}")) - } - - if (epubVer != null) { - deps.add(createDependency("${ASCIIDOCTORJ_EPUB_DEPENDENCY}:${epubVer}")) - } - - if (leanpubVer != null) { - deps.add(createDependency("${ASCIIDOCTORJ_LEANPUB_DEPENDENCY}:${leanpubVer}")) - } - - if (diagramVer != null) { - deps.add(createDependency( - "${ASCIIDOCTORJ_DIAGRAM_DEPENDENCY}:${diagramVer}", excludeTransitiveAsciidoctorJ() - )) - } - - deps.add( - createDependency(jrubyCompleteDep) - ) - - Configuration configuration = configurations.detachedConfiguration( - deps.toArray() as Dependency[] - ) - - configuration.resolutionStrategy.eachDependency { DependencyResolveDetails dsr -> + void loadJRubyResolutionStrategy(Configuration cfg) { + cfg.resolutionStrategy.eachDependency { DependencyResolveDetails dsr -> if (dsr.target.name == 'jruby' && dsr.target.group == 'org.jruby') { dsr.useTarget "${JRUBY_COMPLETE_DEPENDENCY}:${dsr.target.version}" } } - - resolutionStrategies.each { - configuration.resolutionStrategy(it) - } - - configurationCallbacks.each { - it.execute(configuration) - } - - configuration - } - - private Dependency createDependency(final String notation, final Closure configurator = null) { - if (configurator) { - dependencies.create(notation, configurator) - } else { - dependencies.create(notation) - } } private AsciidoctorJExtension getExtFromProject() { @@ -764,13 +652,13 @@ class AsciidoctorJExtension extends AbstractImplementationEngineExtension { asciidoctorExtensions.addAll(dehydrateExtensions(newExtensions)) } - /** Prepare extensions for serialisation. + /** + * Prepare extensions for serialisation. * * This takes care of dehydrating any closures. * * @param exts List of extensions * @return List of extensions suitable for serialization. - * */ private List dehydrateExtensions(final List exts) { Transform.toList(exts) { @@ -779,7 +667,7 @@ class AsciidoctorJExtension extends AbstractImplementationEngineExtension { ((Closure) it).dehydrate() break case Project: - dependencies.project(path: ((Project) it).path) + projectDependency.apply(((Project) it)) break default: it @@ -787,7 +675,7 @@ class AsciidoctorJExtension extends AbstractImplementationEngineExtension { } } - @SuppressWarnings('UnusedPrivateMethodParameter') + @SuppressWarnings(['UnusedPrivateMethodParameter', 'UnusedPrivateMethod']) private String minimumSafeJRubyVersion(final String asciidoctorjVersion) { '9.1.0.0' } @@ -795,7 +683,7 @@ class AsciidoctorJExtension extends AbstractImplementationEngineExtension { @SuppressWarnings('Instanceof') private List patternize(final List patterns) { Transform.toList(patterns) { - (Pattern) (it instanceof Pattern ? it : ~/${stringize(it)}/) + (Pattern) (it instanceof Pattern ? it : ~/${projectOperations.stringTools.stringize(it)}/) } } @@ -808,6 +696,70 @@ class AsciidoctorJExtension extends AbstractImplementationEngineExtension { } } + private void updateConfiguration() { + final String gDslVer = finalGroovyDslVersion + final String pdfVer = finalPdfVersion + final String epubVer = finalEpubVersion + final String leanpubVer = finalLeanpubVersion + final String diagramVer = finalDiagramVersion + + loadDependencyRuleOnce(ASCIIDOCTORJ_CORE_DEPENDENCY) { -> owner.version } + loadDependencyRuleOnce(JRUBY_COMPLETE_DEPENDENCY) { -> + owner.getJrubyVersion() ?: owner.minimumSafeJRubyVersion(owner.getVersion()) + } + + if (gDslVer != null) { + loadDependencyRuleOnce(ASCIIDOCTORJ_GROOVY_DSL_DEPENDENCY) { -> owner.finalGroovyDslVersion } + } + + if (pdfVer != null) { + loadDependencyRuleOnce(ASCIIDOCTORJ_PDF_DEPENDENCY) { -> owner.finalPdfVersion } + } + + if (epubVer != null) { + loadDependencyRuleOnce(ASCIIDOCTORJ_EPUB_DEPENDENCY) { -> owner.finalEpubVersion } + } + + if (leanpubVer != null) { + loadDependencyRuleOnce(ASCIIDOCTORJ_LEANPUB_DEPENDENCY) { -> owner.finalLeanpubVersion } + } + + if (diagramVer != null) { + loadDependencyRuleOnce( + ASCIIDOCTORJ_DIAGRAM_DEPENDENCY, + { -> owner.finalDiagramVersion }, + excludeTransitiveAsciidoctorJ() + ) + } + } + + @SuppressWarnings('DuplicateNumberLiteral') + private void loadDependencyRuleOnce( + final String coords, + Callable versionResolver, + @DelegatesTo(ExternalModuleDependency) Closure configurator = null + ) { + final parts = coords.split(':', 2) + + if (parts.size() != 2) { + throw new ModuleNotFoundException("'${coords}' is not a valid group:name format") + } + + if (!this.privateConfiguration.dependencies.find { + it.group == parts[0] && it.name == parts[1] + }) { + final initialVersion = versionResolver.call() + dependencyCreator.apply("${coords}:${initialVersion}".toString(), configurator) + this.publicConfiguration.resolutionStrategy { ResolutionStrategy rs -> + rs.eachDependency { drd -> + if (drd.requested.group == parts[0] && drd.requested.name == parts[1]) { + DRD_VERSION_RESOLVER.accept(drd, versionResolver) + } + } + } + } + } + private String getFinalGroovyDslVersion() { if (task) { this.modules.groovyDsl.version ?: extFromProject.modules.groovyDsl.version @@ -847,4 +799,24 @@ class AsciidoctorJExtension extends AbstractImplementationEngineExtension { extFromProject.modules.diagram.version } } + + private void loadStandardPublicConfigurationResolutionStrategy() { + loadJRubyResolutionStrategy(publicConfiguration) + } + + private BiFunction createDependencyLoader(DependencyHandler deps, Configuration cfg) { + { String cfgName, String coords, Closure configurator -> + if (configurator != null) { + deps.add(cfgName, coords, configurator) + } else { + deps.add(cfgName, coords) + } + }.curry(cfg.name) as BiFunction + } + + private Function createProjectDependencyLoader(DependencyHandler deps) { + { Project p -> + deps.project(path: p.path) + } as Function + } } diff --git a/jvm/src/main/groovy/org/asciidoctor/gradle/jvm/AsciidoctorJModules.java b/jvm/src/main/groovy/org/asciidoctor/gradle/jvm/AsciidoctorJModules.java new file mode 100644 index 000000000..bde8b68cd --- /dev/null +++ b/jvm/src/main/groovy/org/asciidoctor/gradle/jvm/AsciidoctorJModules.java @@ -0,0 +1,131 @@ +/* + * Copyright 2013-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://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.asciidoctor.gradle.jvm; + +import groovy.lang.Closure; +import groovy.lang.DelegatesTo; +import org.asciidoctor.gradle.base.AsciidoctorModuleDefinition; +import org.gradle.api.Action; + +/** + * Define versions for standard AsciidoctorJ modules. + * + * @since 4.0 + * + * @author Schalk W. Cronjé + */ +public interface AsciidoctorJModules { + + /** + * + * @param cfg + */ + default void pdf(Action cfg) { + cfg.execute(getPdf()); + } + + /** + * + * @param cfg + */ + void pdf(@DelegatesTo(AsciidoctorModuleDefinition.class) Closure cfg); + + /** + * + * @return + */ + AsciidoctorModuleDefinition getPdf(); + + /** + * + * @param cfg + */ + default void epub(Action cfg) { + cfg.execute(getEpub()); + } + + /** + * + * @param cfg + */ + void epub(@DelegatesTo(AsciidoctorModuleDefinition.class) Closure cfg); + + /** + * + * @return + */ + AsciidoctorModuleDefinition getEpub(); + + /** + * + * @param cfg + */ + default void leanpub(Action cfg) { + cfg.execute(getLeanpub()); + } + + /** + * + * @param cfg + */ + void leanpub(@DelegatesTo(AsciidoctorModuleDefinition.class) Closure cfg); + + /** + * + * @return + */ + AsciidoctorModuleDefinition getLeanpub(); + + /** + * + * @param cfg + */ + default void diagram(Action cfg) { + cfg.execute(getDiagram()); + } + + /** + * + * @param cfg + */ + void diagram(@DelegatesTo(AsciidoctorModuleDefinition.class) Closure cfg); + + /** + * + * @return + */ + AsciidoctorModuleDefinition getDiagram(); + + /** + * + * @param cfg + */ + default void groovyDsl(Action cfg) { + cfg.execute(getGroovyDsl()); + } + + /** + * + * @param cfg + */ + void groovyDsl(@DelegatesTo(AsciidoctorModuleDefinition.class) Closure cfg); + + /** + * + * @return + */ + AsciidoctorModuleDefinition getGroovyDsl(); +} diff --git a/jvm/src/main/groovy/org/asciidoctor/gradle/jvm/AsciidoctorJPlugin.groovy b/jvm/src/main/groovy/org/asciidoctor/gradle/jvm/AsciidoctorJPlugin.groovy index d4860255a..54a83e1a8 100644 --- a/jvm/src/main/groovy/org/asciidoctor/gradle/jvm/AsciidoctorJPlugin.groovy +++ b/jvm/src/main/groovy/org/asciidoctor/gradle/jvm/AsciidoctorJPlugin.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,8 +20,6 @@ import org.gradle.api.Action import org.gradle.api.Plugin import org.gradle.api.Project -import static org.ysb33r.grolifant.api.v4.TaskProvider.registerTask - /** * @since 2.0.0 * @author Schalk W. Cronjé @@ -42,6 +40,6 @@ class AsciidoctorJPlugin implements Plugin { } } - registerTask(project, 'asciidoctor', AsciidoctorTask, asciidoctorDefaults) + project.tasks.register('asciidoctor', AsciidoctorTask, asciidoctorDefaults) } } diff --git a/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/AsciidoctorJSNodeExtension.groovy b/jvm/src/main/groovy/org/asciidoctor/gradle/jvm/AsciidoctorJvmExecSpec.groovy similarity index 54% rename from js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/AsciidoctorJSNodeExtension.groovy rename to jvm/src/main/groovy/org/asciidoctor/gradle/jvm/AsciidoctorJvmExecSpec.groovy index 3f535029a..3a5344452 100644 --- a/js/src/main/groovy/org/asciidoctor/gradle/js/nodejs/AsciidoctorJSNodeExtension.groovy +++ b/jvm/src/main/groovy/org/asciidoctor/gradle/jvm/AsciidoctorJvmExecSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,26 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.asciidoctor.gradle.js.nodejs +package org.asciidoctor.gradle.jvm import groovy.transform.CompileStatic -import org.gradle.api.Project -import org.gradle.api.Task -import org.ysb33r.gradle.nodejs.NodeJSExtension +import org.ysb33r.grolifant.api.core.ProjectOperations +import org.ysb33r.grolifant.api.core.runnable.AbstractJvmExecSpec -/** An extension to configure Node.js. +/** + * JVM execution spec for runnign AsciidoctorJ. * - * @since 3.0 + * @author Schalk W. Cronjé + * + * @since 4.0 */ @CompileStatic -class AsciidoctorJSNodeExtension extends NodeJSExtension { - public final static String NAME = 'asciidoctorNodejs' - - AsciidoctorJSNodeExtension(Project project) { - super(project) - } - - AsciidoctorJSNodeExtension(Task task) { - super(task, NAME) +class AsciidoctorJvmExecSpec extends AbstractJvmExecSpec { + AsciidoctorJvmExecSpec(ProjectOperations po) { + super(po) } } diff --git a/jvm/src/main/groovy/org/asciidoctor/gradle/jvm/AsciidoctorTask.groovy b/jvm/src/main/groovy/org/asciidoctor/gradle/jvm/AsciidoctorTask.groovy index 13112a459..66abfb77b 100755 --- a/jvm/src/main/groovy/org/asciidoctor/gradle/jvm/AsciidoctorTask.groovy +++ b/jvm/src/main/groovy/org/asciidoctor/gradle/jvm/AsciidoctorTask.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,11 +21,10 @@ import org.gradle.api.Action import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.util.PatternSet import org.gradle.workers.WorkerExecutor -import org.ysb33r.grolifant.api.v4.FileUtils +import org.ysb33r.grolifant.api.core.ClosureUtils import javax.inject.Inject -import static groovy.lang.Closure.DELEGATE_FIRST import static org.asciidoctor.gradle.base.AsciidoctorUtils.setConvention /** Standard generic task for converting Asciidoctor documents. @@ -57,15 +56,13 @@ import static org.asciidoctor.gradle.base.AsciidoctorUtils.setConvention @CacheableTask class AsciidoctorTask extends AbstractAsciidoctorTask { - /** Configures output options for this task. + /** + * Configures output options for this task. * * @param cfg Closure which will delegate to a {@link org.asciidoctor.gradle.base.OutputOptions} instance. */ - void outputOptions(Closure cfg) { - Closure configurator = (Closure) cfg.clone() - configurator.delegate = this.configuredOutputOptions - configurator.resolveStrategy = DELEGATE_FIRST - configurator.call() + void outputOptions(@DelegatesTo(OutputOptions) Closure cfg) { + ClosureUtils.configureItem(outputOptions, cfg) } /** Configures output options for this task. @@ -74,33 +71,34 @@ class AsciidoctorTask extends AbstractAsciidoctorTask { * to configure. */ void outputOptions(Action cfg) { - cfg.execute(this.configuredOutputOptions) + cfg.execute(outputOptions) } @Inject AsciidoctorTask(WorkerExecutor we) { super(we) final String taskPrefix = 'asciidoctor' + executionMode = JAVA_EXEC // TODO: Remove this as it is just for testing String folderName if (name.startsWith(taskPrefix)) { folderName = name.replaceFirst(taskPrefix, 'asciidoc') } else { folderName = "asciidoc${name.capitalize()}" } - final String safeFolderName = FileUtils.toSafeFileName(folderName) + final String safeFolderName = projectOperations.fsOperations.toSafeFileName(folderName) setConvention(project, sourceDirProperty, project.layout.projectDirectory.dir("src/docs/${folderName}")) setConvention(outputDirProperty, project.layout.buildDirectory.dir("docs/${safeFolderName}")) } @Override - void processAsciidocSources() { + void exec() { if (!baseDirConfigured) { if (attributes.keySet() in ['docinfo', 'docinfo1', 'docinfo2']) { logger.warn('You are using docinfo attributes, but a base directory strategy has not been configured.' + 'It is recommended that you set baseDirFollowsSourceDir() in your task.') } } - super.processAsciidocSources() + super.exec() } /** The default pattern set for secondary sources baced upon the configured backends. @@ -111,15 +109,13 @@ class AsciidoctorTask extends AbstractAsciidoctorTask { * @return Default pattern set. */ @Override - protected PatternSet getDefaultSecondarySourceDocumentPattern() { + PatternSet getDefaultSecondarySourceDocumentPattern() { PatternSet ps = super.defaultSecondarySourceDocumentPattern - Set backends = this.configuredOutputOptions.backends - - if (backends.find { it.startsWith('html') }) { + if (backends().find { it.startsWith('html') }) { ps.include '*docinfo*.html' } - if (backends.find { it.startsWith('docbook') }) { + if (backends().find { it.startsWith('docbook') }) { ps.include '*docinfo*.xml' } diff --git a/jvm/src/main/groovy/org/asciidoctor/gradle/remote/AsciidoctorJExecuter.groovy b/jvm/src/main/groovy/org/asciidoctor/gradle/remote/AsciidoctorJExecuter.groovy deleted file mode 100644 index 8e036f30d..000000000 --- a/jvm/src/main/groovy/org/asciidoctor/gradle/remote/AsciidoctorJExecuter.groovy +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright 2013-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://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.asciidoctor.gradle.remote - -import groovy.transform.CompileDynamic -import groovy.transform.CompileStatic -import groovy.util.logging.Log4j -import org.apache.log4j.Level -import org.apache.log4j.LogManager -import org.asciidoctor.Asciidoctor -import org.asciidoctor.gradle.internal.ExecutorConfiguration -import org.asciidoctor.gradle.internal.ExecutorConfigurationContainer -import org.asciidoctor.gradle.internal.ExecutorLogLevel -import org.asciidoctor.groovydsl.AsciidoctorExtensions -import org.asciidoctor.log.LogHandler - -import javax.inject.Inject - -import static org.asciidoctor.jruby.AsciidoctorJRuby.Factory.create - -/** Actual executor used for running an Asciidoctorj instance. - * - * @since 2.0.0* @author Schalk W. Cronje - */ -@CompileStatic -@Log4j -class AsciidoctorJExecuter extends ExecutorBase implements Runnable { - - @Inject - AsciidoctorJExecuter( - final ExecutorConfigurationContainer execConfig - ) { - super(execConfig) - } - - @Override - void run() { - Thread.currentThread().contextClassLoader = asciidoctorClassLoader - logLevel = findHighestLogLevel(runConfigurations*.executorLogLevel) - failureLevel = findHighestFailureLevel(runConfigurations*.failureLevel.toList()) - logClasspath(Thread.currentThread().contextClassLoader) - if (runConfigurations.size() == 1) { - runSingle() - } else { - runMultiple() - } - } - - /** Forwards the message to Log4J. - * - * @param logLevel The level of the message - * @param msg Message to be logged - */ - @Override - protected void logMessage(ExecutorLogLevel level, String msg) { - switch (level) { - case ExecutorLogLevel.DEBUG: - log.debug(msg) - break - case ExecutorLogLevel.INFO: - log.info(msg) - break - case ExecutorLogLevel.WARN: - log.warn(msg) - break - case ExecutorLogLevel.ERROR: - log.error(msg) - break - case ExecutorLogLevel.QUIET: - log.fatal(msg) - break - } - } - - @SuppressWarnings('CatchThrowable') - private void runSingle() { - ExecutorConfiguration runConfiguration = runConfigurations[0] - - Asciidoctor asciidoctor = create( - runConfiguration.gemPath.empty ? null : runConfiguration.gemPath - ) - - runConfiguration.with { - asciidoctor.requireLibraries(requires) - if (asciidoctorExtensions?.size()) { - registerExtensions(asciidoctor, asciidoctorExtensions) - } - LogHandler lh = getLogHandler(executorLogLevel) - asciidoctor.registerLogHandler(lh) - resetMessagePatternsTo(fatalMessagePatterns) - } - - runConfiguration.outputDir.mkdirs() - - runConfiguration.sourceTree.each { File file -> - try { - if (runConfiguration.logDocuments) { - log.info("Converting ${file}") - } - asciidoctor.convertFile(file, normalisedOptionsFor(file, runConfiguration)) - } catch (Throwable exception) { - throw new AsciidoctorRemoteExecutionException( - "ERROR: Running Asciidoctor whilst attempting to process ${file} " + - "using backend ${runConfiguration.backendName}", - exception - ) - } - } - - failOnFailureLevelReachedOrExceeded() - failOnWarnings() - } - - @SuppressWarnings(['CatchThrowable']) - private void runMultiple() { - String combinedGemPath = runConfigurations*.gemPath.join(File.pathSeparator) - - Asciidoctor asciidoctor = (combinedGemPath.empty || combinedGemPath == File.pathSeparator) ? - create() : - create(combinedGemPath) - - runConfigurations.each { runConfiguration -> - asciidoctor.requireLibraries(runConfiguration.requires) - if (runConfiguration.asciidoctorExtensions?.size()) { - registerExtensions(asciidoctor, runConfiguration.asciidoctorExtensions) - } - } - - runConfigurations.each { runConfiguration -> - logLevel = runConfiguration.executorLogLevel - resetMessagePatternsTo(runConfiguration.fatalMessagePatterns) - runConfiguration.outputDir.mkdirs() - - runConfiguration.sourceTree.each { File file -> - try { - if (runConfiguration.logDocuments) { - log.info("Converting ${file}") - } - asciidoctor.convertFile(file, normalisedOptionsFor(file, runConfiguration)) - } catch (Throwable exception) { - throw new AsciidoctorRemoteExecutionException('Error running Asciidoctor whilst attempting to ' + - "process ${file} using backend ${runConfiguration.backendName}", - exception - ) - } - } - - failOnFailureLevelReachedOrExceeded() - failOnWarnings() - } - } - - @CompileDynamic - private void registerExtensions(Object asciidoctor, List exts) { - AsciidoctorExtensions extensionRegistry = new AsciidoctorExtensions() - - for (Object ext in rehydrateExtensions(extensionRegistry, exts)) { - extensionRegistry.addExtension(ext) - } - extensionRegistry.registerExtensionsWith((Asciidoctor) asciidoctor) - } - - private ClassLoader getAsciidoctorClassLoader() { - this.class.classLoader - } - - private ExecutorLogLevel findHighestLogLevel(Iterable levels) { - int lvl = levels*.level.min() as int - ExecutorLogLevel.values().find { - it.level == lvl - } - } - - private void setLogLevel(ExecutorLogLevel lvl) { - switch (lvl) { - case ExecutorLogLevel.DEBUG: - LogManager.getLogger(log.name).level = Level.DEBUG - break - case ExecutorLogLevel.INFO: - LogManager.getLogger(log.name).level = Level.INFO - break - case ExecutorLogLevel.WARN: - LogManager.getLogger(log.name).level = Level.WARN - break - case ExecutorLogLevel.ERROR: - LogManager.getLogger(log.name).level = Level.ERROR - break - case ExecutorLogLevel.QUIET: - LogManager.getLogger(log.name).level = Level.FATAL - break - } - } - - @SuppressWarnings('Instanceof') - private void logClasspath(ClassLoader cl) { - if (cl instanceof URLClassLoader) { - Set urls = ((URLClassLoader) cl).URLs as Set - - if (cl.parent instanceof URLClassLoader) { - urls.addAll(((URLClassLoader) cl.parent).URLs as Set) - } - - log.info "AsciidoctorJ worker is using effective classpath of: ${urls.join(' ')}" - } - - // TODO: Find a way of logging classpath in JDK9 & 10 - } -} \ No newline at end of file diff --git a/jvm/src/main/groovy/org/asciidoctor/gradle/remote/AsciidoctorJLogProcessor.groovy b/jvm/src/main/groovy/org/asciidoctor/gradle/remote/AsciidoctorJLogProcessor.groovy new file mode 100644 index 000000000..607633caf --- /dev/null +++ b/jvm/src/main/groovy/org/asciidoctor/gradle/remote/AsciidoctorJLogProcessor.groovy @@ -0,0 +1,167 @@ +/* + * Copyright 2013-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://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.asciidoctor.gradle.remote + +import groovy.transform.CompileStatic +import groovy.util.logging.Slf4j +import org.asciidoctor.ast.Cursor +import org.asciidoctor.gradle.internal.ExecutorLogLevel +import org.asciidoctor.log.LogHandler +import org.asciidoctor.log.LogRecord +import org.asciidoctor.log.Severity + +import java.util.regex.Pattern + +/** + * How to deal with failures coming out of AsciidoctorJ. + * + * @author Schalk W. Cronjé + * + * @since 4.0 + */ +@CompileStatic +@Slf4j +class AsciidoctorJLogProcessor implements Serializable { + + private int maxSeverityLevel + private final int failureLevel + private final List warningMessages = [] + private final List messagePatterns = [] + + AsciidoctorJLogProcessor( + int maxSeverityLevel, + int failureLevel + ) { + this.maxSeverityLevel = maxSeverityLevel + this.failureLevel = failureLevel + } + + /** + * If any warning message was set, fail with an exception. + */ + void failOnWarnings() { + if (!warningMessages.empty) { + final String msg = 'ERROR: The following messages from AsciidoctorJ are treated as errors:\n' + + warningMessages.join('\n- ') + throw new AsciidoctorRemoteExecutionException(msg) + } + } + + /** If failure level is reached or exceed, fail with an exception. + * + */ + void failOnFailureLevelReachedOrExceeded() { + if (maxSeverityLevel >= failureLevel) { + Severity maxSeverity = LogSeverityMapper.getSeverityOf(maxSeverityLevel) + Severity failureSeverity = LogSeverityMapper.getSeverityOf(failureLevel) + throw new AsciidoctorRemoteExecutionException('ERROR: Failure level reached or exceeded: ' + + "${maxSeverity} >= $failureSeverity") + } + } + + /** + * Creates a log handler for Asciidoctor + * + * @param required The required level of logging + * @return A log handler instance suitable for registering with AsciidoctorJ. + */ + LogHandler getLogHandler(ExecutorLogLevel required) { + int requiredLevel = required.level + new LogHandler() { + @Override + void log(LogRecord logRecord) { + ExecutorLogLevel logLevel = LogSeverityMapper.translateAsciidoctorLogLevel(logRecord.severity) + if (logLevel.level > maxSeverityLevel) { + maxSeverityLevel = logLevel.level + } + if (logLevel.level >= requiredLevel) { + String msg = logRecord.message + Cursor cursor = logRecord.cursor + if (cursor) { + final String cPath = cursor.path ?: '' + final String cDir = cursor.dir ?: '' + final String cFile = cursor.file ?: '' + final String cLine = cursor.lineNumber >= 0 ? cursor.lineNumber.toString() : '' + msg = "${msg} :: ${cPath} :: ${cDir}/${cFile}:${cLine}" + } + if (logRecord.sourceFileName) { + final String subMsg = logRecord.sourceMethodName ? (':' + logRecord.sourceMethodName) : '' + msg = "${msg} (${logRecord.sourceFileName}${subMsg})" + } + logMessage(logLevel, msg) + } + + addMatchingMessage(logRecord.message) + } + } + } + + /** + * The list of warning messages that was recorded during the conversion. + * + * @return List of recorded warning messages + */ + List getWarningMessages() { + this.warningMessages + } + + /** + * Forwards the message to Slf4j. + * + * @param logLevel The level of the message + * @param msg Message to be logged + */ + void logMessage(ExecutorLogLevel level, String msg) { + switch (level) { + case ExecutorLogLevel.DEBUG: + log.debug(msg) + break + case ExecutorLogLevel.INFO: + log.info(msg) + break + case ExecutorLogLevel.WARN: + log.warn(msg) + break + case ExecutorLogLevel.ERROR: + log.error(msg) + break + case ExecutorLogLevel.QUIET: + log.error(msg) + break + } + } + + /** Patterns for matching log messages as errors + * + * @param patterns List of patterns. Can be empty. + */ + void resetMessagePatternsTo(final List patterns) { + this.messagePatterns.clear() + this.messagePatterns.addAll(patterns) + } + + /** Adds a warning message that fits a pattern. + * + * @param msg + */ + private void addMatchingMessage(final String msg) { + if (!this.messagePatterns.empty) { + if (this.messagePatterns.any { msg =~ it }) { + this.warningMessages.add msg + } + } + } +} diff --git a/jvm/src/main/groovy/org/asciidoctor/gradle/remote/AsciidoctorJSetup.groovy b/jvm/src/main/groovy/org/asciidoctor/gradle/remote/AsciidoctorJSetup.groovy new file mode 100644 index 000000000..e6ec74ce7 --- /dev/null +++ b/jvm/src/main/groovy/org/asciidoctor/gradle/remote/AsciidoctorJSetup.groovy @@ -0,0 +1,122 @@ +/* + * Copyright 2013-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://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.asciidoctor.gradle.remote + +import groovy.transform.CompileStatic +import org.asciidoctor.Options +import org.asciidoctor.gradle.internal.ExecutorConfiguration + +/** + * Sets AsciidoctorJ runtime. + * + * @author Schalk W. Cronjé + * + * @since 4.0 + */ +@CompileStatic +class AsciidoctorJSetup implements Serializable { + public final static String ATTR_PROJECT_DIR = 'gradle-projectdir' + public final static String ATTR_ROOT_DIR = 'gradle-rootdir' + public final static String ATTR_REL_SRC_DIR = 'gradle-relative-srcdir' + + /** + * Returns the path of one File relative to another. + * + * @param target the target directory + * @param base the base directory + * @return target's path relative to the base directory + * @throws IOException if an error occurs while resolving the files' canonical names + */ + String getRelativePath(File target, File base) throws IOException { + base.toPath().relativize(target.toPath()).toFile().toString() + } + + /** + * Normalises Asciidoctor options for a given source file. + * + * Relativizes certain attributes and ensure specific options for backend, sage mode and output + * directory are in place. + * + * @param file Source file to be converted + * @param runConfiguration The current executor configuration + * @return Asciidoctor options + */ + @SuppressWarnings('DuplicateStringLiteral ') + Map normalisedOptionsFor(final File file, ExecutorConfiguration runConfiguration) { + Map mergedOptions = [:] + + runConfiguration.with { + final String srcRelative = getRelativePath(file.parentFile, sourceDir) + + mergedOptions.putAll(options) + mergedOptions.putAll([ + (Options.BACKEND) : backendName, + (Options.IN_PLACE): false, + (Options.SAFE) : safeModeLevel, + (Options.TO_DIR) : (srcRelative.empty ? outputDir : new File(outputDir, srcRelative)).absolutePath, + (Options.MKDIRS) : true + ]) + + mergedOptions[Options.BASEDIR] = (baseDir ?: file.parentFile).absolutePath + + if (mergedOptions.containsKey(Options.TO_FILE)) { + Object toFileValue = mergedOptions[Options.TO_FILE] + Object toDirValue = mergedOptions.remove(Options.TO_DIR) + File toFile = toFileValue instanceof File ? (File) toFileValue : new File(toFileValue.toString()) + File toDir = toDirValue instanceof File ? (File) toDirValue : new File(toDirValue.toString()) + mergedOptions[Options.TO_FILE] = new File(toDir, toFile.name).absolutePath + } + + Map newAttrs = [:] + newAttrs.putAll(attributes) + newAttrs[ATTR_PROJECT_DIR] = projectDir.absolutePath + newAttrs[ATTR_ROOT_DIR] = rootDir.absolutePath + newAttrs[ATTR_REL_SRC_DIR] = getRelativePath(sourceDir, file.parentFile) ?: '.' + + if (legacyAttributes) { + newAttrs['projectdir'] = newAttrs[ATTR_PROJECT_DIR] + newAttrs['rootdir'] = newAttrs[ATTR_ROOT_DIR] + } + + mergedOptions[Options.ATTRIBUTES] = newAttrs + } + + mergedOptions + } + + /** + * Rehydrates docExtensions that were serialised. + * + * @param registry Asciidoctor GroovyDSL registry instance. + * @param exts List of docExtensions to rehydrate. + * @return List of rehydrated extensions + */ + List rehydrateExtensions(final Object registry, final List exts) { + final List availableExtensions = [] + for (Object ext in exts) { + switch (ext) { + case Closure: + Closure rehydrated = ((Closure) ext).rehydrate(registry, null, null) + rehydrated.resolveStrategy = Closure.DELEGATE_ONLY + availableExtensions.add((Object) rehydrated) + break + default: + availableExtensions.add(ext) + } + } + availableExtensions + } +} diff --git a/jvm/src/main/groovy/org/asciidoctor/gradle/remote/AsciidoctorJavaExec.groovy b/jvm/src/main/groovy/org/asciidoctor/gradle/remote/AsciidoctorJavaExec.groovy index 833cd24bd..97688b4aa 100644 --- a/jvm/src/main/groovy/org/asciidoctor/gradle/remote/AsciidoctorJavaExec.groovy +++ b/jvm/src/main/groovy/org/asciidoctor/gradle/remote/AsciidoctorJavaExec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -95,18 +95,6 @@ class AsciidoctorJavaExec extends ExecutorBase { } } - private Asciidoctor getAsciidoctorInstance() { - String combinedGemPath = runConfigurations*.gemPath.findAll { it }.join(File.pathSeparator) - boolean noGemPath = combinedGemPath.empty || combinedGemPath == File.pathSeparator - noGemPath ? create() : create(combinedGemPath) - } - - private void addRequires(Asciidoctor asciidoctor) { - runConfigurations.each { runConfiguration -> - asciidoctor.requireLibraries(runConfiguration.requires) - } - } - /** Writes the message to stdout. * * @param logLevel The level of the message (ignored). @@ -127,4 +115,14 @@ class AsciidoctorJavaExec extends ExecutorBase { } extensionRegistry.registerExtensionsWith((Asciidoctor) asciidoctor) } + + private void addRequires(Asciidoctor asciidoctor) { + runConfigurations.each { runConfiguration -> + asciidoctor.requireLibraries(runConfiguration.requires) + } + } + + private Asciidoctor getAsciidoctorInstance() { + create() + } } diff --git a/jvm/src/main/groovy/org/asciidoctor/gradle/remote/AsciidoctorRemoteExecutionException.groovy b/jvm/src/main/groovy/org/asciidoctor/gradle/remote/AsciidoctorRemoteExecutionException.groovy index f09f739b3..cc4b66772 100644 --- a/jvm/src/main/groovy/org/asciidoctor/gradle/remote/AsciidoctorRemoteExecutionException.groovy +++ b/jvm/src/main/groovy/org/asciidoctor/gradle/remote/AsciidoctorRemoteExecutionException.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/jvm/src/main/groovy/org/asciidoctor/gradle/remote/AsciidoctorWorkerExecutor.groovy b/jvm/src/main/groovy/org/asciidoctor/gradle/remote/AsciidoctorWorkerExecutor.groovy new file mode 100644 index 000000000..27fc226b5 --- /dev/null +++ b/jvm/src/main/groovy/org/asciidoctor/gradle/remote/AsciidoctorWorkerExecutor.groovy @@ -0,0 +1,116 @@ +/* + * Copyright 2013-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://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.asciidoctor.gradle.remote + +import groovy.transform.CompileDynamic +import groovy.transform.CompileStatic +import groovy.util.logging.Log4j +import org.asciidoctor.Asciidoctor +import org.asciidoctor.gradle.internal.AsciidoctorWorkerParameters +import org.asciidoctor.gradle.internal.ExecutorConfiguration +import org.asciidoctor.groovydsl.AsciidoctorExtensions +import org.asciidoctor.log.LogHandler +import org.ysb33r.grolifant.api.remote.worker.WorkerAppExecutor + +import static org.asciidoctor.jruby.AsciidoctorJRuby.Factory.create + +/** + * Runs Asciidoctor inside a worker. + * + * @author Schalk W. Cronjé + * + * @since 4.0 + */ +@CompileStatic +@Log4j +class AsciidoctorWorkerExecutor implements WorkerAppExecutor, Serializable { + + @Delegate + private final AsciidoctorJSetup setup + + @Delegate + private final AsciidoctorJLogProcessor logProcessor + + AsciidoctorWorkerExecutor() { + setup = new AsciidoctorJSetup() + // TODO: Try to set these values up via startup + logProcessor = new AsciidoctorJLogProcessor( + 0, // DEBUG + 4 // FATAL + ) + } + + @Override + void executeWith(AsciidoctorWorkerParameters params) { + // TODO: Current implementation ignores the params.runParallelInWorker setting. + params.asciidoctorConfigurations.values().each { exeConfigs -> + exeConfigs.each { exeConfig -> + runSingle(exeConfig) + } + } + } + + @SuppressWarnings('CatchThrowable') + private void runSingle(ExecutorConfiguration runConfiguration) { + Asciidoctor asciidoctor = create() + + runConfiguration.with { + asciidoctor.requireLibraries(runConfiguration.requires) + if (asciidoctorExtensions?.size()) { + registerExtensions(asciidoctor, asciidoctorExtensions) + } + LogHandler lh = getLogHandler(executorLogLevel) + asciidoctor.registerLogHandler(lh) + resetMessagePatternsTo(fatalMessagePatterns) + } + + runConfiguration.outputDir.mkdirs() + + runConfiguration.sourceTree.each { File file -> + try { + if (runConfiguration.logDocuments) { + log.info("Converting ${file}") + } + asciidoctor.convertFile(file, normalisedOptionsFor(file, runConfiguration)) + } catch (Throwable exception) { + throw new AsciidoctorRemoteExecutionException( + "ERROR: Running Asciidoctor whilst attempting to process ${file} " + + "using backend ${runConfiguration.backendName}", + exception + ) + } + } + + failOnFailureLevelReachedOrExceeded() + failOnWarnings() + } + + @CompileDynamic + private void registerExtensions(Object asciidoctor, List exts) { + AsciidoctorExtensions extensionRegistry = new AsciidoctorExtensions() + + for (Object ext in rehydrateExtensions(extensionRegistry, exts)) { + extensionRegistry.addExtension(ext) + } + extensionRegistry.registerExtensionsWith((Asciidoctor) asciidoctor) + } + +// private void addRequires(Asciidoctor asciidoctor) { +// runConfigurations.each { runConfiguration -> +// asciidoctor.requireLibraries(runConfiguration.requires) +// } +// } +} diff --git a/jvm/src/main/groovy/org/asciidoctor/gradle/remote/ExecutorBase.groovy b/jvm/src/main/groovy/org/asciidoctor/gradle/remote/ExecutorBase.groovy index f483c866c..90ea17dfe 100644 --- a/jvm/src/main/groovy/org/asciidoctor/gradle/remote/ExecutorBase.groovy +++ b/jvm/src/main/groovy/org/asciidoctor/gradle/remote/ExecutorBase.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,6 @@ package org.asciidoctor.gradle.remote import groovy.transform.CompileStatic -import org.asciidoctor.Options import org.asciidoctor.ast.Cursor import org.asciidoctor.gradle.internal.ExecutorConfiguration import org.asciidoctor.gradle.internal.ExecutorConfigurationContainer @@ -35,13 +34,9 @@ import java.util.regex.Pattern */ @CompileStatic abstract class ExecutorBase { - private final static String ATTR_PROJECT_DIR = 'gradle-projectdir' - private final static String ATTR_ROOT_DIR = 'gradle-rootdir' - private final static String ATTR_REL_SRC_DIR = 'gradle-relative-srcdir' - private final List warningMessages = [] private final List messagePatterns = [] - + private final AsciidoctorJSetup setup = new AsciidoctorJSetup() protected int maxSeverityLevel = 0 // DEBUG protected int failureLevel = 4 // FATAL @@ -70,46 +65,7 @@ abstract class ExecutorBase { @SuppressWarnings('DuplicateStringLiteral ') protected Map normalisedOptionsFor(final File file, ExecutorConfiguration runConfiguration) { - - Map mergedOptions = [:] - - runConfiguration.with { - final String srcRelative = getRelativePath(file.parentFile, sourceDir) - - mergedOptions.putAll(options) - mergedOptions.putAll([ - (Options.BACKEND) : backendName, - (Options.IN_PLACE): false, - (Options.SAFE) : safeModeLevel, - (Options.TO_DIR) : (srcRelative.empty ? outputDir : new File(outputDir, srcRelative)).absolutePath, - (Options.MKDIRS) : true - ]) - - mergedOptions[Options.BASEDIR] = (baseDir ?: file.parentFile).absolutePath - - if (mergedOptions.containsKey(Options.TO_FILE)) { - Object toFileValue = mergedOptions[Options.TO_FILE] - Object toDirValue = mergedOptions.remove(Options.TO_DIR) - File toFile = toFileValue instanceof File ? (File) toFileValue : new File(toFileValue.toString()) - File toDir = toDirValue instanceof File ? (File) toDirValue : new File(toDirValue.toString()) - mergedOptions[Options.TO_FILE] = new File(toDir, toFile.name).absolutePath - } - - Map newAttrs = [:] - newAttrs.putAll(attributes) - newAttrs[ATTR_PROJECT_DIR] = projectDir.absolutePath - newAttrs[ATTR_ROOT_DIR] = rootDir.absolutePath - newAttrs[ATTR_REL_SRC_DIR] = getRelativePath(sourceDir, file.parentFile) ?: '.' - - if (legacyAttributes) { - newAttrs['projectdir'] = newAttrs[ATTR_PROJECT_DIR] - newAttrs['rootdir'] = newAttrs[ATTR_ROOT_DIR] - } - - mergedOptions[Options.ATTRIBUTES] = newAttrs - } - - mergedOptions + setup.normalisedOptionsFor(file, runConfiguration) } /** @@ -121,7 +77,7 @@ abstract class ExecutorBase { * @throws IOException if an error occurs while resolving the files' canonical names */ protected String getRelativePath(File target, File base) throws IOException { - base.toPath().relativize(target.toPath()).toFile().toString() + setup.getRelativePath(target, base) } /** Rehydrates docExtensions that were serialised. @@ -131,19 +87,7 @@ abstract class ExecutorBase { * @return */ protected List rehydrateExtensions(final Object registry, final List exts) { - final List availableExtensions = [] - for (Object ext in exts) { - switch (ext) { - case Closure: - Closure rehydrated = ((Closure) ext).rehydrate(registry, null, null) - rehydrated.resolveStrategy = Closure.DELEGATE_ONLY - availableExtensions.add((Object) rehydrated) - break - default: - availableExtensions.add(ext) - } - } - availableExtensions + setup.rehydrateExtensions(registry, exts) } /** Creates a log handler for Asciidoctor diff --git a/jvm/src/main/groovy/org/asciidoctor/gradle/remote/LogSeverityMapper.groovy b/jvm/src/main/groovy/org/asciidoctor/gradle/remote/LogSeverityMapper.groovy index 0db51d2ed..485e9d857 100644 --- a/jvm/src/main/groovy/org/asciidoctor/gradle/remote/LogSeverityMapper.groovy +++ b/jvm/src/main/groovy/org/asciidoctor/gradle/remote/LogSeverityMapper.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,11 @@ import org.asciidoctor.log.Severity @java.lang.SuppressWarnings('NoWildcardImports') import static org.asciidoctor.log.Severity.* -/** Maps from Asciidoctor severities to {@link ExecutorLogLevel} levels. +/** + * Maps from Asciidoctor severities to {@link ExecutorLogLevel} levels. + * + * @author Schalk W. Cronjé + * @author Guillame Grossetie * * @since 2.0 */ diff --git a/jvm/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.jvm.base.properties b/jvm/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.jvm.base.properties deleted file mode 100644 index 95d268732..000000000 --- a/jvm/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.jvm.base.properties +++ /dev/null @@ -1,17 +0,0 @@ -# -# Copyright 2013-2023 the original author or authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://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. -# - -implementation-class=org.asciidoctor.gradle.jvm.AsciidoctorJBasePlugin \ No newline at end of file diff --git a/jvm/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.jvm.convert.properties b/jvm/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.jvm.convert.properties deleted file mode 100644 index 29a30e610..000000000 --- a/jvm/src/main/resources/META-INF/gradle-plugins/org.asciidoctor.jvm.convert.properties +++ /dev/null @@ -1,17 +0,0 @@ -# -# Copyright 2013-2023 the original author or authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://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. -# - -implementation-class=org.asciidoctor.gradle.jvm.AsciidoctorJPlugin \ No newline at end of file diff --git a/jvm/src/remoteTest/groovy/org/asciidoctor/gradle/remote/AsciidoctorJExecutorSpec.groovy b/jvm/src/remoteTest/groovy/org/asciidoctor/gradle/remote/AsciidoctorJExecutorSpec.groovy deleted file mode 100644 index a2b150e9e..000000000 --- a/jvm/src/remoteTest/groovy/org/asciidoctor/gradle/remote/AsciidoctorJExecutorSpec.groovy +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2013-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://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.asciidoctor.gradle.remote - -import org.asciidoctor.gradle.internal.ExecutorConfigurationContainer -import org.asciidoctor.gradle.remote.internal.RemoteSpecification - -class AsciidoctorJExecutorSpec extends RemoteSpecification { - - void 'Can execute a worked-based conversion from a single backend'() { - given: - Map asciidoc = getProject(testProjectDir.root) - ExecutorConfigurationContainer ecc = getContainerSingleEntry(asciidoc.src, asciidoc.outputDir) - AsciidoctorJExecuter aje = new AsciidoctorJExecuter(ecc) - - when: - aje.run() - - then: - new File(asciidoc.outputDir, OUTPUT_HTML).exists() - } - - void 'Can execute a worked-based conversion from multiple backends'() { - given: - Map asciidoc = getProject(testProjectDir.root) - ExecutorConfigurationContainer ecc = getContainerMultipleEntries( - asciidoc.src, - asciidoc.outputDir, - asciidoc.gemPath - ) - AsciidoctorJExecuter aje = new AsciidoctorJExecuter(ecc) - - when: - aje.run() - - then: - new File(asciidoc.outputDir, OUTPUT_HTML).exists() - new File(asciidoc.outputDir, OUTPUT_DOCBOOK).exists() - } -} \ No newline at end of file diff --git a/jvm/src/remoteTest/groovy/org/asciidoctor/gradle/remote/AsciidoctorJavaExecSpec.groovy b/jvm/src/remoteTest/groovy/org/asciidoctor/gradle/remote/AsciidoctorJavaExecSpec.groovy index 0635843f1..cf2233c44 100644 --- a/jvm/src/remoteTest/groovy/org/asciidoctor/gradle/remote/AsciidoctorJavaExecSpec.groovy +++ b/jvm/src/remoteTest/groovy/org/asciidoctor/gradle/remote/AsciidoctorJavaExecSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ class AsciidoctorJavaExecSpec extends RemoteSpecification { void 'Can execute a conversion from execution specification'() { given: - Map asciidoc = getProject(testProjectDir.root) + Map asciidoc = getProject(projectDir) AsciidoctorJavaExec aje = new AsciidoctorJavaExec(getContainerSingleEntry(asciidoc.src, asciidoc.outputDir)) when: @@ -35,12 +35,12 @@ class AsciidoctorJavaExecSpec extends RemoteSpecification { void 'Can execute a conversion using serialised execution specification'() { given: - File executionData = new File(testProjectDir.root, 'execdata') - Map asciidoc = getProject(testProjectDir.root) + File executionData = new File(projectDir, 'execdata') + Map asciidoc = getProject(projectDir) ExecutorConfigurationContainer ecc = getContainerMultipleEntries( asciidoc.src, asciidoc.outputDir, - asciidoc.gemPath + null // asciidoc.gemPath ) ecc.toFile(executionData, ecc.configurations) @@ -63,7 +63,7 @@ class AsciidoctorJavaExecSpec extends RemoteSpecification { void 'Should throw an exception when failure level is reached or exceeded'() { given: - Map asciidoc = getProject(testProjectDir.root) + Map asciidoc = getProject(projectDir) AsciidoctorJavaExec aje = new AsciidoctorJavaExec(new ExecutorConfigurationContainer( getExecutorConfiguration(HTML, asciidoc.src, new File(asciidoc.outputDir, OUTPUT_HTML), null, 1) )) diff --git a/jvm/src/remoteTest/groovy/org/asciidoctor/gradle/remote/AsciidoctorRemoteExecutionExceptionSpec.groovy b/jvm/src/remoteTest/groovy/org/asciidoctor/gradle/remote/AsciidoctorRemoteExecutionExceptionSpec.groovy index 8b0f9c9ce..a96c0d3b2 100644 --- a/jvm/src/remoteTest/groovy/org/asciidoctor/gradle/remote/AsciidoctorRemoteExecutionExceptionSpec.groovy +++ b/jvm/src/remoteTest/groovy/org/asciidoctor/gradle/remote/AsciidoctorRemoteExecutionExceptionSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/jvm/src/remoteTest/groovy/org/asciidoctor/gradle/remote/LogSeverityMapperSpec.groovy b/jvm/src/remoteTest/groovy/org/asciidoctor/gradle/remote/LogSeverityMapperSpec.groovy index 8410630a4..c239f4358 100644 --- a/jvm/src/remoteTest/groovy/org/asciidoctor/gradle/remote/LogSeverityMapperSpec.groovy +++ b/jvm/src/remoteTest/groovy/org/asciidoctor/gradle/remote/LogSeverityMapperSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/jvm/src/remoteTest/groovy/org/asciidoctor/gradle/remote/internal/RemoteSpecification.groovy b/jvm/src/remoteTest/groovy/org/asciidoctor/gradle/remote/internal/RemoteSpecification.groovy index eeda3a982..f5faf53d1 100644 --- a/jvm/src/remoteTest/groovy/org/asciidoctor/gradle/remote/internal/RemoteSpecification.groovy +++ b/jvm/src/remoteTest/groovy/org/asciidoctor/gradle/remote/internal/RemoteSpecification.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,13 +17,12 @@ package org.asciidoctor.gradle.remote.internal import org.asciidoctor.gradle.internal.ExecutorConfiguration import org.asciidoctor.gradle.internal.ExecutorConfigurationContainer -import org.junit.Rule -import org.junit.rules.TemporaryFolder +import org.asciidoctor.gradle.internal.ExecutorLogLevel +import org.asciidoctor.gradle.testfixtures.FunctionalTestFixture import spock.lang.Specification +import spock.lang.TempDir -import static org.asciidoctor.gradle.internal.ExecutorLogLevel.DEBUG - -class RemoteSpecification extends Specification { +class RemoteSpecification extends Specification implements FunctionalTestFixture { static final String INPUT_DOC = 'index.adoc' static final String INPUT_DOC2 = 'index2.adoc' @@ -35,8 +34,8 @@ class RemoteSpecification extends Specification { static final String INVALID_1 = 'abc' static final String INVALID_2 = 'def' - @Rule - TemporaryFolder testProjectDir + @TempDir + File testProjectDir Map getProject(File base) { File src = new File(base, 'src') @@ -70,44 +69,44 @@ in a subdirectory ExecutorConfigurationContainer getContainerSingleEntry(File srcFile, File outputDir) { new ExecutorConfigurationContainer( - getExecutorConfiguration(HTML, srcFile, new File(outputDir, OUTPUT_HTML), null) + getExecutorConfiguration(HTML, srcFile, new File(outputDir, OUTPUT_HTML), null) ) } ExecutorConfigurationContainer getContainerMultipleEntries(File srcFile, File outputDir, File gemDir) { new ExecutorConfigurationContainer([ - getExecutorConfiguration(HTML, srcFile, new File(outputDir, OUTPUT_HTML), null), - getExecutorConfiguration(DOCBOOK, srcFile, new File(outputDir, OUTPUT_DOCBOOK), gemDir) + getExecutorConfiguration(HTML, srcFile, new File(outputDir, OUTPUT_HTML), null), + getExecutorConfiguration(DOCBOOK, srcFile, new File(outputDir, OUTPUT_DOCBOOK), gemDir) ]) } @SuppressWarnings('Println') ExecutorConfiguration getExecutorConfiguration( - final String backend, File srcFile, File outputFile, File gemDir, int failureLevel = 4 // FATAL + final String backend, File srcFile, File outputFile, File gemDir, int failureLevel = 4 // FATAL ) { boolean altOptions = gemDir != null List requires = [] List exts = altOptions ? [{ println 'fake extension' }.dehydrate()] : [] new ExecutorConfiguration( - options: [:], - attributes: [:], - asciidoctorExtensions: exts, - copyResources: false, - safeModeLevel: 0, - backendName: backend, - fatalMessagePatterns: [], - sourceDir: srcFile.parentFile, - outputDir: outputFile.parentFile, - projectDir: testProjectDir.root, - rootDir: testProjectDir.root, - baseDir: testProjectDir.root, - sourceTree: [srcFile, new File(srcFile.parentFile, "subdir/${INPUT_DOC2}")], - logDocuments: altOptions, - executorLogLevel: DEBUG, - failureLevel: failureLevel, - requires: requires, - gemPath: (altOptions ? gemDir.absolutePath : '') + options: [:], + attributes: [:], + asciidoctorExtensions: exts, + copyResources: false, + safeModeLevel: 0, + backendName: backend, + fatalMessagePatterns: [], + sourceDir: srcFile.parentFile, + outputDir: outputFile.parentFile, + projectDir: projectDir, + rootDir: projectDir, + baseDir: projectDir, + sourceTree: [srcFile, new File(srcFile.parentFile, "subdir/${INPUT_DOC2}")], + logDocuments: altOptions, + executorLogLevel: ExecutorLogLevel.DEBUG, + failureLevel: failureLevel, + requires: requires, +// gemPath: (altOptions ? gemDir.absolutePath : '') ) } } diff --git a/jvm/src/test/groovy/org/asciidoctor/gradle/internal/ExecutorConfigurationSpec.groovy b/jvm/src/test/groovy/org/asciidoctor/gradle/internal/ExecutorConfigurationSpec.groovy index 8b73939fc..bbe343191 100644 --- a/jvm/src/test/groovy/org/asciidoctor/gradle/internal/ExecutorConfigurationSpec.groovy +++ b/jvm/src/test/groovy/org/asciidoctor/gradle/internal/ExecutorConfigurationSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,7 +32,6 @@ class ExecutorConfigurationSpec extends Specification { sourceTree: [fake] as Set, fatalMessagePatterns: [~/./], backendName: 'backend', - gemPath : 'gem:path', logDocuments: true, copyResources: true, legacyAttributes: true, diff --git a/jvm/src/test/groovy/org/asciidoctor/gradle/internal/JavaExecUtilsSpec.groovy b/jvm/src/test/groovy/org/asciidoctor/gradle/internal/JavaExecUtilsSpec.groovy index 9b1447ed8..6d4521b6d 100644 --- a/jvm/src/test/groovy/org/asciidoctor/gradle/internal/JavaExecUtilsSpec.groovy +++ b/jvm/src/test/groovy/org/asciidoctor/gradle/internal/JavaExecUtilsSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,21 +16,20 @@ package org.asciidoctor.gradle.internal import org.gradle.api.invocation.Gradle -import org.junit.Rule -import org.junit.rules.TemporaryFolder import spock.lang.Specification +import spock.lang.TempDir import static org.asciidoctor.gradle.internal.JavaExecUtils.getInternalGuavaLocation class JavaExecUtilsSpec extends Specification { - @Rule - TemporaryFolder temporaryFolder + @TempDir + File temporaryFolder void 'Throw exception if internal Guava cannot be found'() { setup: def gradle = Stub(Gradle) - gradle.gradleHomeDir >> temporaryFolder.root + gradle.gradleHomeDir >> temporaryFolder when: getInternalGuavaLocation(gradle) @@ -43,10 +42,10 @@ class JavaExecUtilsSpec extends Specification { void 'Throw exception if multiple internal Guava JARs are found'() { setup: def gradle = Stub(Gradle) - gradle.gradleHomeDir >> temporaryFolder.root - new File(temporaryFolder.root, 'lib').mkdirs() - new File(temporaryFolder.root, 'lib/guava-0.0-android.jar').text = '' - new File(temporaryFolder.root, 'lib/guava-0.1-android.jar').text = '' + gradle.gradleHomeDir >> temporaryFolder + new File(temporaryFolder, 'lib').mkdirs() + new File(temporaryFolder, 'lib/guava-0.0-android.jar').text = '' + new File(temporaryFolder, 'lib/guava-0.1-android.jar').text = '' when: getInternalGuavaLocation(gradle) @@ -59,10 +58,10 @@ class JavaExecUtilsSpec extends Specification { void 'detect jre variant of guava'() { setup: def gradle = Stub(Gradle) - gradle.gradleHomeDir >> temporaryFolder.root - new File(temporaryFolder.root, 'lib').mkdirs() - new File(temporaryFolder.root, 'lib/guavasomething.jar') - def guavaJar = new File(temporaryFolder.root, 'lib/guava-30.0-jre.jar') + gradle.gradleHomeDir >> temporaryFolder + new File(temporaryFolder, 'lib').mkdirs() + new File(temporaryFolder, 'lib/guavasomething.jar') + def guavaJar = new File(temporaryFolder, 'lib/guava-30.0-jre.jar') guavaJar.text = '' when: diff --git a/jvm/src/test/groovy/org/asciidoctor/gradle/jvm/AsciidoctorJBasePluginSpec.groovy b/jvm/src/test/groovy/org/asciidoctor/gradle/jvm/AsciidoctorJBasePluginSpec.groovy index d051d478b..add387200 100644 --- a/jvm/src/test/groovy/org/asciidoctor/gradle/jvm/AsciidoctorJBasePluginSpec.groovy +++ b/jvm/src/test/groovy/org/asciidoctor/gradle/jvm/AsciidoctorJBasePluginSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/jvm/src/test/groovy/org/asciidoctor/gradle/jvm/AsciidoctorJExtensionSpec.groovy b/jvm/src/test/groovy/org/asciidoctor/gradle/jvm/AsciidoctorJExtensionSpec.groovy deleted file mode 100644 index 5e520ce5f..000000000 --- a/jvm/src/test/groovy/org/asciidoctor/gradle/jvm/AsciidoctorJExtensionSpec.groovy +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2013-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://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.asciidoctor.gradle.jvm - -import org.gradle.api.Project -import org.gradle.api.artifacts.Configuration -import org.gradle.testfixtures.ProjectBuilder -import spock.lang.Specification - -/** - * @author Schalk W. Cronjé - */ - -class AsciidoctorJExtensionSpec extends Specification { - - Project project = ProjectBuilder.builder().withName('test').build() - AsciidoctorJExtension asciidoctorj - - void setup() { - project.allprojects { - apply plugin: 'org.asciidoctor.jvm.base' - } - - asciidoctorj = project.extensions.getByType(AsciidoctorJExtension) - } - - void 'Add a callback to configuration'() { - setup: - boolean callbackCalled = false - asciidoctorj.onConfiguration { Configuration cfg -> - callbackCalled = true - } - - when: - asciidoctorj.configuration - - then: - callbackCalled - } - - void 'Configure a repository for callback'() { - project.allprojects { - // tag::restrict-repository[] - repositories { - maven { - name = 'asciidoctorj' - url = 'https://some.repo.example' - } - } - - asciidoctorj { - onConfiguration { cfg -> - repositories.getByName('asciidoctorj').mavenContent { descriptor -> - descriptor.onlyForConfigurations(cfg.name) - } - } - } - // end::restrict-repository[] - } - - when: - project.evaluate() - asciidoctorj.configuration - - then: - noExceptionThrown() - } -} \ No newline at end of file diff --git a/jvm/src/test/groovy/org/asciidoctor/gradle/jvm/AsciidoctorTaskSpec.groovy b/jvm/src/test/groovy/org/asciidoctor/gradle/jvm/AsciidoctorTaskSpec.groovy index d509f8411..741733bc4 100755 --- a/jvm/src/test/groovy/org/asciidoctor/gradle/jvm/AsciidoctorTaskSpec.groovy +++ b/jvm/src/test/groovy/org/asciidoctor/gradle/jvm/AsciidoctorTaskSpec.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,19 +15,13 @@ */ package org.asciidoctor.gradle.jvm -import org.asciidoctor.gradle.internal.ExecutorConfiguration -import org.gradle.api.Action import org.gradle.api.GradleException import org.gradle.api.Project import org.gradle.testfixtures.ProjectBuilder -import org.gradle.workers.WorkerExecutor import org.ysb33r.grolifant.api.core.ProjectOperations import org.ysb33r.grolifant.api.core.StringTools -import org.ysb33r.grolifant.api.v4.JavaForkOptions import spock.lang.Specification -import javax.inject.Inject - import static org.asciidoctor.gradle.base.internal.AsciidoctorAttributes.resolveAsSerializable /** @@ -40,7 +34,6 @@ import static org.asciidoctor.gradle.base.internal.AsciidoctorAttributes.resolve * @author Lari Hotari */ class AsciidoctorTaskSpec extends Specification { - private static final String ASCIIDOCTOR = 'asciidoctor' private static final String ASCIIDOC_RESOURCES_DIR = 'asciidoctor-gradle-jvm/src/test/resources/src/asciidoc' private static final String ASCIIDOC_BUILD_DIR = 'build/asciidoc' @@ -73,7 +66,7 @@ class AsciidoctorTaskSpec extends Specification { void 'Base directory is project directory by default'() { when: - AsciidoctorTask task = asciidoctorTask { + final task = createTask { } then: @@ -82,7 +75,7 @@ class AsciidoctorTaskSpec extends Specification { void 'Base directory can be root project directory'() { when: - AsciidoctorTask task = asciidoctorTask { + final task = createTask { baseDirIsRootProjectDir() } @@ -92,7 +85,7 @@ class AsciidoctorTaskSpec extends Specification { void 'Base directory can be project directory'() { when: - AsciidoctorTask task = asciidoctorTask { + final task = createTask { baseDirIsRootProjectDir() baseDirIsProjectDir() } @@ -103,7 +96,7 @@ class AsciidoctorTaskSpec extends Specification { void 'Base directory can be a fixed directory'() { when: - AsciidoctorTask task = asciidoctorTask { + final task = createTask { baseDir 'foo' } @@ -113,7 +106,7 @@ class AsciidoctorTaskSpec extends Specification { void 'Base directory can be source directory'() { when: - AsciidoctorTask task = asciidoctorTask { + final task = createTask { baseDirFollowsSourceDir() } @@ -123,7 +116,7 @@ class AsciidoctorTaskSpec extends Specification { void 'Base directory can be source directory within a temporary working directory'() { when: - AsciidoctorTask task = asciidoctorTask { + final task = createTask { baseDirFollowsSourceDir() useIntermediateWorkDir() } @@ -134,7 +127,7 @@ class AsciidoctorTaskSpec extends Specification { void 'Base directory can be null to follow source file'() { when: - AsciidoctorTask task = asciidoctorTask { + final task = createTask { baseDirFollowsSourceFile() } @@ -144,7 +137,7 @@ class AsciidoctorTaskSpec extends Specification { void 'Base directory can be set to null'() { when: - AsciidoctorTask task = asciidoctorTask { + final task = createTask { baseDir = null } @@ -154,7 +147,7 @@ class AsciidoctorTaskSpec extends Specification { void "Allow setting of options via method"() { when: - AsciidoctorTask task = asciidoctorTask { + final task = createTask { options eruby: 'erb' options eruby: 'erubis' options doctype: 'book', toc: 'right' @@ -170,7 +163,7 @@ class AsciidoctorTaskSpec extends Specification { void "Allow setting of options via assignment"() { when: - AsciidoctorTask task = asciidoctorTask { + final task = createTask { options = [eruby: 'erb', toc: 'right'] options = [eruby: 'erubis', doctype: 'book'] } @@ -185,7 +178,7 @@ class AsciidoctorTaskSpec extends Specification { void "Allow setting of attributes via method (Map variant)"() { when: - AsciidoctorTask task = asciidoctorTask { + final task = createTask { attributes 'source-highlighter': 'foo' attributes 'source-highlighter': 'coderay' attributes idprefix: '$', idseparator: '-' @@ -201,7 +194,7 @@ class AsciidoctorTaskSpec extends Specification { void "Do not allow setting of attributes via legacy key=value list"() { when: - asciidoctorTask { + createTask { attributes(['source-highlighter=foo', 'source-highlighter=coderay', 'idprefix=$', 'idseparator=-']) } @@ -211,7 +204,7 @@ class AsciidoctorTaskSpec extends Specification { void "Do not allow setting of attributes via legacy key-value string"() { when: - asciidoctorTask { + createTask { attributes 'source-highlighter=foo source-highlighter=coderay idprefix=$ idseparator=-' } @@ -221,7 +214,7 @@ class AsciidoctorTaskSpec extends Specification { void "Allow setting of attributes via assignment"() { when: - AsciidoctorTask task = asciidoctorTask { + final task = createTask { attributes = ['source-highlighter': 'foo', idprefix: '$'] attributes = ['source-highlighter': 'coderay', idseparator: '-'] } @@ -236,7 +229,7 @@ class AsciidoctorTaskSpec extends Specification { void "Mixing attributes with options, produces an exception"() { when: - asciidoctorTask { + createTask { options eruby: 'erubis', attributes: ['source-highlighter': 'foo', idprefix: '$'] options doctype: 'book', attributes: [idseparator: '-'] } @@ -248,7 +241,7 @@ class AsciidoctorTaskSpec extends Specification { void "Mixing attributes with options (with assignment), produces an exception"() { when: Map tmpStore = [eruby: 'erubis', attributes: ['source-highlighter': 'foo', idprefix: '$']] - asciidoctorTask { + createTask { options = tmpStore options = [doctype: 'book', attributes: [idseparator: '-']] } @@ -259,7 +252,7 @@ class AsciidoctorTaskSpec extends Specification { void "Mixing string legacy form of attributes with options with assignment, produces an exception"() { when: - asciidoctorTask { + createTask { options = [ doctype : 'book', attributes: 'toc=right source-highlighter=coderay toc-title=Table\\ of\\ Contents' @@ -272,7 +265,7 @@ class AsciidoctorTaskSpec extends Specification { void "Mixing list legacy form of attributes with options with assignment, produces an exception"() { when: - asciidoctorTask { + createTask { options = [doctype: 'book', attributes: [ 'toc=right', 'source-highlighter=coderay', @@ -289,7 +282,7 @@ class AsciidoctorTaskSpec extends Specification { Set testBackends when: - asciidoctorTask { + createTask { outputOptions { backends 'foo', 'bar' backends 'pdf' @@ -314,7 +307,7 @@ class AsciidoctorTaskSpec extends Specification { Set testBackends when: - asciidoctorTask { + createTask { outputOptions { backends = ['pdf'] backends = ['foo', 'bar'] @@ -342,7 +335,7 @@ class AsciidoctorTaskSpec extends Specification { requires 'asciidoctor-pdf' } } - AsciidoctorTask task = asciidoctorTask { + final task = createTask { asciidoctorj { requires 'slim', 'tilt' } @@ -362,7 +355,7 @@ class AsciidoctorTaskSpec extends Specification { requires 'asciidoctor-pdf' } } - AsciidoctorTask task = asciidoctorTask { + final task = createTask { asciidoctorj { requires = ['slim', 'tilt'] } @@ -377,7 +370,7 @@ class AsciidoctorTaskSpec extends Specification { void "Allow setting of sourceDir via method"() { when: - AsciidoctorTask task = asciidoctorTask { + final task = createTask { sourceDir project.projectDir } @@ -389,7 +382,7 @@ class AsciidoctorTaskSpec extends Specification { void "When setting sourceDir via assignment"() { when: - AsciidoctorTask task = asciidoctorTask { + final task = createTask { sourceDir = project.projectDir } @@ -400,7 +393,7 @@ class AsciidoctorTaskSpec extends Specification { void "When setting sourceDir via setSourceDir"() { when: - AsciidoctorTask task = asciidoctorTask { + final task = createTask { sourceDir = project.projectDir } @@ -410,50 +403,9 @@ class AsciidoctorTaskSpec extends Specification { !systemOut.toString().contains('deprecated') } - void "Allow setting of gemPath via method"() { - when: - AsciidoctorTask task = asciidoctorTask { - asciidoctorj { - gemPaths project.projectDir - } - } - - then: - !systemOut.toString().contains('deprecated') - task.asciidoctorj.asGemPath() == project.projectDir.absolutePath - } - - void "When setting gemPath via assignment"() { - when: - AsciidoctorTask task = asciidoctorTask { - asciidoctorj { - gemPaths = [project.projectDir] - } - } - - then: - task.asciidoctorj.asGemPath() == project.projectDir.absolutePath - !systemOut.toString().contains('deprecated') - } - - void "When setting gemPath via setGemPaths"() { - when: - project.allprojects { - asciidoctorj { - gemPaths = [project.projectDir] - } - } - AsciidoctorTask task = asciidoctorTask { - } - - then: - task.asciidoctorj.asGemPath() == project.projectDir.absolutePath - !systemOut.toString().contains('deprecated') - } - void 'When attribute providers are registered on the task, then global ones will not be used.'() { when: - AsciidoctorTask task = asciidoctorTask { + final task = createTask { asciidoctorj { attributeProvider { [:] @@ -465,10 +417,6 @@ class AsciidoctorTaskSpec extends Specification { task.attributeProviders != project.extensions.getByType(AsciidoctorJExtension).attributeProviders } - void 'Set processMode via string'() { - c - } - void 'Asciidoctor task with non-default name has different source directory'() { when: AsciidoctorTask task = project.tasks.create(name: 'kilowatt', type: AsciidoctorTask) @@ -477,50 +425,56 @@ class AsciidoctorTaskSpec extends Specification { task.sourceDir == project.file('src/docs/asciidocKilowatt') } - void 'Configure fork options via closure'() { - when: - AsciidoctorTask task = asciidoctorTask { - forkOptions { - debug = true - } - } - - then: - task.javaForkOptions.debug == true - } - - void 'Configure fork options via an Action'() { - when: - def withOptions = new Action() { - void execute(JavaForkOptions javaForkOptions) { - javaForkOptions.minHeapSize = '123' - } - } - - AsciidoctorTask task = asciidoctorTask { - forkOptions withOptions - } - - then: - task.javaForkOptions.minHeapSize == '123' - } - - AsciidoctorTask asciidoctorTask(Closure cfg) { - project.tasks.create(name: ASCIIDOCTOR, type: AsciidoctorTask).configure cfg - } + private AsciidoctorTask createTask(@DelegatesTo(AsciidoctorTask) Closure configurator) { + final task = project.tasks.create('asciidoctorTask', AsciidoctorTask) + task.configure(configurator) + task + } + +// void 'Configure fork options via closure'() { +// when: +// AsciidoctorTask task = asciidoctorTask { +// forkOptions { +// debug = true +// } +// } +// +// then: +// task.javaForkOptions.debug == true +// } + +// void 'Configure fork options via an Action'() { +// when: +// def withOptions = new Action() { +// void execute(JavaForkOptions javaForkOptions) { +// javaForkOptions.minHeapSize = '123' +// } +// } +// +// AsciidoctorTask task = asciidoctorTask { +// forkOptions withOptions +// } +// +// then: +// task.javaForkOptions.minHeapSize == '123' +// } +// +// AsciidoctorTask asciidoctorTask(Closure cfg) { +// project.tasks.create(name: ASCIIDOCTOR, type: AsciidoctorTask).configure cfg +// } } -class ExecutorConfigurationInspectingAsciidoctorTask extends AsciidoctorTask { - @Inject - ExecutorConfigurationInspectingAsciidoctorTask(WorkerExecutor we) { - super(we) - } - - // method for unit testing what attributes get passed to execution - ExecutorConfiguration getSampleExecutorConfiguration(String backendName = 'html', - File workingSourceDir = new File('.'), - Set sourceFiles = [] as Set, - Optional lang = Optional.empty()) { - super.getExecutorConfigurationFor(backendName, workingSourceDir, sourceFiles, lang) - } -} +//class ExecutorConfigurationInspectingAsciidoctorTask extends AsciidoctorTask { +// @Inject +// ExecutorConfigurationInspectingAsciidoctorTask(WorkerExecutor we) { +// super(we) +// } +// +// // method for unit testing what attributes get passed to execution +// ExecutorConfiguration getSampleExecutorConfiguration(String backendName = 'html', +// File workingSourceDir = new File('.'), +// Set sourceFiles = [] as Set, +// Optional lang = Optional.empty()) { +// super.getExecutorConfigurationFor(backendName, workingSourceDir, sourceFiles, lang) +// } +//} diff --git a/jvm/src/test/resources/src/asciidoc/docinfo-footer.xml b/jvm/src/test/resources/src/asciidoc/docinfo-footer.xml index 12def4f89..1033dcfa1 100644 --- a/jvm/src/test/resources/src/asciidoc/docinfo-footer.xml +++ b/jvm/src/test/resources/src/asciidoc/docinfo-footer.xml @@ -1,6 +1,6 @@