From f4e937d20a48355409877b881643d00ea1224365 Mon Sep 17 00:00:00 2001 From: v1nc3n4 <5869062+v1nc3n4@users.noreply.github.com> Date: Fri, 7 Jun 2024 21:20:25 +0200 Subject: [PATCH] fix: fixes serialization issues and compliance with Gradle configuration cache Fixes #217 --- .../build.gradle.kts | 1 + plugin/build.gradle.kts | 18 +- plugin/buildSrc/build.gradle.kts | 5 +- plugin/lombok.config | 1 + plugin/settings.gradle.kts | 10 +- ....java => AssembleFrontedTaskFuncTest.java} | 60 ++- .../gradle/CheckFrontendTaskFuncTest.java | 58 ++- .../gradle/CleanFrontendTaskFuncTest.java | 51 +-- .../gradle/InstallFrontendTaskFuncTest.java | 42 +- .../InstallPackageManagerTaskFuncTest.java | 60 ++- ....java => PublishFrontendTaskFuncTest.java} | 67 ++-- .../ResolvePackageManagerTaskFuncTest.java | 97 +++-- .../test/GradleHelper.java | 2 +- .../node-dist-without-corepack/bin/node | 4 - .../node-dist-without-corepack/bin/npm | 2 - .../node-dist-without-corepack/bin/npx | 2 - .../node-dist-without-corepack/bin/pnpm | 2 - .../node-dist-without-corepack/bin/pnpx | 2 - .../node-dist-without-corepack/bin/yarn | 2 - .../node-dist-without-corepack/node.exe | Bin 40766 -> 0 bytes .../node-dist-without-corepack/npm.cmd | 8 - .../node-dist-without-corepack/npx.cmd | 8 - .../node-dist-without-corepack/pnpm.cmd | 8 - .../node-dist-without-corepack/pnpx.cmd | 8 - .../node-dist-without-corepack/yarn.cmd | 8 - .../node-dist-without-npm/bin/corepack | 2 - .../resources/node-dist-without-npm/bin/node | 4 - .../resources/node-dist-without-npm/bin/npx | 2 - .../resources/node-dist-without-npm/bin/pnpm | 2 - .../resources/node-dist-without-npm/bin/pnpx | 2 - .../resources/node-dist-without-npm/bin/yarn | 2 - .../node-dist-without-npm/corepack.cmd | 8 - .../resources/node-dist-without-npm/node.exe | Bin 40766 -> 0 bytes .../resources/node-dist-without-npm/npx.cmd | 8 - .../resources/node-dist-without-npm/pnpm.cmd | 8 - .../resources/node-dist-without-npm/pnpx.cmd | 8 - .../resources/node-dist-without-npm/yarn.cmd | 8 - .../node-dist-without-npx/bin/corepack | 2 - .../resources/node-dist-without-npx/bin/node | 4 - .../resources/node-dist-without-npx/bin/npm | 2 - .../resources/node-dist-without-npx/bin/pnpm | 2 - .../resources/node-dist-without-npx/bin/pnpx | 2 - .../resources/node-dist-without-npx/bin/yarn | 2 - .../node-dist-without-npx/corepack.cmd | 8 - .../resources/node-dist-without-npx/node.exe | Bin 40766 -> 0 bytes .../resources/node-dist-without-npx/npm.cmd | 8 - .../resources/node-dist-without-npx/pnpm.cmd | 8 - .../resources/node-dist-without-npx/pnpx.cmd | 8 - .../resources/node-dist-without-npx/yarn.cmd | 8 - .../node-dist-without-pnpm/bin/corepack | 2 - .../resources/node-dist-without-pnpm/bin/node | 4 - .../resources/node-dist-without-pnpm/bin/npm | 2 - .../resources/node-dist-without-pnpm/bin/npx | 2 - .../resources/node-dist-without-pnpm/bin/pnpx | 2 - .../resources/node-dist-without-pnpm/bin/yarn | 2 - .../node-dist-without-pnpm/corepack.cmd | 8 - .../resources/node-dist-without-pnpm/node.exe | Bin 40766 -> 0 bytes .../resources/node-dist-without-pnpm/npm.cmd | 8 - .../resources/node-dist-without-pnpm/npx.cmd | 8 - .../resources/node-dist-without-pnpm/pnpx.cmd | 8 - .../resources/node-dist-without-pnpm/yarn.cmd | 8 - .../node-dist-without-pnpx/bin/corepack | 2 - .../resources/node-dist-without-pnpx/bin/node | 4 - .../resources/node-dist-without-pnpx/bin/npm | 2 - .../resources/node-dist-without-pnpx/bin/npx | 2 - .../resources/node-dist-without-pnpx/bin/pnpm | 2 - .../resources/node-dist-without-pnpx/bin/yarn | 2 - .../node-dist-without-pnpx/corepack.cmd | 8 - .../resources/node-dist-without-pnpx/node.exe | Bin 40766 -> 0 bytes .../resources/node-dist-without-pnpx/npm.cmd | 8 - .../resources/node-dist-without-pnpx/npx.cmd | 8 - .../resources/node-dist-without-pnpx/pnpm.cmd | 8 - .../resources/node-dist-without-pnpx/yarn.cmd | 8 - .../node-dist-without-yarn/bin/corepack | 2 - .../resources/node-dist-without-yarn/bin/node | 4 - .../resources/node-dist-without-yarn/bin/npm | 2 - .../resources/node-dist-without-yarn/bin/npx | 2 - .../resources/node-dist-without-yarn/bin/pnpm | 2 - .../resources/node-dist-without-yarn/bin/pnpx | 2 - .../node-dist-without-yarn/corepack.cmd | 8 - .../resources/node-dist-without-yarn/node.exe | Bin 40766 -> 0 bytes .../resources/node-dist-without-yarn/npm.cmd | 8 - .../resources/node-dist-without-yarn/npx.cmd | 8 - .../resources/node-dist-without-yarn/pnpm.cmd | 8 - .../resources/node-dist-without-yarn/pnpx.cmd | 8 - .../FrontendGradlePlugin.java | 379 +++++++++++------- .../domain/FileManager.java | 3 +- .../frontendgradleplugin/domain/Platform.java | 4 +- .../domain/PlatformProvider.java | 16 - .../domain/ResolvePackageManager.java | 31 +- .../domain/ResolvePackageManagerCommand.java | 2 +- .../domain/SystemProperties.java | 4 +- .../domain/SystemSettingsProvider.java | 61 --- .../installer/ResolveProxySettingsByUrl.java | 13 +- .../ResolveProxySettingsByUrlCommand.java | 4 +- .../installer/archiver/AbstractArchiver.java | 8 +- .../archiver/ArchiverProviderImpl.java | 7 +- .../infrastructure/archiver/TarArchiver.java | 6 +- .../infrastructure/archiver/TarEntry.java | 2 +- .../infrastructure/archiver/ZipArchiver.java | 7 +- .../infrastructure/bean/BeanRegistry.java | 66 ++- .../infrastructure/bean/Beans.java | 65 --- .../gradle/AbstractRunCommandTask.java | 69 +++- .../gradle/AbstractRunCommandTaskType.java | 37 +- .../infrastructure/gradle/AssembleTask.java | 10 +- .../gradle/BeanRegistryBuildService.java | 30 ++ .../infrastructure/gradle/CheckTask.java | 10 +- .../infrastructure/gradle/CleanTask.java | 10 +- .../gradle/FrontendExtension.java | 21 - .../gradle/GradleLoggerAdapter.java | 12 +- .../infrastructure/gradle/GradleSettings.java | 23 +- .../gradle/InstallFrontendTask.java | 6 +- .../gradle/InstallNodeTask.java | 142 ++++++- .../gradle/InstallPackageManagerTask.java | 35 +- .../infrastructure/gradle/PublishTask.java | 10 +- .../gradle/ResolvePackageManagerTask.java | 75 +++- .../infrastructure/gradle/RunCorepack.java | 8 +- .../infrastructure/gradle/RunNode.java | 8 +- .../infrastructure/gradle/RunNpm.java | 8 +- .../infrastructure/gradle/RunPnpm.java | 8 +- .../infrastructure/gradle/RunYarn.java | 8 +- .../gradle/SystemExtension.java | 77 ---- .../gradle/SystemProviders.java | 54 +++ .../gradle/SystemSettingsProviderImpl.java | 96 ----- ...igurer.java => TaskLoggerInitializer.java} | 32 +- .../httpclient/HttpClientProviderImpl.java | 8 +- .../system/ChannelProviderImpl.java | 7 +- .../system/FileManagerImpl.java | 4 + .../system/PlatformProviderImpl.java | 26 -- .../FrontendGradlePluginTest.java | 223 +++++++---- .../domain/SystemPropertiesFixture.java | 4 +- .../ResolveProxySettingsByUrlTest.java | 28 +- .../infrastructure/bean/BeanRegistryTest.java | 12 +- .../gradle/SystemProvidersTest.java | 70 ++++ .../SystemSettingsProviderImplTest.java | 149 ------- ...st.java => TaskLoggerInitializerTest.java} | 45 +-- site/src/components/link/yarn-link.vue | 13 +- .../node-install-directory-property.vue | 1 - .../task/assemble-frontend-task.vue | 7 +- .../components/task/check-frontend-task.vue | 7 +- .../components/task/clean-frontend-task.vue | 7 +- .../components/task/install-frontend-task.vue | 19 +- .../src/components/task/install-node-task.vue | 31 +- .../task/install-package-manager-task.vue | 5 +- .../task/optional-task-property-badge.vue | 10 + .../components/task/publish-frontend-task.vue | 11 +- .../task/resolve-package-manager-task.vue | 60 +-- .../task/run-corepack-task-type.vue | 6 +- .../components/task/run-node-task-type.vue | 8 +- .../src/components/task/run-npm-task-type.vue | 4 +- .../components/task/run-pnpm-task-type.vue | 4 +- .../components/task/run-yarn-task-type.vue | 4 +- site/src/components/task/task.vue | 21 +- .../v7/task/install-frontend-task-v7.vue | 2 +- site/src/pages/7/index.vue | 2 +- site/src/pages/getting-started.vue | 4 +- site/src/pages/index.vue | 2 +- site/src/utils/task-outcome.ts | 8 + 158 files changed, 1435 insertions(+), 1640 deletions(-) rename plugin/src/intTest/java/org/siouan/frontendgradleplugin/infrastructure/gradle/{AssembleTaskFuncTest.java => AssembleFrontedTaskFuncTest.java} (58%) rename plugin/src/intTest/java/org/siouan/frontendgradleplugin/infrastructure/gradle/{PublishTaskFuncTest.java => PublishFrontendTaskFuncTest.java} (57%) delete mode 100644 plugin/src/intTest/resources/node-dist-without-corepack/bin/node delete mode 100644 plugin/src/intTest/resources/node-dist-without-corepack/bin/npm delete mode 100644 plugin/src/intTest/resources/node-dist-without-corepack/bin/npx delete mode 100644 plugin/src/intTest/resources/node-dist-without-corepack/bin/pnpm delete mode 100644 plugin/src/intTest/resources/node-dist-without-corepack/bin/pnpx delete mode 100644 plugin/src/intTest/resources/node-dist-without-corepack/bin/yarn delete mode 100644 plugin/src/intTest/resources/node-dist-without-corepack/node.exe delete mode 100644 plugin/src/intTest/resources/node-dist-without-corepack/npm.cmd delete mode 100644 plugin/src/intTest/resources/node-dist-without-corepack/npx.cmd delete mode 100644 plugin/src/intTest/resources/node-dist-without-corepack/pnpm.cmd delete mode 100644 plugin/src/intTest/resources/node-dist-without-corepack/pnpx.cmd delete mode 100644 plugin/src/intTest/resources/node-dist-without-corepack/yarn.cmd delete mode 100644 plugin/src/intTest/resources/node-dist-without-npm/bin/corepack delete mode 100644 plugin/src/intTest/resources/node-dist-without-npm/bin/node delete mode 100644 plugin/src/intTest/resources/node-dist-without-npm/bin/npx delete mode 100644 plugin/src/intTest/resources/node-dist-without-npm/bin/pnpm delete mode 100644 plugin/src/intTest/resources/node-dist-without-npm/bin/pnpx delete mode 100644 plugin/src/intTest/resources/node-dist-without-npm/bin/yarn delete mode 100644 plugin/src/intTest/resources/node-dist-without-npm/corepack.cmd delete mode 100644 plugin/src/intTest/resources/node-dist-without-npm/node.exe delete mode 100644 plugin/src/intTest/resources/node-dist-without-npm/npx.cmd delete mode 100644 plugin/src/intTest/resources/node-dist-without-npm/pnpm.cmd delete mode 100644 plugin/src/intTest/resources/node-dist-without-npm/pnpx.cmd delete mode 100644 plugin/src/intTest/resources/node-dist-without-npm/yarn.cmd delete mode 100644 plugin/src/intTest/resources/node-dist-without-npx/bin/corepack delete mode 100644 plugin/src/intTest/resources/node-dist-without-npx/bin/node delete mode 100644 plugin/src/intTest/resources/node-dist-without-npx/bin/npm delete mode 100644 plugin/src/intTest/resources/node-dist-without-npx/bin/pnpm delete mode 100644 plugin/src/intTest/resources/node-dist-without-npx/bin/pnpx delete mode 100644 plugin/src/intTest/resources/node-dist-without-npx/bin/yarn delete mode 100644 plugin/src/intTest/resources/node-dist-without-npx/corepack.cmd delete mode 100644 plugin/src/intTest/resources/node-dist-without-npx/node.exe delete mode 100644 plugin/src/intTest/resources/node-dist-without-npx/npm.cmd delete mode 100644 plugin/src/intTest/resources/node-dist-without-npx/pnpm.cmd delete mode 100644 plugin/src/intTest/resources/node-dist-without-npx/pnpx.cmd delete mode 100644 plugin/src/intTest/resources/node-dist-without-npx/yarn.cmd delete mode 100644 plugin/src/intTest/resources/node-dist-without-pnpm/bin/corepack delete mode 100644 plugin/src/intTest/resources/node-dist-without-pnpm/bin/node delete mode 100644 plugin/src/intTest/resources/node-dist-without-pnpm/bin/npm delete mode 100644 plugin/src/intTest/resources/node-dist-without-pnpm/bin/npx delete mode 100644 plugin/src/intTest/resources/node-dist-without-pnpm/bin/pnpx delete mode 100644 plugin/src/intTest/resources/node-dist-without-pnpm/bin/yarn delete mode 100644 plugin/src/intTest/resources/node-dist-without-pnpm/corepack.cmd delete mode 100644 plugin/src/intTest/resources/node-dist-without-pnpm/node.exe delete mode 100644 plugin/src/intTest/resources/node-dist-without-pnpm/npm.cmd delete mode 100644 plugin/src/intTest/resources/node-dist-without-pnpm/npx.cmd delete mode 100644 plugin/src/intTest/resources/node-dist-without-pnpm/pnpx.cmd delete mode 100644 plugin/src/intTest/resources/node-dist-without-pnpm/yarn.cmd delete mode 100644 plugin/src/intTest/resources/node-dist-without-pnpx/bin/corepack delete mode 100644 plugin/src/intTest/resources/node-dist-without-pnpx/bin/node delete mode 100644 plugin/src/intTest/resources/node-dist-without-pnpx/bin/npm delete mode 100644 plugin/src/intTest/resources/node-dist-without-pnpx/bin/npx delete mode 100644 plugin/src/intTest/resources/node-dist-without-pnpx/bin/pnpm delete mode 100644 plugin/src/intTest/resources/node-dist-without-pnpx/bin/yarn delete mode 100644 plugin/src/intTest/resources/node-dist-without-pnpx/corepack.cmd delete mode 100644 plugin/src/intTest/resources/node-dist-without-pnpx/node.exe delete mode 100644 plugin/src/intTest/resources/node-dist-without-pnpx/npm.cmd delete mode 100644 plugin/src/intTest/resources/node-dist-without-pnpx/npx.cmd delete mode 100644 plugin/src/intTest/resources/node-dist-without-pnpx/pnpm.cmd delete mode 100644 plugin/src/intTest/resources/node-dist-without-pnpx/yarn.cmd delete mode 100644 plugin/src/intTest/resources/node-dist-without-yarn/bin/corepack delete mode 100644 plugin/src/intTest/resources/node-dist-without-yarn/bin/node delete mode 100644 plugin/src/intTest/resources/node-dist-without-yarn/bin/npm delete mode 100644 plugin/src/intTest/resources/node-dist-without-yarn/bin/npx delete mode 100644 plugin/src/intTest/resources/node-dist-without-yarn/bin/pnpm delete mode 100644 plugin/src/intTest/resources/node-dist-without-yarn/bin/pnpx delete mode 100644 plugin/src/intTest/resources/node-dist-without-yarn/corepack.cmd delete mode 100644 plugin/src/intTest/resources/node-dist-without-yarn/node.exe delete mode 100644 plugin/src/intTest/resources/node-dist-without-yarn/npm.cmd delete mode 100644 plugin/src/intTest/resources/node-dist-without-yarn/npx.cmd delete mode 100644 plugin/src/intTest/resources/node-dist-without-yarn/pnpm.cmd delete mode 100644 plugin/src/intTest/resources/node-dist-without-yarn/pnpx.cmd delete mode 100644 plugin/src/main/java/org/siouan/frontendgradleplugin/domain/PlatformProvider.java delete mode 100644 plugin/src/main/java/org/siouan/frontendgradleplugin/domain/SystemSettingsProvider.java delete mode 100644 plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/bean/Beans.java create mode 100644 plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/BeanRegistryBuildService.java delete mode 100644 plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/SystemExtension.java create mode 100644 plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/SystemProviders.java delete mode 100644 plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/SystemSettingsProviderImpl.java rename plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/{TaskLoggerConfigurer.java => TaskLoggerInitializer.java} (59%) delete mode 100644 plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/system/PlatformProviderImpl.java create mode 100644 plugin/src/test/java/org/siouan/frontendgradleplugin/infrastructure/gradle/SystemProvidersTest.java delete mode 100644 plugin/src/test/java/org/siouan/frontendgradleplugin/infrastructure/gradle/SystemSettingsProviderImplTest.java rename plugin/src/test/java/org/siouan/frontendgradleplugin/infrastructure/gradle/{TaskLoggerConfigurerTest.java => TaskLoggerInitializerTest.java} (54%) create mode 100644 site/src/components/task/optional-task-property-badge.vue create mode 100644 site/src/utils/task-outcome.ts diff --git a/examples/application-with-preinstalled-nodejs-distribution/build.gradle.kts b/examples/application-with-preinstalled-nodejs-distribution/build.gradle.kts index 301292c6..d70ee558 100644 --- a/examples/application-with-preinstalled-nodejs-distribution/build.gradle.kts +++ b/examples/application-with-preinstalled-nodejs-distribution/build.gradle.kts @@ -17,6 +17,7 @@ frontend { } tasks.register("nodeVersion") { + dependsOn("installNode") script.set("-v") } diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index cd9ad0ad..e99230dc 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -27,8 +27,9 @@ version = fgpVersion description = fgpDescription java { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } withJavadocJar() withSourcesJar() } @@ -102,12 +103,12 @@ tasks.named("check") { tasks.named("jacocoTestReport") { dependsOn(tasks.named("test"), tasks.named("integrationTest")) executionData.setFrom( - file("${project.buildDir}/jacoco/test.exec"), - file("${project.buildDir}/jacoco/integrationTest.exec") + file("${project.layout.buildDirectory}/jacoco/test.exec"), + file("${project.layout.buildDirectory}/jacoco/integrationTest.exec") ) reports { xml.required.set(true) - xml.outputLocation.set(file("${buildDir}/reports/jacoco/report.xml")) + xml.outputLocation.set(file("${project.layout.buildDirectory}/reports/jacoco/report.xml")) } } @@ -118,6 +119,8 @@ idea { // Force integration test source set as test folder testSources.from(project.sourceSets.getByName("intTest").java.srcDirs) testResources.from(project.sourceSets.getByName("intTest").resources.srcDirs) + isDownloadJavadoc = true + isDownloadSources = true } } @@ -158,13 +161,14 @@ sonarqube { property("sonar.java.binaries", "build/classes/java/main") property("sonar.java.test.binaries", "build/classes/java/test,build/classes/java/intTest") property("sonar.junit.reportPaths", "build/test-results/test/,build/test-results/integrationTest/") - property("sonar.jacoco.xmlReportPaths", "${buildDir}/reports/jacoco/report.xml") + property("sonar.jacoco.xmlReportPaths", "${project.layout.buildDirectory}/reports/jacoco/report.xml") + property("sonar.java.test.binaries", "build/classes/java/test,build/classes/java/intTest") property("sonar.verbose", true) // Irrelevant duplications detected on task inputs property( "sonar.cpd.exclusions", - "**/org/siouan/frontendgradleplugin/domain/model/*.java,**/org/siouan/frontendgradleplugin/domain/usecase/Get*ExecutablePath.java" + "**/org/siouan/frontendgradleplugin/domain/model/*.java,**/org/siouan/frontendgradleplugin/domain/usecase/Resolve*ExecutablePath.java,**/org/siouan/frontendgradleplugin/infrastructure/gradle/FrontendExtension.java" ) } } diff --git a/plugin/buildSrc/build.gradle.kts b/plugin/buildSrc/build.gradle.kts index 39f9584b..268f1d70 100644 --- a/plugin/buildSrc/build.gradle.kts +++ b/plugin/buildSrc/build.gradle.kts @@ -7,6 +7,7 @@ dependencies { } java { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } } diff --git a/plugin/lombok.config b/plugin/lombok.config index df71bb6a..ba6373cb 100644 --- a/plugin/lombok.config +++ b/plugin/lombok.config @@ -1,2 +1,3 @@ config.stopBubbling = true lombok.addLombokGeneratedAnnotation = true +lombok.log.fieldName=LOGGER diff --git a/plugin/settings.gradle.kts b/plugin/settings.gradle.kts index 9f7aed6b..a286b6cb 100644 --- a/plugin/settings.gradle.kts +++ b/plugin/settings.gradle.kts @@ -2,21 +2,21 @@ val fgpArtifactId: String by extra pluginManagement { plugins { - id("com.gradle.enterprise") version "3.17.4" + id("com.gradle.develocity") version "3.17.4" id("com.gradle.plugin-publish") version "1.2.1" id("org.sonarqube") version "5.0.0.4638" } } plugins { - id("com.gradle.enterprise") + id("com.gradle.develocity") } rootProject.name = fgpArtifactId -gradleEnterprise { +develocity { buildScan { - termsOfServiceUrl = "https://gradle.com/terms-of-service" - termsOfServiceAgree = "yes" + termsOfUseUrl.set("https://gradle.com/help/legal-terms-of-use") + termsOfUseAgree.set("yes") } } diff --git a/plugin/src/intTest/java/org/siouan/frontendgradleplugin/infrastructure/gradle/AssembleTaskFuncTest.java b/plugin/src/intTest/java/org/siouan/frontendgradleplugin/infrastructure/gradle/AssembleFrontedTaskFuncTest.java similarity index 58% rename from plugin/src/intTest/java/org/siouan/frontendgradleplugin/infrastructure/gradle/AssembleTaskFuncTest.java rename to plugin/src/intTest/java/org/siouan/frontendgradleplugin/infrastructure/gradle/AssembleFrontedTaskFuncTest.java index 7e66db15..1e41bead 100644 --- a/plugin/src/intTest/java/org/siouan/frontendgradleplugin/infrastructure/gradle/AssembleTaskFuncTest.java +++ b/plugin/src/intTest/java/org/siouan/frontendgradleplugin/infrastructure/gradle/AssembleFrontedTaskFuncTest.java @@ -1,8 +1,13 @@ package org.siouan.frontendgradleplugin.infrastructure.gradle; +import static org.siouan.frontendgradleplugin.FrontendGradlePlugin.ASSEMBLE_TASK_NAME; +import static org.siouan.frontendgradleplugin.FrontendGradlePlugin.GRADLE_ASSEMBLE_TASK_NAME; import static org.siouan.frontendgradleplugin.test.GradleBuildAssertions.assertAssembleTaskOutcomes; import static org.siouan.frontendgradleplugin.test.GradleBuildFiles.createBuildFile; import static org.siouan.frontendgradleplugin.test.GradleHelper.runGradle; +import static org.siouan.frontendgradleplugin.test.PluginTaskOutcome.SKIPPED; +import static org.siouan.frontendgradleplugin.test.PluginTaskOutcome.SUCCESS; +import static org.siouan.frontendgradleplugin.test.PluginTaskOutcome.UP_TO_DATE; import static org.siouan.frontendgradleplugin.test.Resources.getResourcePath; import static org.siouan.frontendgradleplugin.test.Resources.getResourceUrl; @@ -14,16 +19,14 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import org.siouan.frontendgradleplugin.FrontendGradlePlugin; import org.siouan.frontendgradleplugin.test.FrontendMapBuilder; -import org.siouan.frontendgradleplugin.test.PluginTaskOutcome; /** - * Functional tests to verify the {@link AssembleTask} integration in a Gradle build. Test cases uses fake Node/Yarn - * distributions, to avoid the download overhead. The 'yarn' and 'npm' executables in these distributions simply call - * the 'node' executable with the same arguments. + * Functional tests to verify the {@link AssembleTask} integration in a Gradle build. Test cases uses fake + * Node/NPM/PNPM/Yarn distributions, to avoid the download overhead. The 'npm', 'pnpm', 'yarn' executables in these + * distributions simply call the 'node' executable with the same arguments. */ -class AssembleTaskFuncTest { +class AssembleFrontedTaskFuncTest { @TempDir Path projectDirectoryPath; @@ -36,7 +39,7 @@ void setUp() throws IOException { } @Test - void should_skip_task_when_package_json_file_is_not_a_file() throws IOException { + void should_skip_task_when_package_json_file_does_not_exist() throws IOException { final FrontendMapBuilder frontendMapBuilder = new FrontendMapBuilder() .nodeVersion("18.17.1") .nodeDistributionUrl(getResourceUrl("node-v18.17.1.zip")) @@ -44,15 +47,13 @@ void should_skip_task_when_package_json_file_is_not_a_file() throws IOException .packageJsonDirectory(packageJsonDirectoryPath); createBuildFile(projectDirectoryPath, frontendMapBuilder.toMap()); - final BuildResult result1 = runGradle(projectDirectoryPath, FrontendGradlePlugin.ASSEMBLE_TASK_NAME); + final BuildResult result1 = runGradle(projectDirectoryPath, ASSEMBLE_TASK_NAME); - assertAssembleTaskOutcomes(result1, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SKIPPED, - PluginTaskOutcome.SKIPPED, PluginTaskOutcome.SKIPPED, PluginTaskOutcome.SKIPPED, null); + assertAssembleTaskOutcomes(result1, SUCCESS, SUCCESS, SKIPPED, SKIPPED, SKIPPED, null); - final BuildResult result2 = runGradle(projectDirectoryPath, FrontendGradlePlugin.ASSEMBLE_TASK_NAME); + final BuildResult result2 = runGradle(projectDirectoryPath, ASSEMBLE_TASK_NAME); - assertAssembleTaskOutcomes(result2, PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.SKIPPED, - PluginTaskOutcome.SKIPPED, PluginTaskOutcome.SKIPPED, PluginTaskOutcome.SKIPPED, null); + assertAssembleTaskOutcomes(result2, UP_TO_DATE, UP_TO_DATE, SKIPPED, SKIPPED, SKIPPED, null); } @Test @@ -64,15 +65,13 @@ void should_skip_task_when_script_is_not_defined() throws IOException { .packageJsonDirectory(packageJsonDirectoryPath); createBuildFile(projectDirectoryPath, frontendMapBuilder.toMap()); - final BuildResult result1 = runGradle(projectDirectoryPath, FrontendGradlePlugin.ASSEMBLE_TASK_NAME); + final BuildResult result1 = runGradle(projectDirectoryPath, ASSEMBLE_TASK_NAME); - assertAssembleTaskOutcomes(result1, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, - PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SKIPPED, null); + assertAssembleTaskOutcomes(result1, SUCCESS, SUCCESS, SUCCESS, SUCCESS, SKIPPED, null); - final BuildResult result2 = runGradle(projectDirectoryPath, FrontendGradlePlugin.ASSEMBLE_TASK_NAME); + final BuildResult result2 = runGradle(projectDirectoryPath, ASSEMBLE_TASK_NAME); - assertAssembleTaskOutcomes(result2, PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.UP_TO_DATE, - PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SKIPPED, null); + assertAssembleTaskOutcomes(result2, UP_TO_DATE, UP_TO_DATE, UP_TO_DATE, SUCCESS, SKIPPED, null); } @Test @@ -84,17 +83,13 @@ void should_skip_task_when_running_gradle_task_and_script_is_not_defined() throw .packageJsonDirectory(packageJsonDirectoryPath); createBuildFile(projectDirectoryPath, frontendMapBuilder.toMap()); - final BuildResult result1 = runGradle(projectDirectoryPath, FrontendGradlePlugin.GRADLE_ASSEMBLE_TASK_NAME); + final BuildResult result1 = runGradle(projectDirectoryPath, GRADLE_ASSEMBLE_TASK_NAME); - assertAssembleTaskOutcomes(result1, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, - PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SKIPPED, - PluginTaskOutcome.UP_TO_DATE); + assertAssembleTaskOutcomes(result1, SUCCESS, SUCCESS, SUCCESS, SUCCESS, SKIPPED, UP_TO_DATE); - final BuildResult result2 = runGradle(projectDirectoryPath, FrontendGradlePlugin.GRADLE_ASSEMBLE_TASK_NAME); + final BuildResult result2 = runGradle(projectDirectoryPath, GRADLE_ASSEMBLE_TASK_NAME); - assertAssembleTaskOutcomes(result2, PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.UP_TO_DATE, - PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SKIPPED, - PluginTaskOutcome.UP_TO_DATE); + assertAssembleTaskOutcomes(result2, UP_TO_DATE, UP_TO_DATE, UP_TO_DATE, SUCCESS, SKIPPED, UP_TO_DATE); } @Test @@ -107,15 +102,12 @@ void should_assemble_frontend() throws IOException { .assembleScript("run assemble"); createBuildFile(projectDirectoryPath, frontendMapBuilder.toMap()); - final BuildResult result1 = runGradle(projectDirectoryPath, FrontendGradlePlugin.GRADLE_ASSEMBLE_TASK_NAME); + final BuildResult result1 = runGradle(projectDirectoryPath, GRADLE_ASSEMBLE_TASK_NAME); - assertAssembleTaskOutcomes(result1, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, - PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS); + assertAssembleTaskOutcomes(result1, SUCCESS, SUCCESS, SUCCESS, SUCCESS, SUCCESS, SUCCESS); - final BuildResult result2 = runGradle(projectDirectoryPath, FrontendGradlePlugin.GRADLE_ASSEMBLE_TASK_NAME); + final BuildResult result2 = runGradle(projectDirectoryPath, GRADLE_ASSEMBLE_TASK_NAME); - assertAssembleTaskOutcomes(result2, PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.UP_TO_DATE, - PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, - PluginTaskOutcome.SUCCESS); + assertAssembleTaskOutcomes(result2, UP_TO_DATE, UP_TO_DATE, UP_TO_DATE, SUCCESS, SUCCESS, SUCCESS); } } diff --git a/plugin/src/intTest/java/org/siouan/frontendgradleplugin/infrastructure/gradle/CheckFrontendTaskFuncTest.java b/plugin/src/intTest/java/org/siouan/frontendgradleplugin/infrastructure/gradle/CheckFrontendTaskFuncTest.java index 56ada41d..65407a24 100644 --- a/plugin/src/intTest/java/org/siouan/frontendgradleplugin/infrastructure/gradle/CheckFrontendTaskFuncTest.java +++ b/plugin/src/intTest/java/org/siouan/frontendgradleplugin/infrastructure/gradle/CheckFrontendTaskFuncTest.java @@ -1,8 +1,13 @@ package org.siouan.frontendgradleplugin.infrastructure.gradle; +import static org.siouan.frontendgradleplugin.FrontendGradlePlugin.CHECK_TASK_NAME; +import static org.siouan.frontendgradleplugin.FrontendGradlePlugin.GRADLE_CHECK_TASK_NAME; import static org.siouan.frontendgradleplugin.test.GradleBuildAssertions.assertCheckTaskOutcomes; import static org.siouan.frontendgradleplugin.test.GradleBuildFiles.createBuildFile; import static org.siouan.frontendgradleplugin.test.GradleHelper.runGradle; +import static org.siouan.frontendgradleplugin.test.PluginTaskOutcome.SKIPPED; +import static org.siouan.frontendgradleplugin.test.PluginTaskOutcome.SUCCESS; +import static org.siouan.frontendgradleplugin.test.PluginTaskOutcome.UP_TO_DATE; import static org.siouan.frontendgradleplugin.test.Resources.getResourcePath; import static org.siouan.frontendgradleplugin.test.Resources.getResourceUrl; @@ -13,14 +18,12 @@ import org.gradle.testkit.runner.BuildResult; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import org.siouan.frontendgradleplugin.FrontendGradlePlugin; import org.siouan.frontendgradleplugin.test.FrontendMapBuilder; -import org.siouan.frontendgradleplugin.test.PluginTaskOutcome; /** - * Functional tests to verify the {@link CheckTask} integration in a Gradle build. Test cases uses fake Node/Yarn - * distributions, to avoid the download overhead. The 'yarn' and 'npm' executables in these distributions simply call - * the 'node' executable with the same arguments. + * Functional tests to verify the {@link CheckTask} integration in a Gradle build. Test cases uses fake + * Node/NPM/PNPM/Yarn distributions, to avoid the download overhead. The 'npm', 'pnpm', 'yarn' executables in these + * distributions simply call the 'node' executable with the same arguments. */ class CheckFrontendTaskFuncTest { @@ -28,22 +31,20 @@ class CheckFrontendTaskFuncTest { Path projectDirectoryPath; @Test - void should_skip_task_when_package_json_file_is_not_a_file() throws IOException { + void should_skip_task_when_package_json_file_does_not_exist() throws IOException { final FrontendMapBuilder frontendMapBuilder = new FrontendMapBuilder() .nodeVersion("18.17.1") .nodeDistributionUrl(getResourceUrl("node-v18.17.1.zip")) .checkScript("run check"); createBuildFile(projectDirectoryPath, frontendMapBuilder.toMap()); - final BuildResult result1 = runGradle(projectDirectoryPath, FrontendGradlePlugin.CHECK_TASK_NAME); + final BuildResult result1 = runGradle(projectDirectoryPath, CHECK_TASK_NAME); - assertCheckTaskOutcomes(result1, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SKIPPED, - PluginTaskOutcome.SKIPPED, PluginTaskOutcome.SKIPPED, PluginTaskOutcome.SKIPPED, null); + assertCheckTaskOutcomes(result1, SUCCESS, SUCCESS, SKIPPED, SKIPPED, SKIPPED, null); - final BuildResult result2 = runGradle(projectDirectoryPath, FrontendGradlePlugin.CHECK_TASK_NAME); + final BuildResult result2 = runGradle(projectDirectoryPath, CHECK_TASK_NAME); - assertCheckTaskOutcomes(result2, PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.SKIPPED, - PluginTaskOutcome.SKIPPED, PluginTaskOutcome.SKIPPED, PluginTaskOutcome.SKIPPED, null); + assertCheckTaskOutcomes(result2, UP_TO_DATE, UP_TO_DATE, SKIPPED, SKIPPED, SKIPPED, null); } @Test @@ -54,15 +55,13 @@ void should_skip_task_when_script_is_not_defined() throws IOException { .nodeDistributionUrl(getResourceUrl("node-v18.17.1.zip")); createBuildFile(projectDirectoryPath, frontendMapBuilder.toMap()); - final BuildResult result1 = runGradle(projectDirectoryPath, FrontendGradlePlugin.CHECK_TASK_NAME); + final BuildResult result1 = runGradle(projectDirectoryPath, CHECK_TASK_NAME); - assertCheckTaskOutcomes(result1, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, - PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SKIPPED, null); + assertCheckTaskOutcomes(result1, SUCCESS, SUCCESS, SUCCESS, SUCCESS, SKIPPED, null); - final BuildResult result2 = runGradle(projectDirectoryPath, FrontendGradlePlugin.CHECK_TASK_NAME); + final BuildResult result2 = runGradle(projectDirectoryPath, CHECK_TASK_NAME); - assertCheckTaskOutcomes(result2, PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.UP_TO_DATE, - PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SKIPPED, null); + assertCheckTaskOutcomes(result2, UP_TO_DATE, UP_TO_DATE, UP_TO_DATE, SUCCESS, SKIPPED, null); } @Test @@ -73,17 +72,13 @@ void should_skip_task_when_running_gradle_task_and_script_is_not_defined() throw .nodeDistributionUrl(getResourceUrl("node-v18.17.1.zip")); createBuildFile(projectDirectoryPath, frontendMapBuilder.toMap()); - final BuildResult result1 = runGradle(projectDirectoryPath, FrontendGradlePlugin.GRADLE_CHECK_TASK_NAME); + final BuildResult result1 = runGradle(projectDirectoryPath, GRADLE_CHECK_TASK_NAME); - assertCheckTaskOutcomes(result1, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, - PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SKIPPED, - PluginTaskOutcome.UP_TO_DATE); + assertCheckTaskOutcomes(result1, SUCCESS, SUCCESS, SUCCESS, SUCCESS, SKIPPED, UP_TO_DATE); - final BuildResult result2 = runGradle(projectDirectoryPath, FrontendGradlePlugin.GRADLE_CHECK_TASK_NAME); + final BuildResult result2 = runGradle(projectDirectoryPath, GRADLE_CHECK_TASK_NAME); - assertCheckTaskOutcomes(result2, PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.UP_TO_DATE, - PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SKIPPED, - PluginTaskOutcome.UP_TO_DATE); + assertCheckTaskOutcomes(result2, UP_TO_DATE, UP_TO_DATE, UP_TO_DATE, SUCCESS, SKIPPED, UP_TO_DATE); } @Test @@ -95,15 +90,12 @@ void should_check_frontend() throws IOException { .checkScript("run check"); createBuildFile(projectDirectoryPath, frontendMapBuilder.toMap()); - final BuildResult result1 = runGradle(projectDirectoryPath, FrontendGradlePlugin.GRADLE_CHECK_TASK_NAME); + final BuildResult result1 = runGradle(projectDirectoryPath, GRADLE_CHECK_TASK_NAME); - assertCheckTaskOutcomes(result1, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, - PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS); + assertCheckTaskOutcomes(result1, SUCCESS, SUCCESS, SUCCESS, SUCCESS, SUCCESS, SUCCESS); - final BuildResult result2 = runGradle(projectDirectoryPath, FrontendGradlePlugin.GRADLE_CHECK_TASK_NAME); + final BuildResult result2 = runGradle(projectDirectoryPath, GRADLE_CHECK_TASK_NAME); - assertCheckTaskOutcomes(result2, PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.UP_TO_DATE, - PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, - PluginTaskOutcome.SUCCESS); + assertCheckTaskOutcomes(result2, UP_TO_DATE, UP_TO_DATE, UP_TO_DATE, SUCCESS, SUCCESS, SUCCESS); } } diff --git a/plugin/src/intTest/java/org/siouan/frontendgradleplugin/infrastructure/gradle/CleanFrontendTaskFuncTest.java b/plugin/src/intTest/java/org/siouan/frontendgradleplugin/infrastructure/gradle/CleanFrontendTaskFuncTest.java index 3173bb85..8c7d8c3d 100644 --- a/plugin/src/intTest/java/org/siouan/frontendgradleplugin/infrastructure/gradle/CleanFrontendTaskFuncTest.java +++ b/plugin/src/intTest/java/org/siouan/frontendgradleplugin/infrastructure/gradle/CleanFrontendTaskFuncTest.java @@ -1,8 +1,13 @@ package org.siouan.frontendgradleplugin.infrastructure.gradle; +import static org.siouan.frontendgradleplugin.FrontendGradlePlugin.CLEAN_TASK_NAME; +import static org.siouan.frontendgradleplugin.FrontendGradlePlugin.GRADLE_CLEAN_TASK_NAME; import static org.siouan.frontendgradleplugin.test.GradleBuildAssertions.assertCleanTaskOutcomes; import static org.siouan.frontendgradleplugin.test.GradleBuildFiles.createBuildFile; import static org.siouan.frontendgradleplugin.test.GradleHelper.runGradle; +import static org.siouan.frontendgradleplugin.test.PluginTaskOutcome.SKIPPED; +import static org.siouan.frontendgradleplugin.test.PluginTaskOutcome.SUCCESS; +import static org.siouan.frontendgradleplugin.test.PluginTaskOutcome.UP_TO_DATE; import static org.siouan.frontendgradleplugin.test.Resources.getResourcePath; import static org.siouan.frontendgradleplugin.test.Resources.getResourceUrl; @@ -13,9 +18,7 @@ import org.gradle.testkit.runner.BuildResult; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import org.siouan.frontendgradleplugin.FrontendGradlePlugin; import org.siouan.frontendgradleplugin.test.FrontendMapBuilder; -import org.siouan.frontendgradleplugin.test.PluginTaskOutcome; /** * Functional tests to verify the {@link CleanTask} integration in a Gradle build. Test cases uses fake Node/Yarn @@ -28,22 +31,20 @@ class CleanFrontendTaskFuncTest { Path projectDirectoryPath; @Test - void should_skip_task_when_package_json_file_is_not_a_file() throws IOException { + void should_skip_task_when_package_json_file_does_not_exist() throws IOException { final FrontendMapBuilder frontendMapBuilder = new FrontendMapBuilder() .nodeVersion("18.17.1") .nodeDistributionUrl(getResourceUrl("node-v18.17.1.zip")) .cleanScript("run clean"); createBuildFile(projectDirectoryPath, frontendMapBuilder.toMap()); - final BuildResult result1 = runGradle(projectDirectoryPath, FrontendGradlePlugin.CLEAN_TASK_NAME); + final BuildResult result1 = runGradle(projectDirectoryPath, CLEAN_TASK_NAME); - assertCleanTaskOutcomes(result1, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SKIPPED, - PluginTaskOutcome.SKIPPED, PluginTaskOutcome.SKIPPED, PluginTaskOutcome.SKIPPED, null); + assertCleanTaskOutcomes(result1, SUCCESS, SUCCESS, SKIPPED, SKIPPED, SKIPPED, null); - final BuildResult result2 = runGradle(projectDirectoryPath, FrontendGradlePlugin.CLEAN_TASK_NAME); + final BuildResult result2 = runGradle(projectDirectoryPath, CLEAN_TASK_NAME); - assertCleanTaskOutcomes(result2, PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.SKIPPED, - PluginTaskOutcome.SKIPPED, PluginTaskOutcome.SKIPPED, PluginTaskOutcome.SKIPPED, null); + assertCleanTaskOutcomes(result2, UP_TO_DATE, UP_TO_DATE, SKIPPED, SKIPPED, SKIPPED, null); } @Test @@ -54,15 +55,13 @@ void should_skip_task_when_script_is_not_defined() throws IOException { .nodeDistributionUrl(getResourceUrl("node-v18.17.1.zip")); createBuildFile(projectDirectoryPath, frontendMapBuilder.toMap()); - final BuildResult result1 = runGradle(projectDirectoryPath, FrontendGradlePlugin.CLEAN_TASK_NAME); + final BuildResult result1 = runGradle(projectDirectoryPath, CLEAN_TASK_NAME); - assertCleanTaskOutcomes(result1, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, - PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SKIPPED, null); + assertCleanTaskOutcomes(result1, SUCCESS, SUCCESS, SUCCESS, SUCCESS, SKIPPED, null); - final BuildResult result2 = runGradle(projectDirectoryPath, FrontendGradlePlugin.CLEAN_TASK_NAME); + final BuildResult result2 = runGradle(projectDirectoryPath, CLEAN_TASK_NAME); - assertCleanTaskOutcomes(result2, PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.UP_TO_DATE, - PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SKIPPED, null); + assertCleanTaskOutcomes(result2, UP_TO_DATE, UP_TO_DATE, UP_TO_DATE, SUCCESS, SKIPPED, null); } @Test @@ -73,16 +72,13 @@ void should_skip_task_when_running_gradle_task_and_script_is_not_defined() throw .nodeDistributionUrl(getResourceUrl("node-v18.17.1.zip")); createBuildFile(projectDirectoryPath, frontendMapBuilder.toMap()); - final BuildResult result1 = runGradle(projectDirectoryPath, FrontendGradlePlugin.GRADLE_CLEAN_TASK_NAME); + final BuildResult result1 = runGradle(projectDirectoryPath, GRADLE_CLEAN_TASK_NAME); - assertCleanTaskOutcomes(result1, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, - PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SKIPPED, PluginTaskOutcome.SUCCESS); + assertCleanTaskOutcomes(result1, SUCCESS, SUCCESS, SUCCESS, SUCCESS, SKIPPED, SUCCESS); - final BuildResult result2 = runGradle(projectDirectoryPath, FrontendGradlePlugin.GRADLE_CLEAN_TASK_NAME); + final BuildResult result2 = runGradle(projectDirectoryPath, GRADLE_CLEAN_TASK_NAME); - assertCleanTaskOutcomes(result2, PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.UP_TO_DATE, - PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SKIPPED, - PluginTaskOutcome.UP_TO_DATE); + assertCleanTaskOutcomes(result2, UP_TO_DATE, UP_TO_DATE, UP_TO_DATE, SUCCESS, SKIPPED, UP_TO_DATE); } @Test @@ -94,15 +90,12 @@ void should_clean_frontend() throws IOException { .cleanScript("run clean"); createBuildFile(projectDirectoryPath, frontendMapBuilder.toMap()); - final BuildResult result1 = runGradle(projectDirectoryPath, FrontendGradlePlugin.GRADLE_CLEAN_TASK_NAME); + final BuildResult result1 = runGradle(projectDirectoryPath, GRADLE_CLEAN_TASK_NAME); - assertCleanTaskOutcomes(result1, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, - PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS); + assertCleanTaskOutcomes(result1, SUCCESS, SUCCESS, SUCCESS, SUCCESS, SUCCESS, SUCCESS); - final BuildResult result2 = runGradle(projectDirectoryPath, FrontendGradlePlugin.GRADLE_CLEAN_TASK_NAME); + final BuildResult result2 = runGradle(projectDirectoryPath, GRADLE_CLEAN_TASK_NAME); - assertCleanTaskOutcomes(result2, PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.UP_TO_DATE, - PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, - PluginTaskOutcome.UP_TO_DATE); + assertCleanTaskOutcomes(result2, UP_TO_DATE, UP_TO_DATE, UP_TO_DATE, SUCCESS, SUCCESS, UP_TO_DATE); } } diff --git a/plugin/src/intTest/java/org/siouan/frontendgradleplugin/infrastructure/gradle/InstallFrontendTaskFuncTest.java b/plugin/src/intTest/java/org/siouan/frontendgradleplugin/infrastructure/gradle/InstallFrontendTaskFuncTest.java index 68553f11..c9530bb3 100644 --- a/plugin/src/intTest/java/org/siouan/frontendgradleplugin/infrastructure/gradle/InstallFrontendTaskFuncTest.java +++ b/plugin/src/intTest/java/org/siouan/frontendgradleplugin/infrastructure/gradle/InstallFrontendTaskFuncTest.java @@ -1,8 +1,12 @@ package org.siouan.frontendgradleplugin.infrastructure.gradle; +import static org.siouan.frontendgradleplugin.FrontendGradlePlugin.INSTALL_FRONTEND_TASK_NAME; import static org.siouan.frontendgradleplugin.test.GradleBuildAssertions.assertTaskOutcomes; import static org.siouan.frontendgradleplugin.test.GradleBuildFiles.createBuildFile; import static org.siouan.frontendgradleplugin.test.GradleHelper.runGradle; +import static org.siouan.frontendgradleplugin.test.PluginTaskOutcome.SKIPPED; +import static org.siouan.frontendgradleplugin.test.PluginTaskOutcome.SUCCESS; +import static org.siouan.frontendgradleplugin.test.PluginTaskOutcome.UP_TO_DATE; import static org.siouan.frontendgradleplugin.test.Resources.getResourcePath; import static org.siouan.frontendgradleplugin.test.Resources.getResourceUrl; @@ -13,14 +17,12 @@ import org.gradle.testkit.runner.BuildResult; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import org.siouan.frontendgradleplugin.FrontendGradlePlugin; import org.siouan.frontendgradleplugin.test.FrontendMapBuilder; -import org.siouan.frontendgradleplugin.test.PluginTaskOutcome; /** * Functional tests to verify the {@link InstallFrontendTask} integration in a Gradle build. Test cases uses fake - * Node/Yarn distributions, to avoid the download overhead. The 'yarn' and 'npm' executables in these distributions - * simply call the 'node' executable with the same arguments. + * Node/NPM/PNPM/Yarn distributions, to avoid the download overhead. The 'npm', 'pnpm', 'yarn' executables in these + * distributions simply call the 'node' executable with the same arguments. */ class InstallFrontendTaskFuncTest { @@ -28,21 +30,19 @@ class InstallFrontendTaskFuncTest { Path projectDirectoryPath; @Test - void should_skip_task_when_package_json_file_is_not_a_file() throws IOException { + void should_skip_task_when_package_json_file_does_not_exist() throws IOException { final FrontendMapBuilder frontendMapBuilder = new FrontendMapBuilder() .nodeVersion("18.17.1") .nodeDistributionUrl(getResourceUrl("node-v18.17.1.zip")); createBuildFile(projectDirectoryPath, frontendMapBuilder.toMap()); - final BuildResult result1 = runGradle(projectDirectoryPath, FrontendGradlePlugin.INSTALL_FRONTEND_TASK_NAME); + final BuildResult result1 = runGradle(projectDirectoryPath, INSTALL_FRONTEND_TASK_NAME); - assertTaskOutcomes(result1, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SKIPPED, PluginTaskOutcome.SKIPPED, - PluginTaskOutcome.SKIPPED); + assertTaskOutcomes(result1, SUCCESS, SUCCESS, SKIPPED, SKIPPED); - final BuildResult result2 = runGradle(projectDirectoryPath, FrontendGradlePlugin.INSTALL_FRONTEND_TASK_NAME); + final BuildResult result2 = runGradle(projectDirectoryPath, INSTALL_FRONTEND_TASK_NAME); - assertTaskOutcomes(result2, PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.SKIPPED, PluginTaskOutcome.SKIPPED, - PluginTaskOutcome.SKIPPED); + assertTaskOutcomes(result2, UP_TO_DATE, UP_TO_DATE, SKIPPED, SKIPPED); } @Test @@ -53,15 +53,13 @@ void should_succeed_with_default_script() throws IOException { .nodeDistributionUrl(getResourceUrl("node-v18.17.1.zip")); createBuildFile(projectDirectoryPath, frontendMapBuilder.toMap()); - final BuildResult result1 = runGradle(projectDirectoryPath, FrontendGradlePlugin.INSTALL_FRONTEND_TASK_NAME); + final BuildResult result1 = runGradle(projectDirectoryPath, INSTALL_FRONTEND_TASK_NAME); - assertTaskOutcomes(result1, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, - PluginTaskOutcome.SUCCESS); + assertTaskOutcomes(result1, SUCCESS, SUCCESS, SUCCESS, SUCCESS); - final BuildResult result2 = runGradle(projectDirectoryPath, FrontendGradlePlugin.INSTALL_FRONTEND_TASK_NAME); + final BuildResult result2 = runGradle(projectDirectoryPath, INSTALL_FRONTEND_TASK_NAME); - assertTaskOutcomes(result2, PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.UP_TO_DATE, - PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.SUCCESS); + assertTaskOutcomes(result2, UP_TO_DATE, UP_TO_DATE, UP_TO_DATE, SUCCESS); } @Test @@ -73,14 +71,12 @@ void should_succeed_with_custom_script() throws IOException { .installScript("ci"); createBuildFile(projectDirectoryPath, frontendMapBuilder.toMap()); - final BuildResult result1 = runGradle(projectDirectoryPath, FrontendGradlePlugin.INSTALL_FRONTEND_TASK_NAME); + final BuildResult result1 = runGradle(projectDirectoryPath, INSTALL_FRONTEND_TASK_NAME); - assertTaskOutcomes(result1, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, - PluginTaskOutcome.SUCCESS); + assertTaskOutcomes(result1, SUCCESS, SUCCESS, SUCCESS, SUCCESS); - final BuildResult result2 = runGradle(projectDirectoryPath, FrontendGradlePlugin.INSTALL_FRONTEND_TASK_NAME); + final BuildResult result2 = runGradle(projectDirectoryPath, INSTALL_FRONTEND_TASK_NAME); - assertTaskOutcomes(result2, PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.UP_TO_DATE, - PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.SUCCESS); + assertTaskOutcomes(result2, UP_TO_DATE, UP_TO_DATE, UP_TO_DATE, SUCCESS); } } diff --git a/plugin/src/intTest/java/org/siouan/frontendgradleplugin/infrastructure/gradle/InstallPackageManagerTaskFuncTest.java b/plugin/src/intTest/java/org/siouan/frontendgradleplugin/infrastructure/gradle/InstallPackageManagerTaskFuncTest.java index 1ea246f0..263d4211 100644 --- a/plugin/src/intTest/java/org/siouan/frontendgradleplugin/infrastructure/gradle/InstallPackageManagerTaskFuncTest.java +++ b/plugin/src/intTest/java/org/siouan/frontendgradleplugin/infrastructure/gradle/InstallPackageManagerTaskFuncTest.java @@ -1,9 +1,14 @@ package org.siouan.frontendgradleplugin.infrastructure.gradle; +import static org.siouan.frontendgradleplugin.FrontendGradlePlugin.INSTALL_PACKAGE_MANAGER_TASK_NAME; import static org.siouan.frontendgradleplugin.test.GradleBuildAssertions.assertTaskOutcomes; import static org.siouan.frontendgradleplugin.test.GradleBuildFiles.createBuildFile; import static org.siouan.frontendgradleplugin.test.GradleHelper.runGradle; import static org.siouan.frontendgradleplugin.test.GradleHelper.runGradleAndExpectFailure; +import static org.siouan.frontendgradleplugin.test.PluginTaskOutcome.FAILED; +import static org.siouan.frontendgradleplugin.test.PluginTaskOutcome.SKIPPED; +import static org.siouan.frontendgradleplugin.test.PluginTaskOutcome.SUCCESS; +import static org.siouan.frontendgradleplugin.test.PluginTaskOutcome.UP_TO_DATE; import static org.siouan.frontendgradleplugin.test.Resources.getResourcePath; import static org.siouan.frontendgradleplugin.test.Resources.getResourceUrl; @@ -15,9 +20,7 @@ import org.gradle.testkit.runner.BuildResult; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import org.siouan.frontendgradleplugin.FrontendGradlePlugin; import org.siouan.frontendgradleplugin.test.FrontendMapBuilder; -import org.siouan.frontendgradleplugin.test.PluginTaskOutcome; /** * Functional tests to verify the {@link InstallPackageManagerTask} integration in a Gradle build. Test cases uses a @@ -30,21 +33,19 @@ class InstallPackageManagerTaskFuncTest { Path projectDirectoryPath; @Test - void should_skip_task_when_package_json_file_is_not_a_file() throws IOException { + void should_skip_task_when_package_json_file_does_not_exist() throws IOException { final FrontendMapBuilder frontendMapBuilder = new FrontendMapBuilder() .nodeVersion("18.17.1") .nodeDistributionUrl(getResourceUrl("node-v18.17.1.zip")); createBuildFile(projectDirectoryPath, frontendMapBuilder.toMap()); - final BuildResult result1 = runGradle(projectDirectoryPath, - FrontendGradlePlugin.INSTALL_PACKAGE_MANAGER_TASK_NAME); + final BuildResult result1 = runGradle(projectDirectoryPath, INSTALL_PACKAGE_MANAGER_TASK_NAME); - assertTaskOutcomes(result1, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SKIPPED, PluginTaskOutcome.SKIPPED); + assertTaskOutcomes(result1, SUCCESS, SUCCESS, SKIPPED); - final BuildResult result2 = runGradle(projectDirectoryPath, - FrontendGradlePlugin.INSTALL_PACKAGE_MANAGER_TASK_NAME); + final BuildResult result2 = runGradle(projectDirectoryPath, INSTALL_PACKAGE_MANAGER_TASK_NAME); - assertTaskOutcomes(result2, PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.SKIPPED, PluginTaskOutcome.SKIPPED); + assertTaskOutcomes(result2, UP_TO_DATE, UP_TO_DATE, SKIPPED); } @Test @@ -52,10 +53,9 @@ void should_fail_when_node_install_directory_is_not_a_directory() throws IOExcep Files.copy(getResourcePath("package-any-manager.json"), projectDirectoryPath.resolve("package.json")); createBuildFile(projectDirectoryPath, new FrontendMapBuilder().nodeDistributionProvided(true).toMap()); - final BuildResult result = runGradleAndExpectFailure(projectDirectoryPath, - FrontendGradlePlugin.INSTALL_PACKAGE_MANAGER_TASK_NAME); + final BuildResult result = runGradleAndExpectFailure(projectDirectoryPath, INSTALL_PACKAGE_MANAGER_TASK_NAME); - assertTaskOutcomes(result, PluginTaskOutcome.SKIPPED, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.FAILED); + assertTaskOutcomes(result, SKIPPED, SUCCESS, FAILED); } @Test @@ -66,48 +66,36 @@ void should_install_package_managers() throws IOException { .nodeDistributionUrl(getResourceUrl("node-v18.17.1.zip")); createBuildFile(projectDirectoryPath, frontendMapBuilder.toMap()); - final BuildResult installNpmResult1 = runGradle(projectDirectoryPath, - FrontendGradlePlugin.INSTALL_PACKAGE_MANAGER_TASK_NAME); + final BuildResult installNpmResult1 = runGradle(projectDirectoryPath, INSTALL_PACKAGE_MANAGER_TASK_NAME); - assertTaskOutcomes(installNpmResult1, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, - PluginTaskOutcome.SUCCESS); + assertTaskOutcomes(installNpmResult1, SUCCESS, SUCCESS, SUCCESS); - final BuildResult installNpmResult2 = runGradle(projectDirectoryPath, - FrontendGradlePlugin.INSTALL_PACKAGE_MANAGER_TASK_NAME); + final BuildResult installNpmResult2 = runGradle(projectDirectoryPath, INSTALL_PACKAGE_MANAGER_TASK_NAME); - assertTaskOutcomes(installNpmResult2, PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.UP_TO_DATE, - PluginTaskOutcome.UP_TO_DATE); + assertTaskOutcomes(installNpmResult2, UP_TO_DATE, UP_TO_DATE, UP_TO_DATE); Files.copy(getResourcePath("package-pnpm.json"), projectDirectoryPath.resolve("package.json"), StandardCopyOption.REPLACE_EXISTING); createBuildFile(projectDirectoryPath, frontendMapBuilder.toMap()); - final BuildResult installPnpmResult1 = runGradle(projectDirectoryPath, - FrontendGradlePlugin.INSTALL_PACKAGE_MANAGER_TASK_NAME); + final BuildResult installPnpmResult1 = runGradle(projectDirectoryPath, INSTALL_PACKAGE_MANAGER_TASK_NAME); - assertTaskOutcomes(installPnpmResult1, PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.SUCCESS, - PluginTaskOutcome.SUCCESS); + assertTaskOutcomes(installPnpmResult1, UP_TO_DATE, SUCCESS, SUCCESS); - final BuildResult installPnpmResult2 = runGradle(projectDirectoryPath, - FrontendGradlePlugin.INSTALL_PACKAGE_MANAGER_TASK_NAME); + final BuildResult installPnpmResult2 = runGradle(projectDirectoryPath, INSTALL_PACKAGE_MANAGER_TASK_NAME); - assertTaskOutcomes(installPnpmResult2, PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.UP_TO_DATE, - PluginTaskOutcome.UP_TO_DATE); + assertTaskOutcomes(installPnpmResult2, UP_TO_DATE, UP_TO_DATE, UP_TO_DATE); Files.copy(getResourcePath("package-yarn.json"), projectDirectoryPath.resolve("package.json"), StandardCopyOption.REPLACE_EXISTING); createBuildFile(projectDirectoryPath, frontendMapBuilder.toMap()); - final BuildResult installYarnResult1 = runGradle(projectDirectoryPath, - FrontendGradlePlugin.INSTALL_PACKAGE_MANAGER_TASK_NAME); + final BuildResult installYarnResult1 = runGradle(projectDirectoryPath, INSTALL_PACKAGE_MANAGER_TASK_NAME); - assertTaskOutcomes(installYarnResult1, PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.SUCCESS, - PluginTaskOutcome.SUCCESS); + assertTaskOutcomes(installYarnResult1, UP_TO_DATE, SUCCESS, SUCCESS); - final BuildResult installYarnResult2 = runGradle(projectDirectoryPath, - FrontendGradlePlugin.INSTALL_PACKAGE_MANAGER_TASK_NAME); + final BuildResult installYarnResult2 = runGradle(projectDirectoryPath, INSTALL_PACKAGE_MANAGER_TASK_NAME); - assertTaskOutcomes(installYarnResult2, PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.UP_TO_DATE, - PluginTaskOutcome.UP_TO_DATE); + assertTaskOutcomes(installYarnResult2, UP_TO_DATE, UP_TO_DATE, UP_TO_DATE); } } diff --git a/plugin/src/intTest/java/org/siouan/frontendgradleplugin/infrastructure/gradle/PublishTaskFuncTest.java b/plugin/src/intTest/java/org/siouan/frontendgradleplugin/infrastructure/gradle/PublishFrontendTaskFuncTest.java similarity index 57% rename from plugin/src/intTest/java/org/siouan/frontendgradleplugin/infrastructure/gradle/PublishTaskFuncTest.java rename to plugin/src/intTest/java/org/siouan/frontendgradleplugin/infrastructure/gradle/PublishFrontendTaskFuncTest.java index 29c886e1..ddc369bf 100644 --- a/plugin/src/intTest/java/org/siouan/frontendgradleplugin/infrastructure/gradle/PublishTaskFuncTest.java +++ b/plugin/src/intTest/java/org/siouan/frontendgradleplugin/infrastructure/gradle/PublishFrontendTaskFuncTest.java @@ -1,8 +1,13 @@ package org.siouan.frontendgradleplugin.infrastructure.gradle; +import static org.siouan.frontendgradleplugin.FrontendGradlePlugin.GRADLE_PUBLISH_TASK_NAME; +import static org.siouan.frontendgradleplugin.FrontendGradlePlugin.PUBLISH_TASK_NAME; import static org.siouan.frontendgradleplugin.test.GradleBuildAssertions.assertPublishTaskOutcomes; import static org.siouan.frontendgradleplugin.test.GradleBuildFiles.createBuildFile; import static org.siouan.frontendgradleplugin.test.GradleHelper.runGradle; +import static org.siouan.frontendgradleplugin.test.PluginTaskOutcome.SKIPPED; +import static org.siouan.frontendgradleplugin.test.PluginTaskOutcome.SUCCESS; +import static org.siouan.frontendgradleplugin.test.PluginTaskOutcome.UP_TO_DATE; import static org.siouan.frontendgradleplugin.test.Resources.getResourcePath; import static org.siouan.frontendgradleplugin.test.Resources.getResourceUrl; @@ -14,18 +19,16 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import org.siouan.frontendgradleplugin.FrontendGradlePlugin; import org.siouan.frontendgradleplugin.test.FrontendMapBuilder; -import org.siouan.frontendgradleplugin.test.PluginTaskOutcome; /** - * Functional tests to verify the {@link PublishTask} integration in a Gradle build. Test cases uses fake Node/Yarn - * distributions, to avoid the download overhead. The 'yarn' and 'npm' executables in these distributions simply call - * the 'node' executable with the same arguments. + * Functional tests to verify the {@link PublishTask} integration in a Gradle build. Test cases uses fake + * Node/NPM/PNPM/Yarn distributions, to avoid the download overhead. The 'npm', 'pnpm', 'yarn' executables in these + * distributions simply call the 'node' executable with the same arguments. * * @since 1.4.0 */ -class PublishTaskFuncTest { +class PublishFrontendTaskFuncTest { @TempDir Path projectDirectoryPath; @@ -37,6 +40,25 @@ void setUp() throws IOException { packageJsonDirectoryPath = Files.createDirectory(projectDirectoryPath.resolve("frontend")); } + @Test + void should_skip_plugin_task_when_package_json_file_does_not_exist() throws IOException { + final FrontendMapBuilder frontendMapBuilder = new FrontendMapBuilder() + .nodeVersion("18.17.1") + .nodeDistributionUrl(getResourceUrl("node-v18.17.1.zip")) + .assembleScript("run assemble") + .publishScript("run publish") + .packageJsonDirectory(packageJsonDirectoryPath); + createBuildFile(projectDirectoryPath, frontendMapBuilder.toMap()); + + final BuildResult result1 = runGradle(projectDirectoryPath, PUBLISH_TASK_NAME); + + assertPublishTaskOutcomes(result1, SUCCESS, SUCCESS, SKIPPED, SKIPPED, SKIPPED, null); + + final BuildResult result2 = runGradle(projectDirectoryPath, PUBLISH_TASK_NAME); + + assertPublishTaskOutcomes(result2, UP_TO_DATE, UP_TO_DATE, SKIPPED, SKIPPED, SKIPPED, null); + } + @Test void should_skip_plugin_task_when_script_is_not_defined() throws IOException { Files.copy(getResourcePath("package-any-manager.json"), packageJsonDirectoryPath.resolve("package.json")); @@ -46,15 +68,13 @@ void should_skip_plugin_task_when_script_is_not_defined() throws IOException { .packageJsonDirectory(packageJsonDirectoryPath); createBuildFile(projectDirectoryPath, frontendMapBuilder.toMap()); - final BuildResult result1 = runGradle(projectDirectoryPath, FrontendGradlePlugin.PUBLISH_TASK_NAME); + final BuildResult result1 = runGradle(projectDirectoryPath, PUBLISH_TASK_NAME); - assertPublishTaskOutcomes(result1, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, - PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SKIPPED, null); + assertPublishTaskOutcomes(result1, SUCCESS, SUCCESS, SUCCESS, SUCCESS, SKIPPED, null); - final BuildResult result2 = runGradle(projectDirectoryPath, FrontendGradlePlugin.PUBLISH_TASK_NAME); + final BuildResult result2 = runGradle(projectDirectoryPath, PUBLISH_TASK_NAME); - assertPublishTaskOutcomes(result2, PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.UP_TO_DATE, - PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SKIPPED, null); + assertPublishTaskOutcomes(result2, UP_TO_DATE, UP_TO_DATE, UP_TO_DATE, SUCCESS, SKIPPED, null); } @Test @@ -66,17 +86,13 @@ void should_skip_plugin_task_when_running_gradle_task_and_script_is_not_defined( .packageJsonDirectory(packageJsonDirectoryPath); createBuildFile(projectDirectoryPath, frontendMapBuilder.toMap()); - final BuildResult result1 = runGradle(projectDirectoryPath, FrontendGradlePlugin.GRADLE_PUBLISH_TASK_NAME); + final BuildResult result1 = runGradle(projectDirectoryPath, GRADLE_PUBLISH_TASK_NAME); - assertPublishTaskOutcomes(result1, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, - PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SKIPPED, - PluginTaskOutcome.UP_TO_DATE); + assertPublishTaskOutcomes(result1, SUCCESS, SUCCESS, SUCCESS, SUCCESS, SKIPPED, UP_TO_DATE); - final BuildResult result2 = runGradle(projectDirectoryPath, FrontendGradlePlugin.GRADLE_PUBLISH_TASK_NAME); + final BuildResult result2 = runGradle(projectDirectoryPath, GRADLE_PUBLISH_TASK_NAME); - assertPublishTaskOutcomes(result2, PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.UP_TO_DATE, - PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SKIPPED, - PluginTaskOutcome.UP_TO_DATE); + assertPublishTaskOutcomes(result2, UP_TO_DATE, UP_TO_DATE, UP_TO_DATE, SUCCESS, SKIPPED, UP_TO_DATE); } @Test @@ -90,15 +106,12 @@ void should_publish_frontend() throws IOException { .publishScript("run publish"); createBuildFile(projectDirectoryPath, frontendMapBuilder.toMap()); - final BuildResult result1 = runGradle(projectDirectoryPath, FrontendGradlePlugin.GRADLE_PUBLISH_TASK_NAME); + final BuildResult result1 = runGradle(projectDirectoryPath, GRADLE_PUBLISH_TASK_NAME); - assertPublishTaskOutcomes(result1, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, - PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS); + assertPublishTaskOutcomes(result1, SUCCESS, SUCCESS, SUCCESS, SUCCESS, SUCCESS, SUCCESS); - final BuildResult result2 = runGradle(projectDirectoryPath, FrontendGradlePlugin.GRADLE_PUBLISH_TASK_NAME); + final BuildResult result2 = runGradle(projectDirectoryPath, GRADLE_PUBLISH_TASK_NAME); - assertPublishTaskOutcomes(result2, PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.UP_TO_DATE, - PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS, - PluginTaskOutcome.SUCCESS); + assertPublishTaskOutcomes(result2, UP_TO_DATE, UP_TO_DATE, UP_TO_DATE, SUCCESS, SUCCESS, SUCCESS); } } diff --git a/plugin/src/intTest/java/org/siouan/frontendgradleplugin/infrastructure/gradle/ResolvePackageManagerTaskFuncTest.java b/plugin/src/intTest/java/org/siouan/frontendgradleplugin/infrastructure/gradle/ResolvePackageManagerTaskFuncTest.java index 841ffd59..b53b993e 100644 --- a/plugin/src/intTest/java/org/siouan/frontendgradleplugin/infrastructure/gradle/ResolvePackageManagerTaskFuncTest.java +++ b/plugin/src/intTest/java/org/siouan/frontendgradleplugin/infrastructure/gradle/ResolvePackageManagerTaskFuncTest.java @@ -1,10 +1,19 @@ package org.siouan.frontendgradleplugin.infrastructure.gradle; import static org.assertj.core.api.Assertions.assertThat; +import static org.siouan.frontendgradleplugin.FrontendGradlePlugin.DEFAULT_CACHE_DIRECTORY_NAME; +import static org.siouan.frontendgradleplugin.FrontendGradlePlugin.PACKAGE_JSON_FILE_NAME; +import static org.siouan.frontendgradleplugin.FrontendGradlePlugin.PACKAGE_MANAGER_EXECUTABLE_PATH_FILE_NAME; +import static org.siouan.frontendgradleplugin.FrontendGradlePlugin.PACKAGE_MANAGER_SPECIFICATION_FILE_NAME; +import static org.siouan.frontendgradleplugin.FrontendGradlePlugin.RESOLVE_PACKAGE_MANAGER_TASK_NAME; import static org.siouan.frontendgradleplugin.test.GradleBuildAssertions.assertTaskOutcomes; import static org.siouan.frontendgradleplugin.test.GradleBuildFiles.createBuildFile; import static org.siouan.frontendgradleplugin.test.GradleHelper.runGradle; import static org.siouan.frontendgradleplugin.test.GradleHelper.runGradleAndExpectFailure; +import static org.siouan.frontendgradleplugin.test.PluginTaskOutcome.FAILED; +import static org.siouan.frontendgradleplugin.test.PluginTaskOutcome.SKIPPED; +import static org.siouan.frontendgradleplugin.test.PluginTaskOutcome.SUCCESS; +import static org.siouan.frontendgradleplugin.test.PluginTaskOutcome.UP_TO_DATE; import static org.siouan.frontendgradleplugin.test.Resources.getResourcePath; import static org.siouan.frontendgradleplugin.test.Resources.getResourceUrl; @@ -17,9 +26,7 @@ import org.gradle.testkit.runner.BuildResult; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import org.siouan.frontendgradleplugin.FrontendGradlePlugin; import org.siouan.frontendgradleplugin.test.FrontendMapBuilder; -import org.siouan.frontendgradleplugin.test.PluginTaskOutcome; /** * Functional tests to verify the {@link ResolvePackageManagerTask} integration in a Gradle build. @@ -32,20 +39,30 @@ class ResolvePackageManagerTaskFuncTest { Path projectDirectoryPath; @Test - void should_skip_task_when_package_json_file_is_not_a_file() throws IOException { - Files.createDirectory(projectDirectoryPath.resolve(FrontendGradlePlugin.PACKAGE_JSON_FILE_NAME)); + void should_delete_output_files_when_package_json_file_does_not_exist() throws IOException { + Files.createDirectory(projectDirectoryPath.resolve(PACKAGE_JSON_FILE_NAME)); createBuildFile(projectDirectoryPath, new FrontendMapBuilder().nodeDistributionProvided(true).toMap()); + final Path packageManagerSpecificationFilePath = projectDirectoryPath.resolve( + Paths.get(DEFAULT_CACHE_DIRECTORY_NAME, RESOLVE_PACKAGE_MANAGER_TASK_NAME, + PACKAGE_MANAGER_SPECIFICATION_FILE_NAME)); + Files.createDirectories(packageManagerSpecificationFilePath.getParent()); + Files.createFile(packageManagerSpecificationFilePath); + final Path packageManagerExecutablePathFilePath = projectDirectoryPath.resolve( + Paths.get(DEFAULT_CACHE_DIRECTORY_NAME, RESOLVE_PACKAGE_MANAGER_TASK_NAME, + PACKAGE_MANAGER_EXECUTABLE_PATH_FILE_NAME)); + Files.createFile(packageManagerExecutablePathFilePath); + + final BuildResult result1 = runGradle(projectDirectoryPath, RESOLVE_PACKAGE_MANAGER_TASK_NAME); + + assertTaskOutcomes(result1, SKIPPED, SUCCESS); + assertThat(packageManagerSpecificationFilePath).doesNotExist(); + assertThat(packageManagerExecutablePathFilePath).doesNotExist(); - final BuildResult result = runGradle(projectDirectoryPath, - FrontendGradlePlugin.RESOLVE_PACKAGE_MANAGER_TASK_NAME); + final BuildResult result2 = runGradle(projectDirectoryPath, RESOLVE_PACKAGE_MANAGER_TASK_NAME); - assertTaskOutcomes(result, PluginTaskOutcome.SKIPPED, PluginTaskOutcome.SKIPPED); - assertThat(projectDirectoryPath.resolve(Paths.get(FrontendGradlePlugin.DEFAULT_CACHE_DIRECTORY_NAME, - FrontendGradlePlugin.RESOLVE_PACKAGE_MANAGER_TASK_NAME, - FrontendGradlePlugin.PACKAGE_MANAGER_SPECIFICATION_FILE_NAME))).doesNotExist(); - assertThat(projectDirectoryPath.resolve(Paths.get(FrontendGradlePlugin.DEFAULT_CACHE_DIRECTORY_NAME, - FrontendGradlePlugin.RESOLVE_PACKAGE_MANAGER_TASK_NAME, - FrontendGradlePlugin.PACKAGE_MANAGER_EXECUTABLE_PATH_FILE_NAME))).doesNotExist(); + assertTaskOutcomes(result2, SKIPPED, UP_TO_DATE); + assertThat(packageManagerSpecificationFilePath).doesNotExist(); + assertThat(packageManagerExecutablePathFilePath).doesNotExist(); } @Test @@ -56,16 +73,15 @@ void should_fail_when_package_manager_property_is_not_set_in_package_json_file() .nodeInstallDirectory(getResourcePath("node-dist-provided")) .toMap()); - final BuildResult result = runGradleAndExpectFailure(projectDirectoryPath, - FrontendGradlePlugin.RESOLVE_PACKAGE_MANAGER_TASK_NAME); + final BuildResult result = runGradleAndExpectFailure(projectDirectoryPath, RESOLVE_PACKAGE_MANAGER_TASK_NAME); - assertTaskOutcomes(result, PluginTaskOutcome.SKIPPED, PluginTaskOutcome.FAILED); - assertThat(projectDirectoryPath.resolve(Paths.get(FrontendGradlePlugin.DEFAULT_CACHE_DIRECTORY_NAME, - FrontendGradlePlugin.RESOLVE_PACKAGE_MANAGER_TASK_NAME, - FrontendGradlePlugin.PACKAGE_MANAGER_SPECIFICATION_FILE_NAME))).doesNotExist(); - assertThat(projectDirectoryPath.resolve(Paths.get(FrontendGradlePlugin.DEFAULT_CACHE_DIRECTORY_NAME, - FrontendGradlePlugin.RESOLVE_PACKAGE_MANAGER_TASK_NAME, - FrontendGradlePlugin.PACKAGE_MANAGER_EXECUTABLE_PATH_FILE_NAME))).doesNotExist(); + assertTaskOutcomes(result, SKIPPED, FAILED); + assertThat(projectDirectoryPath.resolve( + Paths.get(DEFAULT_CACHE_DIRECTORY_NAME, RESOLVE_PACKAGE_MANAGER_TASK_NAME, + PACKAGE_MANAGER_SPECIFICATION_FILE_NAME))).doesNotExist(); + assertThat(projectDirectoryPath.resolve( + Paths.get(DEFAULT_CACHE_DIRECTORY_NAME, RESOLVE_PACKAGE_MANAGER_TASK_NAME, + PACKAGE_MANAGER_EXECUTABLE_PATH_FILE_NAME))).doesNotExist(); } @Test @@ -76,16 +92,15 @@ void should_fail_when_package_manager_property_is_invalid_in_package_json_file() .nodeInstallDirectory(getResourcePath("node-dist-provided")) .toMap()); - final BuildResult result = runGradleAndExpectFailure(projectDirectoryPath, - FrontendGradlePlugin.RESOLVE_PACKAGE_MANAGER_TASK_NAME); + final BuildResult result = runGradleAndExpectFailure(projectDirectoryPath, RESOLVE_PACKAGE_MANAGER_TASK_NAME); - assertTaskOutcomes(result, PluginTaskOutcome.SKIPPED, PluginTaskOutcome.FAILED); - assertThat(projectDirectoryPath.resolve(Paths.get(FrontendGradlePlugin.DEFAULT_CACHE_DIRECTORY_NAME, - FrontendGradlePlugin.RESOLVE_PACKAGE_MANAGER_TASK_NAME, - FrontendGradlePlugin.PACKAGE_MANAGER_SPECIFICATION_FILE_NAME))).doesNotExist(); - assertThat(projectDirectoryPath.resolve(Paths.get(FrontendGradlePlugin.DEFAULT_CACHE_DIRECTORY_NAME, - FrontendGradlePlugin.RESOLVE_PACKAGE_MANAGER_TASK_NAME, - FrontendGradlePlugin.PACKAGE_MANAGER_EXECUTABLE_PATH_FILE_NAME))).doesNotExist(); + assertTaskOutcomes(result, SKIPPED, FAILED); + assertThat(projectDirectoryPath.resolve( + Paths.get(DEFAULT_CACHE_DIRECTORY_NAME, RESOLVE_PACKAGE_MANAGER_TASK_NAME, + PACKAGE_MANAGER_SPECIFICATION_FILE_NAME))).doesNotExist(); + assertThat(projectDirectoryPath.resolve( + Paths.get(DEFAULT_CACHE_DIRECTORY_NAME, RESOLVE_PACKAGE_MANAGER_TASK_NAME, + PACKAGE_MANAGER_EXECUTABLE_PATH_FILE_NAME))).doesNotExist(); } @Test @@ -96,25 +111,21 @@ void should_pass_when_package_manager_property_is_valid() throws IOException { .nodeDistributionUrl(getResourceUrl("node-v18.17.1.zip")) .toMap()); - final BuildResult result1 = runGradle(projectDirectoryPath, - FrontendGradlePlugin.RESOLVE_PACKAGE_MANAGER_TASK_NAME); + final BuildResult result1 = runGradle(projectDirectoryPath, RESOLVE_PACKAGE_MANAGER_TASK_NAME); - assertTaskOutcomes(result1, PluginTaskOutcome.SUCCESS, PluginTaskOutcome.SUCCESS); + assertTaskOutcomes(result1, SUCCESS, SUCCESS); final Path packageManagerNameFilePath = projectDirectoryPath.resolve( - Paths.get(FrontendGradlePlugin.DEFAULT_CACHE_DIRECTORY_NAME, - FrontendGradlePlugin.RESOLVE_PACKAGE_MANAGER_TASK_NAME, - FrontendGradlePlugin.PACKAGE_MANAGER_SPECIFICATION_FILE_NAME)); + Paths.get(DEFAULT_CACHE_DIRECTORY_NAME, RESOLVE_PACKAGE_MANAGER_TASK_NAME, + PACKAGE_MANAGER_SPECIFICATION_FILE_NAME)); assertThat(packageManagerNameFilePath).content(StandardCharsets.UTF_8).isEqualTo("npm@9.6.7"); final Path packageManagerExecutablePathFilePath = projectDirectoryPath.resolve( - Paths.get(FrontendGradlePlugin.DEFAULT_CACHE_DIRECTORY_NAME, - FrontendGradlePlugin.RESOLVE_PACKAGE_MANAGER_TASK_NAME, - FrontendGradlePlugin.PACKAGE_MANAGER_EXECUTABLE_PATH_FILE_NAME)); + Paths.get(DEFAULT_CACHE_DIRECTORY_NAME, RESOLVE_PACKAGE_MANAGER_TASK_NAME, + PACKAGE_MANAGER_EXECUTABLE_PATH_FILE_NAME)); assertThat(packageManagerExecutablePathFilePath).exists(); - final BuildResult result2 = runGradle(projectDirectoryPath, - FrontendGradlePlugin.RESOLVE_PACKAGE_MANAGER_TASK_NAME); + final BuildResult result2 = runGradle(projectDirectoryPath, RESOLVE_PACKAGE_MANAGER_TASK_NAME); - assertTaskOutcomes(result2, PluginTaskOutcome.UP_TO_DATE, PluginTaskOutcome.UP_TO_DATE); + assertTaskOutcomes(result2, UP_TO_DATE, UP_TO_DATE); assertThat(packageManagerNameFilePath).content(StandardCharsets.UTF_8).isEqualTo("npm@9.6.7"); assertThat(packageManagerExecutablePathFilePath).exists(); } diff --git a/plugin/src/intTest/java/org/siouan/frontendgradleplugin/test/GradleHelper.java b/plugin/src/intTest/java/org/siouan/frontendgradleplugin/test/GradleHelper.java index 28f92a76..f58c11dd 100644 --- a/plugin/src/intTest/java/org/siouan/frontendgradleplugin/test/GradleHelper.java +++ b/plugin/src/intTest/java/org/siouan/frontendgradleplugin/test/GradleHelper.java @@ -16,7 +16,7 @@ */ public final class GradleHelper { - private static final String MINIMAL_GRADLE_VERSION = "7.3"; + private static final String MINIMAL_GRADLE_VERSION = "8.5"; private GradleHelper() { } diff --git a/plugin/src/intTest/resources/node-dist-without-corepack/bin/node b/plugin/src/intTest/resources/node-dist-without-corepack/bin/node deleted file mode 100644 index b9b6f161..00000000 --- a/plugin/src/intTest/resources/node-dist-without-corepack/bin/node +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -echo "$0 $*" - diff --git a/plugin/src/intTest/resources/node-dist-without-corepack/bin/npm b/plugin/src/intTest/resources/node-dist-without-corepack/bin/npm deleted file mode 100644 index 41de4b4e..00000000 --- a/plugin/src/intTest/resources/node-dist-without-corepack/bin/npm +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env node - diff --git a/plugin/src/intTest/resources/node-dist-without-corepack/bin/npx b/plugin/src/intTest/resources/node-dist-without-corepack/bin/npx deleted file mode 100644 index 41de4b4e..00000000 --- a/plugin/src/intTest/resources/node-dist-without-corepack/bin/npx +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env node - diff --git a/plugin/src/intTest/resources/node-dist-without-corepack/bin/pnpm b/plugin/src/intTest/resources/node-dist-without-corepack/bin/pnpm deleted file mode 100644 index 41de4b4e..00000000 --- a/plugin/src/intTest/resources/node-dist-without-corepack/bin/pnpm +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env node - diff --git a/plugin/src/intTest/resources/node-dist-without-corepack/bin/pnpx b/plugin/src/intTest/resources/node-dist-without-corepack/bin/pnpx deleted file mode 100644 index 41de4b4e..00000000 --- a/plugin/src/intTest/resources/node-dist-without-corepack/bin/pnpx +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env node - diff --git a/plugin/src/intTest/resources/node-dist-without-corepack/bin/yarn b/plugin/src/intTest/resources/node-dist-without-corepack/bin/yarn deleted file mode 100644 index 41de4b4e..00000000 --- a/plugin/src/intTest/resources/node-dist-without-corepack/bin/yarn +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env node - diff --git a/plugin/src/intTest/resources/node-dist-without-corepack/node.exe b/plugin/src/intTest/resources/node-dist-without-corepack/node.exe deleted file mode 100644 index 01ad30e9a0765e24a129e7dbfb61fcc042dce81c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40766 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 Xd8R<1_b@G3j { */ private static final String TASK_GROUP = "Frontend"; + public static final String BEAN_REGISTRY_BUILD_SERVICE_NAME_PREFIX = "beanRegistryBuildService"; + @Override public void apply(final Project project) { project.getPluginManager().apply(BasePlugin.class); project.getPluginManager().apply(PublishingPlugin.class); - final SystemExtension systemExtension = new SystemExtension(project.getProviders()); final FrontendExtension frontendExtension = project .getExtensions() .create(EXTENSION_NAME, FrontendExtension.class, project.getObjects()); @@ -223,82 +223,78 @@ public void apply(final Project project) { frontendExtension .getCacheDirectory() .convention(project.getLayout().getProjectDirectory().dir(DEFAULT_CACHE_DIRECTORY_NAME)); - frontendExtension - .getInternalPackageManagerSpecificationFile() - .convention(frontendExtension - .getCacheDirectory() - .dir(RESOLVE_PACKAGE_MANAGER_TASK_NAME) - .map(directory -> directory.file(PACKAGE_MANAGER_SPECIFICATION_FILE_NAME))); - frontendExtension - .getInternalPackageManagerExecutablePathFile() - .convention(frontendExtension - .getCacheDirectory() - .dir(RESOLVE_PACKAGE_MANAGER_TASK_NAME) - .map(directory -> directory.file(PACKAGE_MANAGER_EXECUTABLE_PATH_FILE_NAME))); frontendExtension.getVerboseModeEnabled().convention(false); // Create bean registry and register concrete base implementations. - final String beanRegistryId = configureBeanRegistry(project, systemExtension, frontendExtension); + final Provider beanRegistryBuildServiceProvider = configureBeanRegistry(project, + Set.of(GradleSettings.ofProject(project), GradleLoggerAdapter.class, FileManagerImpl.class, + ChannelProviderImpl.class, ArchiverProviderImpl.class, HttpClientProviderImpl.class)); // Create and configure all tasks. - configureTasks(project, beanRegistryId); + configureTasks(project, beanRegistryBuildServiceProvider, frontendExtension, + new SystemProviders(project.getProviders())); } /** * Creates a bean registry and registers concrete implementations for injection. * * @param project Project. - * @param systemExtension Extension providing system properties. - * @param frontendExtension Extension providing frontend properties. - * @return ID of the bean registry. - */ - protected String configureBeanRegistry(final Project project, final SystemExtension systemExtension, - final FrontendExtension frontendExtension) { - final String beanRegistryId = Beans.getBeanRegistryId(project.getLayout().getProjectDirectory().toString()); - final SystemSettingsProvider systemSettingsProvider = new SystemSettingsProviderImpl(systemExtension, - DEFAULT_HTTP_PROXY_PORT, DEFAULT_HTTPS_PROXY_PORT); - final PlatformProvider platformProvider = new PlatformProviderImpl(systemSettingsProvider); - final GradleSettings gradleSettings = new GradleSettings(project.getLogging().getLevel(), - project.getGradle().getStartParameter().getLogLevel()); - Beans.initBeanRegistry(beanRegistryId); - Beans.registerBean(beanRegistryId, frontendExtension); - Beans.registerBean(beanRegistryId, systemSettingsProvider); - Beans.registerBean(beanRegistryId, platformProvider); - Beans.registerBean(beanRegistryId, gradleSettings); - Beans.registerBean(beanRegistryId, GradleLoggerAdapter.class); - Beans.registerBean(beanRegistryId, TaskLoggerConfigurer.class); - Beans.registerBean(beanRegistryId, FileManagerImpl.class); - Beans.registerBean(beanRegistryId, ChannelProviderImpl.class); - Beans.registerBean(beanRegistryId, ArchiverProviderImpl.class); - Beans.registerBean(beanRegistryId, HttpClientProviderImpl.class); - return beanRegistryId; + * @param beanClassesOrInstances Classes or instances of bean to register. + * @return Provider of the bean registry. + */ + protected Provider configureBeanRegistry(final Project project, + final Set beanClassesOrInstances) { + final BeanRegistry beanRegistry = new BeanRegistry(); + beanClassesOrInstances.forEach(beanClassOrInstance -> { + if (beanClassOrInstance instanceof Class beanClass) { + beanRegistry.registerBeanClass(beanClass); + } else { + beanRegistry.registerBean(beanClassOrInstance); + } + }); + return project + .getGradle() + .getSharedServices() + .registerIfAbsent(BEAN_REGISTRY_BUILD_SERVICE_NAME_PREFIX + project.getLayout().getProjectDirectory(), BeanRegistryBuildService.class, + buildServiceSpec -> buildServiceSpec.getParameters().getBeanRegistry().set(beanRegistry)); } /** * Registers plugin tasks. * * @param project Project. - * @param beanRegistryId Bean registry ID. + * @param beanRegistryBuildServiceProvider Bean registry build service provider. + * @param frontendExtension Frontend extension. + * @param systemProviders Providers of system properties. */ - protected void configureTasks(final Project project, final String beanRegistryId) { + protected void configureTasks(final Project project, + final Provider beanRegistryBuildServiceProvider, + final FrontendExtension frontendExtension, final SystemProviders systemProviders) { final TaskContainer taskContainer = project.getTasks(); - final FrontendExtension frontendExtension = getBeanOrFail(beanRegistryId, FrontendExtension.class); taskContainer.register(INSTALL_NODE_TASK_NAME, InstallNodeTask.class, - task -> configureInstallNodeTask(task, beanRegistryId, frontendExtension)); + task -> configureInstallNodeTask(task, beanRegistryBuildServiceProvider, frontendExtension, + systemProviders)); taskContainer.register(RESOLVE_PACKAGE_MANAGER_TASK_NAME, ResolvePackageManagerTask.class, - task -> configureResolvePackageManagerTask(task, taskContainer, frontendExtension)); + task -> configureResolvePackageManagerTask(task, taskContainer, beanRegistryBuildServiceProvider, + frontendExtension, systemProviders)); taskContainer.register(INSTALL_PACKAGE_MANAGER_TASK_NAME, InstallPackageManagerTask.class, - task -> configureInstallPackageManagerTask(task, beanRegistryId, taskContainer, frontendExtension)); + task -> configureInstallPackageManagerTask(task, taskContainer, beanRegistryBuildServiceProvider, + frontendExtension, systemProviders)); taskContainer.register(INSTALL_FRONTEND_TASK_NAME, InstallFrontendTask.class, - task -> configureInstallFrontendTask(task, beanRegistryId, taskContainer, frontendExtension)); + task -> configureInstallFrontendTask(task, taskContainer, beanRegistryBuildServiceProvider, + frontendExtension, systemProviders)); taskContainer.register(CLEAN_TASK_NAME, CleanTask.class, - task -> configureCleanTask(task, beanRegistryId, taskContainer, frontendExtension)); + task -> configureCleanTask(task, taskContainer, beanRegistryBuildServiceProvider, frontendExtension, + systemProviders)); taskContainer.register(CHECK_TASK_NAME, CheckTask.class, - task -> configureCheckTask(task, beanRegistryId, taskContainer, frontendExtension)); + task -> configureCheckTask(task, taskContainer, beanRegistryBuildServiceProvider, frontendExtension, + systemProviders)); taskContainer.register(ASSEMBLE_TASK_NAME, AssembleTask.class, - task -> configureAssembleTask(task, beanRegistryId, taskContainer, frontendExtension)); + task -> configureAssembleTask(task, taskContainer, beanRegistryBuildServiceProvider, frontendExtension, + systemProviders)); taskContainer.register(PUBLISH_TASK_NAME, PublishTask.class, - task -> configurePublishTask(task, beanRegistryId, taskContainer, frontendExtension)); + task -> configurePublishTask(task, taskContainer, beanRegistryBuildServiceProvider, frontendExtension, + systemProviders)); // Configure dependencies with Gradle built-in tasks. configureDependency(taskContainer, GRADLE_CLEAN_TASK_NAME, CLEAN_TASK_NAME, CleanTask.class); @@ -311,13 +307,17 @@ protected void configureTasks(final Project project, final String beanRegistryId * Configures the given task with the plugin extension. * * @param task Task. - * @param beanRegistryId Bean registry ID. + * @param beanRegistryBuildServiceProvider Bean registry build service provider. * @param frontendExtension Plugin extension. */ - protected void configureInstallNodeTask(final InstallNodeTask task, final String beanRegistryId, - final FrontendExtension frontendExtension) { + protected void configureInstallNodeTask(final InstallNodeTask task, + final Provider beanRegistryBuildServiceProvider, + final FrontendExtension frontendExtension, final SystemProviders systemProviders) { task.setGroup(TASK_GROUP); task.setDescription("Downloads and installs a Node.js distribution."); + task.getBeanRegistryBuildService().set(beanRegistryBuildServiceProvider); + task.usesService(beanRegistryBuildServiceProvider); + task.getNodeVersion().set(frontendExtension.getNodeVersion()); task.getNodeDistributionUrlRoot().set(frontendExtension.getNodeDistributionUrlRoot()); task.getNodeDistributionUrlPathPattern().set(frontendExtension.getNodeDistributionUrlPathPattern()); @@ -336,18 +336,53 @@ protected void configureInstallNodeTask(final InstallNodeTask task, final String .getNodeExecutableFile() .fileProvider(frontendExtension .getNodeInstallDirectory() - .map(directory -> getBeanOrFail(beanRegistryId, ResolveNodeExecutablePath.class) - .execute(ResolveExecutablePathCommand - .builder() - .nodeInstallDirectoryPath(directory.getAsFile().toPath()) - .platform(getBeanOrFail(beanRegistryId, PlatformProvider.class).getPlatform()) - .build()) - .toFile())); + .flatMap(directory -> beanRegistryBuildServiceProvider.map(beanRegistryBuildService -> { + final BeanRegistry beanRegistry = beanRegistryBuildService.getBeanRegistry(); + return getBeanOrFail(beanRegistry, ResolveNodeExecutablePath.class) + .execute(ResolveExecutablePathCommand + .builder() + .nodeInstallDirectoryPath(directory.getAsFile().toPath()) + .platform(Platform + .builder() + .jvmArch(systemProviders.getJvmArch().get()) + .osName(systemProviders.getOsName().get()) + .build()) + .build()) + .toFile(); + }))); task.getMaxDownloadAttempts().set(frontendExtension.getMaxDownloadAttempts()); task.getRetryHttpStatuses().set(frontendExtension.getRetryHttpStatuses()); task.getRetryInitialIntervalMs().set(frontendExtension.getRetryInitialIntervalMs()); task.getRetryIntervalMultiplier().set(frontendExtension.getRetryIntervalMultiplier()); task.getRetryMaxIntervalMs().set(frontendExtension.getRetryMaxIntervalMs()); + task.getVerboseModeEnabled().set(frontendExtension.getVerboseModeEnabled()); + task.getSystemHttpProxyHost().set(systemProviders.getHttpProxyHost()); + task + .getSystemHttpProxyPort() + .set(systemProviders + .getHttpProxyPort() + .filter(port -> !port.isBlank()) + .map(Integer::parseInt) + .orElse(DEFAULT_HTTP_PROXY_PORT)); + task.getSystemHttpsProxyHost().set(systemProviders.getHttpsProxyHost()); + task + .getSystemHttpsProxyPort() + .set(systemProviders + .getHttpsProxyPort() + .filter(port -> !port.isBlank()) + .map(Integer::parseInt) + .orElse(DEFAULT_HTTPS_PROXY_PORT)); + task + .getSystemNonProxyHosts() + .set(systemProviders + .getNonProxyHosts() + .filter(nonProxyHosts -> !nonProxyHosts.isBlank()) + .map(nonProxyHosts -> nonProxyHosts.split(SystemProperties.NON_PROXY_HOSTS_SPLIT_PATTERN)) + .map(Set::of) + // https://github.com/gradle/gradle/issues/26942 + // Do not use JDK 11 method "Set.of" that is not serialized correctly by Gradle + .orElse(Collections.emptySet())); + bindSystemArchPropertiesToTaskInputs(systemProviders, task.getSystemJvmArch(), task.getSystemOsName()); task.setOnlyIf(t -> !frontendExtension.getNodeDistributionProvided().get()); } @@ -356,23 +391,39 @@ protected void configureInstallNodeTask(final InstallNodeTask task, final String * * @param task Task. * @param taskContainer Gradle task container. + * @param beanRegistryBuildServiceProvider Bean registry build service provider. * @param frontendExtension Plugin extension. */ protected void configureResolvePackageManagerTask(final ResolvePackageManagerTask task, - final TaskContainer taskContainer, final FrontendExtension frontendExtension) { + final TaskContainer taskContainer, final Provider beanRegistryBuildServiceProvider, + final FrontendExtension frontendExtension, final SystemProviders systemProviders) { task.setGroup(TASK_GROUP); task.setDescription("Resolves the package manager."); - final Provider packageJsonFileProvider = frontendExtension - .getPackageJsonDirectory() - .file(PACKAGE_JSON_FILE_NAME) - .map(RegularFile::getAsFile); - task.getPackageJsonFile().fileProvider(packageJsonFileProvider); + task.getBeanRegistryBuildService().set(beanRegistryBuildServiceProvider); + task.usesService(beanRegistryBuildServiceProvider); + + task + .getPackageJsonFile() + .fileProvider(frontendExtension + .getPackageJsonDirectory() + .file(PACKAGE_JSON_FILE_NAME) + .map(RegularFile::getAsFile) + .filter(packageJsonFile -> Files.isRegularFile(packageJsonFile.toPath()))); task.getNodeInstallDirectory().set(frontendExtension.getNodeInstallDirectory().getAsFile()); - task.getPackageManagerSpecificationFile().set(frontendExtension.getInternalPackageManagerSpecificationFile()); - task.getPackageManagerExecutablePathFile().set(frontendExtension.getInternalPackageManagerExecutablePathFile()); - // The task is skipped when there's no package.json file. It allows to define a project that installs only a - // Node.js distribution. - task.setOnlyIf(t -> packageJsonFileProvider.map(File::toPath).map(Files::isRegularFile).getOrElse(false)); + task + .getPackageManagerSpecificationFile() + .set(frontendExtension + .getCacheDirectory() + .dir(RESOLVE_PACKAGE_MANAGER_TASK_NAME) + .map(directory -> directory.file(PACKAGE_MANAGER_SPECIFICATION_FILE_NAME))); + task + .getPackageManagerExecutablePathFile() + .set(frontendExtension + .getCacheDirectory() + .dir(RESOLVE_PACKAGE_MANAGER_TASK_NAME) + .map(directory -> directory.file(PACKAGE_MANAGER_EXECUTABLE_PATH_FILE_NAME))); + task.getVerboseModeEnabled().set(frontendExtension.getVerboseModeEnabled()); + bindSystemArchPropertiesToTaskInputs(systemProviders, task.getSystemJvmArch(), task.getSystemOsName()); configureDependency(taskContainer, task, INSTALL_NODE_TASK_NAME, InstallNodeTask.class); } @@ -381,25 +432,20 @@ protected void configureResolvePackageManagerTask(final ResolvePackageManagerTas * Configures the given task with the plugin extension. * * @param task Task. - * @param beanRegistryId Bean registry ID. * @param taskContainer Task container. + * @param beanRegistryBuildServiceProvider Bean registry build service provider. * @param frontendExtension Plugin extension. */ - protected void configureInstallPackageManagerTask(final InstallPackageManagerTask task, final String beanRegistryId, - final TaskContainer taskContainer, final FrontendExtension frontendExtension) { + protected void configureInstallPackageManagerTask(final InstallPackageManagerTask task, + final TaskContainer taskContainer, final Provider beanRegistryBuildServiceProvider, + final FrontendExtension frontendExtension, final SystemProviders systemProviders) { task.setGroup(TASK_GROUP); task.setDescription("Installs the package manager."); + task.getBeanRegistryBuildService().set(beanRegistryBuildServiceProvider); + task.usesService(beanRegistryBuildServiceProvider); + task.getPackageJsonDirectory().set(frontendExtension.getPackageJsonDirectory().getAsFile()); - task.getNodeInstallDirectory().set(frontendExtension.getNodeInstallDirectory().map(nodeInstallDirectory -> { - final Path nodeInstallDirectoryPath = nodeInstallDirectory.getAsFile().toPath(); - if (!Files.isDirectory(nodeInstallDirectoryPath)) { - // Performing this verification ahead of time avoids automatic creation of any parent directory by - // Gradle if the task is not skipped. This may be the case if the Node.js distribution is provided, but - // the install directory is not correctly configured and points to nowhere. - throw new GradleException("Node.js install directory not found: " + nodeInstallDirectoryPath); - } - return nodeInstallDirectory.getAsFile(); - })); + task.getNodeInstallDirectory().set(frontendExtension.getNodeInstallDirectory().getAsFile()); final Provider resolvePackageManagerTaskProvider = taskContainer.named( RESOLVE_PACKAGE_MANAGER_TASK_NAME, ResolvePackageManagerTask.class); task @@ -418,9 +464,10 @@ protected void configureInstallPackageManagerTask(final InstallPackageManagerTas // touched by Gradle. return null; } + final BeanRegistry beanRegistry = beanRegistryBuildServiceProvider.get().getBeanRegistry(); try { return Paths - .get(getBeanOrFail(beanRegistryId, FileManager.class).readString(filePath, + .get(getBeanOrFail(beanRegistry, FileManager.class).readString(filePath, StandardCharsets.UTF_8)) .toFile(); } catch (final IOException e) { @@ -428,26 +475,38 @@ protected void configureInstallPackageManagerTask(final InstallPackageManagerTas "Cannot read path to package manager executable from file: " + filePath, e); } })); - task.setOnlyIf(t -> isTaskExecuted(taskContainer, RESOLVE_PACKAGE_MANAGER_TASK_NAME)); + task.getVerboseModeEnabled().set(frontendExtension.getVerboseModeEnabled()); + bindSystemArchPropertiesToTaskInputs(systemProviders, task.getSystemJvmArch(), task.getSystemOsName()); + // The task is skipped when there's no package.json file. It allows to define a project that installs only a + // Node.js distribution. + task.setOnlyIf(t -> packageJsonFileExists(frontendExtension)); } /** * Configures the given task with the plugin extension. * * @param task Task. - * @param beanRegistryId Bean registry ID. * @param taskContainer Task container. + * @param beanRegistryBuildServiceProvider Bean registry build service provider. * @param frontendExtension Plugin extension. */ - protected void configureInstallFrontendTask(final InstallFrontendTask task, final String beanRegistryId, - final TaskContainer taskContainer, final FrontendExtension frontendExtension) { + protected void configureInstallFrontendTask(final InstallFrontendTask task, final TaskContainer taskContainer, + final Provider beanRegistryBuildServiceProvider, + final FrontendExtension frontendExtension, final SystemProviders systemProviders) { task.setGroup(TASK_GROUP); task.setDescription("Installs frontend dependencies."); + task.getBeanRegistryBuildService().set(beanRegistryBuildServiceProvider); + task.usesService(beanRegistryBuildServiceProvider); + + final BeanRegistry beanRegistry = beanRegistryBuildServiceProvider.get().getBeanRegistry(); task.getPackageJsonDirectory().set(frontendExtension.getPackageJsonDirectory().getAsFile()); task.getNodeInstallDirectory().set(frontendExtension.getNodeInstallDirectory().getAsFile()); - task.getExecutableType().set(getExecutableType(taskContainer, beanRegistryId)); + task.getExecutableType().set(getExecutableType(taskContainer, beanRegistry)); task.getInstallScript().set(frontendExtension.getInstallScript()); - task.setOnlyIf(t -> isTaskExecuted(taskContainer, INSTALL_PACKAGE_MANAGER_TASK_NAME)); + task.getVerboseModeEnabled().set(frontendExtension.getVerboseModeEnabled()); + bindSystemArchPropertiesToTaskInputs(systemProviders, task.getSystemJvmArch(), task.getSystemOsName()); + task.setOnlyIf(t -> packageJsonFileExists(frontendExtension)); + configureDependency(taskContainer, task, INSTALL_PACKAGE_MANAGER_TASK_NAME, InstallPackageManagerTask.class); } @@ -455,21 +514,27 @@ protected void configureInstallFrontendTask(final InstallFrontendTask task, fina * Configures the given task with the plugin extension. * * @param task Task. - * @param beanRegistryId Bean registry ID. * @param taskContainer Task container. + * @param beanRegistryBuildServiceProvider Bean registry build service provider. * @param frontendExtension Plugin extension. */ - protected void configureCleanTask(final CleanTask task, final String beanRegistryId, - final TaskContainer taskContainer, final FrontendExtension frontendExtension) { + protected void configureCleanTask(final CleanTask task, final TaskContainer taskContainer, + final Provider beanRegistryBuildServiceProvider, + final FrontendExtension frontendExtension, final SystemProviders systemProviders) { task.setGroup(TASK_GROUP); task.setDescription("Cleans frontend resources outside the build directory by running a specific script."); + task.getBeanRegistryBuildService().set(beanRegistryBuildServiceProvider); + task.usesService(beanRegistryBuildServiceProvider); + + final BeanRegistry beanRegistry = beanRegistryBuildServiceProvider.get().getBeanRegistry(); task.getPackageJsonDirectory().set(frontendExtension.getPackageJsonDirectory().getAsFile()); task.getNodeInstallDirectory().set(frontendExtension.getNodeInstallDirectory().getAsFile()); - task.getExecutableType().set(getExecutableType(taskContainer, beanRegistryId)); + task.getExecutableType().set(getExecutableType(taskContainer, beanRegistry)); task.getCleanScript().set(frontendExtension.getCleanScript()); - task.setOnlyIf(t -> isTaskExecuted(taskContainer, INSTALL_FRONTEND_TASK_NAME) && frontendExtension - .getCleanScript() - .isPresent()); + task.getVerboseModeEnabled().set(frontendExtension.getVerboseModeEnabled()); + bindSystemArchPropertiesToTaskInputs(systemProviders, task.getSystemJvmArch(), task.getSystemOsName()); + task.setOnlyIf(t -> packageJsonFileExists(frontendExtension) && frontendExtension.getCleanScript().isPresent()); + configureDependency(taskContainer, task, INSTALL_FRONTEND_TASK_NAME, InstallFrontendTask.class); } @@ -477,21 +542,27 @@ protected void configureCleanTask(final CleanTask task, final String beanRegistr * Configures the given task with the plugin extension. * * @param task Task. - * @param beanRegistryId Bean registry ID. * @param taskContainer Task container. + * @param beanRegistryBuildServiceProvider Bean registry build service provider. * @param frontendExtension Plugin extension. */ - protected void configureCheckTask(final CheckTask task, final String beanRegistryId, - final TaskContainer taskContainer, final FrontendExtension frontendExtension) { + protected void configureCheckTask(final CheckTask task, final TaskContainer taskContainer, + final Provider beanRegistryBuildServiceProvider, + final FrontendExtension frontendExtension, final SystemProviders systemProviders) { task.setGroup(TASK_GROUP); task.setDescription("Checks frontend by running a specific script."); + task.getBeanRegistryBuildService().set(beanRegistryBuildServiceProvider); + task.usesService(beanRegistryBuildServiceProvider); + + final BeanRegistry beanRegistry = beanRegistryBuildServiceProvider.get().getBeanRegistry(); task.getPackageJsonDirectory().set(frontendExtension.getPackageJsonDirectory().getAsFile()); task.getNodeInstallDirectory().set(frontendExtension.getNodeInstallDirectory().getAsFile()); - task.getExecutableType().set(getExecutableType(taskContainer, beanRegistryId)); + task.getExecutableType().set(getExecutableType(taskContainer, beanRegistry)); task.getCheckScript().set(frontendExtension.getCheckScript()); - task.setOnlyIf(t -> isTaskExecuted(taskContainer, INSTALL_FRONTEND_TASK_NAME) && frontendExtension - .getCheckScript() - .isPresent()); + task.getVerboseModeEnabled().set(frontendExtension.getVerboseModeEnabled()); + bindSystemArchPropertiesToTaskInputs(systemProviders, task.getSystemJvmArch(), task.getSystemOsName()); + task.setOnlyIf(t -> packageJsonFileExists(frontendExtension) && frontendExtension.getCheckScript().isPresent()); + configureDependency(taskContainer, task, INSTALL_FRONTEND_TASK_NAME, InstallFrontendTask.class); } @@ -499,21 +570,28 @@ protected void configureCheckTask(final CheckTask task, final String beanRegistr * Configures the given task with the plugin extension. * * @param task Task. - * @param beanRegistryId Bean registry ID. * @param taskContainer Task container. + * @param beanRegistryBuildServiceProvider Bean registry build service provider. * @param frontendExtension Plugin extension. */ - protected void configureAssembleTask(final AssembleTask task, final String beanRegistryId, - final TaskContainer taskContainer, final FrontendExtension frontendExtension) { + protected void configureAssembleTask(final AssembleTask task, final TaskContainer taskContainer, + final Provider beanRegistryBuildServiceProvider, + final FrontendExtension frontendExtension, final SystemProviders systemProviders) { task.setGroup(TASK_GROUP); task.setDescription("Assembles frontend artifacts by running a specific script."); + task.getBeanRegistryBuildService().set(beanRegistryBuildServiceProvider); + task.usesService(beanRegistryBuildServiceProvider); + + final BeanRegistry beanRegistry = beanRegistryBuildServiceProvider.get().getBeanRegistry(); task.getPackageJsonDirectory().set(frontendExtension.getPackageJsonDirectory().getAsFile()); task.getNodeInstallDirectory().set(frontendExtension.getNodeInstallDirectory().getAsFile()); - task.getExecutableType().set(getExecutableType(taskContainer, beanRegistryId)); + task.getExecutableType().set(getExecutableType(taskContainer, beanRegistry)); task.getAssembleScript().set(frontendExtension.getAssembleScript()); - task.setOnlyIf(t -> isTaskExecuted(taskContainer, INSTALL_FRONTEND_TASK_NAME) && frontendExtension - .getAssembleScript() - .isPresent()); + task.getVerboseModeEnabled().set(frontendExtension.getVerboseModeEnabled()); + bindSystemArchPropertiesToTaskInputs(systemProviders, task.getSystemJvmArch(), task.getSystemOsName()); + task.setOnlyIf( + t -> packageJsonFileExists(frontendExtension) && frontendExtension.getAssembleScript().isPresent()); + configureDependency(taskContainer, task, INSTALL_FRONTEND_TASK_NAME, InstallFrontendTask.class); } @@ -521,33 +599,42 @@ protected void configureAssembleTask(final AssembleTask task, final String beanR * Configures the given task with the plugin extension. * * @param task Task. - * @param beanRegistryId Bean registry ID. * @param taskContainer Task container. + * @param beanRegistryBuildServiceProvider Bean registry build service provider. * @param frontendExtension Plugin extension. */ - protected void configurePublishTask(final PublishTask task, final String beanRegistryId, - final TaskContainer taskContainer, final FrontendExtension frontendExtension) { + protected void configurePublishTask(final PublishTask task, final TaskContainer taskContainer, + final Provider beanRegistryBuildServiceProvider, + final FrontendExtension frontendExtension, final SystemProviders systemProviders) { task.setGroup(TASK_GROUP); task.setDescription("Publishes frontend artifacts by running a specific script."); + task.getBeanRegistryBuildService().set(beanRegistryBuildServiceProvider); + task.usesService(beanRegistryBuildServiceProvider); + + final BeanRegistry beanRegistry = beanRegistryBuildServiceProvider.get().getBeanRegistry(); task.getPackageJsonDirectory().set(frontendExtension.getPackageJsonDirectory().getAsFile()); task.getNodeInstallDirectory().set(frontendExtension.getNodeInstallDirectory().getAsFile()); - task.getExecutableType().set(getExecutableType(taskContainer, beanRegistryId)); + task.getExecutableType().set(getExecutableType(taskContainer, beanRegistry)); task.getPublishScript().set(frontendExtension.getPublishScript()); - task.setOnlyIf(t -> isTaskExecuted(taskContainer, INSTALL_FRONTEND_TASK_NAME) && frontendExtension - .getAssembleScript() - .isPresent() && frontendExtension.getPublishScript().isPresent()); + task.getVerboseModeEnabled().set(frontendExtension.getVerboseModeEnabled()); + bindSystemArchPropertiesToTaskInputs(systemProviders, task.getSystemJvmArch(), task.getSystemOsName()); + + task.setOnlyIf( + t -> packageJsonFileExists(frontendExtension) && frontendExtension.getAssembleScript().isPresent() + && frontendExtension.getPublishScript().isPresent()); configureDependency(taskContainer, task, ASSEMBLE_TASK_NAME, AssembleTask.class); } - private Provider getExecutableType(final TaskContainer taskContainer, final String beanRegistryId) { + private Provider getExecutableType(final TaskContainer taskContainer, + final BeanRegistry beanRegistry) { return taskContainer .named(RESOLVE_PACKAGE_MANAGER_TASK_NAME, ResolvePackageManagerTask.class) .flatMap(ResolvePackageManagerTask::getPackageManagerSpecificationFile) .map(f -> { final Path packageManagerSpecificationFilePath = f.getAsFile().toPath(); try { - return getBeanOrFail(beanRegistryId, ParsePackageManagerSpecification.class) - .execute(getBeanOrFail(beanRegistryId, FileManager.class).readString( + return getBeanOrFail(beanRegistry, ParsePackageManagerSpecification.class) + .execute(getBeanOrFail(beanRegistry, FileManager.class).readString( packageManagerSpecificationFilePath, StandardCharsets.UTF_8)) .type() .getExecutableType(); @@ -593,31 +680,33 @@ private void configureDependency(final TaskCont /** * Returns a bean from a registry of throws an error if no bean is registered or is instanciable. * - * @param beanRegistryId ID of the bean registry. + * @param beanRegistry Bean registry. * @param beanClass Class of the bean to be found/instanciated. * @param Type of bean. * @return The bean. */ - private T getBeanOrFail(final String beanRegistryId, final Class beanClass) { + private T getBeanOrFail(final BeanRegistry beanRegistry, final Class beanClass) { try { - return Beans.getBean(beanRegistryId, beanClass); + return beanRegistry.getBean(beanClass); } catch (final BeanInstanciationException | TooManyCandidateBeansException | ZeroOrMultiplePublicConstructorsException e) { throw new GradleException(e.getClass().getName() + ": " + e.getMessage(), e); } } - /** - * Tells whether a task has been executed in the current build. - * - * @param taskContainer Task container. - * @param taskName Task name. - * @return {@code true} if the task was executed (i.e. not skipped or up-to-date). - */ - private boolean isTaskExecuted(final TaskContainer taskContainer, final String taskName) { - return taskContainer.named(taskName).map(task -> { - final TaskState taskState = task.getState(); - return taskState.getUpToDate() || !taskState.getSkipped(); - }).getOrElse(false); + private void bindSystemArchPropertiesToTaskInputs(final SystemProviders systemProviders, + final Property systemJvmArch, final Property systemOsName) { + systemJvmArch.set(systemProviders.getJvmArch()); + systemOsName.set(systemProviders.getOsName()); + } + + private boolean packageJsonFileExists(final FrontendExtension frontendExtension) { + return frontendExtension + .getPackageJsonDirectory() + .file(PACKAGE_JSON_FILE_NAME) + .map(RegularFile::getAsFile) + .map(File::toPath) + .map(Files::isRegularFile) + .get(); } } diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/FileManager.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/FileManager.java index eb072c8f..58210d4a 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/FileManager.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/FileManager.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.Serializable; import java.nio.charset.Charset; import java.nio.file.CopyOption; import java.nio.file.OpenOption; @@ -17,7 +18,7 @@ * * @since 2.0.0 */ -public interface FileManager { +public interface FileManager extends Serializable { /** * Copies all bytes from an input stream into a file. diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/Platform.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/Platform.java index 43a55e7f..ea252b33 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/Platform.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/Platform.java @@ -30,14 +30,14 @@ public class Platform { private static final String[] SUPPORTED_WINDOWS_OS_IDS = new String[] {"windows"}; /** - * Architecture on which JVM runs. + * Architecture of the underlying JVM. */ @EqualsAndHashCode.Include @ToString.Include private final String jvmArch; /** - * Name of the underlying O/S. + * System name of the O/S. */ @EqualsAndHashCode.Include @ToString.Include diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/PlatformProvider.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/PlatformProvider.java deleted file mode 100644 index fe1cb744..00000000 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/PlatformProvider.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.siouan.frontendgradleplugin.domain; - -/** - * A provider of {@link Platform} instances. - * - * @since 7.0.0 - */ -public interface PlatformProvider { - - /** - * Gets a descriptor of the underlying platform. - * - * @return Platform. - */ - Platform getPlatform(); -} diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/ResolvePackageManager.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/ResolvePackageManager.java index b2704351..892fa8da 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/ResolvePackageManager.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/ResolvePackageManager.java @@ -32,18 +32,23 @@ public class ResolvePackageManager { public void execute(final ResolvePackageManagerCommand command) throws InvalidJsonFileException, UnsupportedPackageManagerException, IOException, MalformedPackageManagerSpecification { - final PackageManager packageManager = parsePackageManagerFromPackageJsonFile.execute( - command.packageJsonFilePath()); - fileManager.writeString(command.packageManagerSpecificationFilePath(), - packageManager.type().getPackageManagerName() + '@' + packageManager.version(), StandardCharsets.UTF_8, - StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); - fileManager.writeString(command.packageManagerExecutablePathFilePath(), getExecutablePath - .execute(GetExecutablePathCommand - .builder() - .executableType(packageManager.type().getExecutableType()) - .nodeInstallDirectoryPath(command.nodeInstallDirectoryPath()) - .platform(command.platform()) - .build()) - .toString(), StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + if (command.packageJsonFilePath() == null) { + fileManager.deleteIfExists(command.packageManagerSpecificationFilePath()); + fileManager.deleteIfExists(command.packageManagerExecutablePathFilePath()); + } else { + final PackageManager packageManager = parsePackageManagerFromPackageJsonFile.execute( + command.packageJsonFilePath()); + fileManager.writeString(command.packageManagerSpecificationFilePath(), + packageManager.type().getPackageManagerName() + '@' + packageManager.version(), StandardCharsets.UTF_8, + StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + fileManager.writeString(command.packageManagerExecutablePathFilePath(), getExecutablePath + .execute(GetExecutablePathCommand + .builder() + .executableType(packageManager.type().getExecutableType()) + .nodeInstallDirectoryPath(command.nodeInstallDirectoryPath()) + .platform(command.platform()) + .build()) + .toString(), StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + } } } diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/ResolvePackageManagerCommand.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/ResolvePackageManagerCommand.java index 3a8a4fdf..f25c5a7b 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/ResolvePackageManagerCommand.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/ResolvePackageManagerCommand.java @@ -7,7 +7,7 @@ /** * Parameters to resolve the package manager in a project. * - * @param packageJsonFilePath Path to the metadata file (i.e. {@code package.json} file). + * @param packageJsonFilePath Optional path to the metadata file (i.e. {@code package.json} file). * @param nodeInstallDirectoryPath Path to a Node.js install directory. * @param platform Underlying platform. * @param packageManagerSpecificationFilePath Path to the file providing the name of the package manager. diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/SystemProperties.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/SystemProperties.java index e0dc1a7c..ee5a5a1e 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/SystemProperties.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/SystemProperties.java @@ -8,7 +8,7 @@ @NoArgsConstructor(access = AccessLevel.PRIVATE) public final class SystemProperties { - public static final String JVM_ARCH_PROPERTY = "os.arch"; + public static final String JVM_ARCH = "os.arch"; public static final String HTTP_PROXY_HOST = "http.proxyHost"; @@ -22,5 +22,5 @@ public final class SystemProperties { public static final String NON_PROXY_HOSTS_SPLIT_PATTERN = Pattern.quote("|"); - public static final String OS_NAME_PROPERTY = "os.name"; + public static final String OS_NAME = "os.name"; } diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/SystemSettingsProvider.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/SystemSettingsProvider.java deleted file mode 100644 index af6d7199..00000000 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/SystemSettingsProvider.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.siouan.frontendgradleplugin.domain; - -import java.util.Set; - -/** - * System-wide settings provider. - * - * @since 5.2.0 - */ -public interface SystemSettingsProvider { - - /** - * Gets a list of hosts that should be reached directly, bypassing the proxy. - * - * @return Set of patterns separated by '|'. The patterns may start or end with a '*' for wildcards. Any host - * matching one of these patterns will be reached through a direct connection instead of through a proxy. - */ - Set getNonProxyHosts(); - - /** - * Gets the host name of the proxy server for HTTP requests. - * - * @return Host name. - */ - String getHttpProxyHost(); - - /** - * Gets the port number of the proxy server for HTTP requests. - * - * @return Port number. - */ - int getHttpProxyPort(); - - /** - * Gets the host name of the proxy server for HTTPS requests. - * - * @return Host name. - */ - String getHttpsProxyHost(); - - /** - * Gets the port number of the proxy server for HTTPS requests. - * - * @return Port number. - */ - int getHttpsProxyPort(); - - /** - * Gets the current JVM architecture. - * - * @return String describing the JVM architecture. - */ - String getSystemJvmArch(); - - /** - * Gets the current O/S name. - * - * @return String describing the O/S. - */ - String getSystemOsName(); -} diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/installer/ResolveProxySettingsByUrl.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/installer/ResolveProxySettingsByUrl.java index 85288c98..4912ed61 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/installer/ResolveProxySettingsByUrl.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/installer/ResolveProxySettingsByUrl.java @@ -3,7 +3,6 @@ import java.net.URL; import lombok.RequiredArgsConstructor; -import org.siouan.frontendgradleplugin.domain.SystemSettingsProvider; /** * Resolves proxy settings for a given URL. @@ -19,8 +18,6 @@ public class ResolveProxySettingsByUrl { private static final String HTTPS_PROTOCOL = "https"; - private final SystemSettingsProvider systemSettingsProvider; - private final IsNonProxyHost isNonProxyHost; private final SelectProxySettings selectProxySettings; @@ -31,7 +28,7 @@ public ProxySettings execute(final ResolveProxySettingsByUrlCommand command) { if (resourceProtocol.equals(HTTP_PROTOCOL) || resourceProtocol.equals(HTTPS_PROTOCOL)) { if (isNonProxyHost.execute(IsNonProxyHostCommand .builder() - .nonProxyHosts(systemSettingsProvider.getNonProxyHosts()) + .nonProxyHosts(command.systemNonProxyHosts()) .hostNameOrIpAddress(resourceUrl.getHost()) .build())) { return ProxySettings.NONE; @@ -39,15 +36,15 @@ public ProxySettings execute(final ResolveProxySettingsByUrlCommand command) { final SelectProxySettingsCommand.SelectProxySettingsCommandBuilder selectProxySettingsCommandBuilder = SelectProxySettingsCommand.builder(); if (resourceProtocol.equals(HTTPS_PROTOCOL)) { selectProxySettingsCommandBuilder - .systemProxyHost(systemSettingsProvider.getHttpsProxyHost()) - .systemProxyPort(systemSettingsProvider.getHttpsProxyPort()) + .systemProxyHost(command.systemHttpsProxyHost()) + .systemProxyPort(command.systemHttpsProxyPort()) .proxyHost(command.httpsProxyHost()) .proxyPort(command.httpsProxyPort()) .proxyCredentials(command.httpsProxyCredentials()); } else { selectProxySettingsCommandBuilder - .systemProxyHost(systemSettingsProvider.getHttpProxyHost()) - .systemProxyPort(systemSettingsProvider.getHttpProxyPort()) + .systemProxyHost(command.systemHttpProxyHost()) + .systemProxyPort(command.systemHttpProxyPort()) .proxyHost(command.httpProxyHost()) .proxyPort(command.httpProxyPort()) .proxyCredentials(command.httpProxyCredentials()); diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/installer/ResolveProxySettingsByUrlCommand.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/installer/ResolveProxySettingsByUrlCommand.java index b43ef3b0..848436d0 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/installer/ResolveProxySettingsByUrlCommand.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/installer/ResolveProxySettingsByUrlCommand.java @@ -1,6 +1,7 @@ package org.siouan.frontendgradleplugin.domain.installer; import java.net.URL; +import java.util.Set; import lombok.Builder; @@ -12,4 +13,5 @@ @Builder public record ResolveProxySettingsByUrlCommand(String httpProxyHost, int httpProxyPort, Credentials httpProxyCredentials, String httpsProxyHost, int httpsProxyPort, Credentials httpsProxyCredentials, - URL resourceUrl) {} + URL resourceUrl, String systemHttpProxyHost, int systemHttpProxyPort, String systemHttpsProxyHost, + int systemHttpsProxyPort, Set systemNonProxyHosts) {} diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/installer/archiver/AbstractArchiver.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/installer/archiver/AbstractArchiver.java index ed864f6b..fe595fbb 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/installer/archiver/AbstractArchiver.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/domain/installer/archiver/AbstractArchiver.java @@ -3,6 +3,8 @@ import static java.util.stream.Collectors.toSet; import java.io.IOException; +import java.io.Serial; +import java.io.Serializable; import java.nio.file.Path; import java.nio.file.attribute.PosixFilePermission; import java.util.Map; @@ -27,7 +29,11 @@ * @since 1.1.3 */ @RequiredArgsConstructor(access = AccessLevel.PROTECTED) -public abstract class AbstractArchiver implements Archiver { +public abstract class AbstractArchiver + implements Archiver, Serializable { + + @Serial + private static final long serialVersionUID = -3028639659907720045L; /** * Map of binary masks used on Unix modes to extract a single permission. diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/archiver/ArchiverProviderImpl.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/archiver/ArchiverProviderImpl.java index f4c720c3..dac4ff4b 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/archiver/ArchiverProviderImpl.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/archiver/ArchiverProviderImpl.java @@ -1,5 +1,7 @@ package org.siouan.frontendgradleplugin.infrastructure.archiver; +import java.io.Serial; +import java.io.Serializable; import java.nio.file.Path; import java.util.Map; import java.util.Optional; @@ -13,7 +15,10 @@ * * @since 1.1.3 */ -public class ArchiverProviderImpl implements ArchiverProvider { +public class ArchiverProviderImpl implements ArchiverProvider, Serializable { + + @Serial + private static final long serialVersionUID = 5967837524064117384L; /** * Map of archivers supporting a given extension. diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/archiver/TarArchiver.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/archiver/TarArchiver.java index e34ca298..48368f52 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/archiver/TarArchiver.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/archiver/TarArchiver.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.Serial; import java.nio.file.Path; import java.util.Optional; @@ -27,6 +28,9 @@ */ public class TarArchiver extends AbstractArchiver { + @Serial + private static final long serialVersionUID = -1649165974155881017L; + private final byte[] buffer; public TarArchiver(final FileManager fileManager) { @@ -81,7 +85,7 @@ TarArchiveInputStream buildLowLevelInputStream(final InputStream inputStream) { @Override protected Optional getNextEntry(final TarArchiverContext context) throws IOException { - return Optional.ofNullable(context.getInputStream().getNextTarEntry()).map(TarEntry::new); + return Optional.ofNullable(context.getInputStream().getNextEntry()).map(TarEntry::new); } @Override diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/archiver/TarEntry.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/archiver/TarEntry.java index 12b86ed9..147d9ea0 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/archiver/TarEntry.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/archiver/TarEntry.java @@ -13,7 +13,7 @@ * @since 1.1.3 */ @Builder -record TarEntry(TarArchiveEntry lowLevelEntry) implements ArchiveEntry { +public record TarEntry(TarArchiveEntry lowLevelEntry) implements ArchiveEntry { @Override public String getName() { diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/archiver/ZipArchiver.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/archiver/ZipArchiver.java index 87f9d136..879fe2b9 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/archiver/ZipArchiver.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/archiver/ZipArchiver.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.Serial; import java.nio.file.Path; import java.util.Optional; @@ -26,13 +27,17 @@ */ public class ZipArchiver extends AbstractArchiver { + @Serial + private static final long serialVersionUID = -6879163434284590850L; + public ZipArchiver(final FileManager fileManager) { super(fileManager); } @Override protected ZipArchiverContext initializeContext(final ExplodeCommand explodeCommand) throws IOException { - return new ZipArchiverContext(explodeCommand, new ZipFile(explodeCommand.getArchiveFilePath().toFile())); + return new ZipArchiverContext(explodeCommand, + ZipFile.builder().setFile(explodeCommand.getArchiveFilePath().toFile()).get()); } @Override diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/bean/BeanRegistry.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/bean/BeanRegistry.java index 0f002666..aaa4effd 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/bean/BeanRegistry.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/bean/BeanRegistry.java @@ -2,13 +2,19 @@ import static java.util.stream.Collectors.toSet; +import java.io.Serial; +import java.io.Serializable; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import lombok.extern.slf4j.Slf4j; + /** * This class is a simple implementation of Inversion of Control and dependency injection: it provides a minimal set of * features to ease bean instanciation and reusability, externalize instanciation flow, for the needs of the plugin @@ -16,12 +22,16 @@ * plugins with modularity. Bean classes are instanciated when needed using the one and only one public constructor * available. The registry handles recursive instanciation of constructor parameters, using reflection. However, the * registry does not provide a package-scan feature, e.g. to search for implementations of non-instanciable classes. - * Some implementations must be registered explicitly using the {@link #registerBean(Class)} method or the + * Some implementations must be registered explicitly using the {@link #registerBeanClass(Class)} method or the * {@link #registerBean(Object)} method. The registry handles singleton instances only. * * @since 2.0.0 */ -public class BeanRegistry { +@Slf4j +public class BeanRegistry implements Serializable { + + @Serial + private static final long serialVersionUID = 8882478289468756368L; /** * Set of types available for injection. When a bean is instanciated, it is placed in the map of {@link #singletons} @@ -40,7 +50,9 @@ public class BeanRegistry { * Builds an initializes the registry with {@link #init()}. */ public BeanRegistry() { - this.registeredBeanTypes = ConcurrentHashMap.newKeySet(); + // Do not use method "ConcurrentHashMap.newKeySet()" since serialization of class KeySetView requires + // '--add-opens' JVM arg. + this.registeredBeanTypes = Collections.newSetFromMap(new ConcurrentHashMap<>()); this.singletons = new ConcurrentHashMap<>(); init(); } @@ -75,18 +87,41 @@ public void init() { */ public T getBean(final Class beanClass) throws BeanInstanciationException, TooManyCandidateBeansException, ZeroOrMultiplePublicConstructorsException { + LOGGER.debug("Requesting: {}", beanClass.getSimpleName()); + return getBean(beanClass, 0); + } + + /** + * Gets an instance of a bean of the given class. If no instance is found, the class is registered and a new + * instance is created and returned. If no bean with the exact same class is registered, this method looks up for an + * assignable child class registered. + * + * @param beanClass Bean class. + * @param Type of bean. + * @return Bean. + * @throws ZeroOrMultiplePublicConstructorsException If the bean class does not contain one and only one public + * constructor, or if a parameter class in the public constructor does not match the same requirement. + * @throws TooManyCandidateBeansException If multiple instances of the bean class are available in the registry, + * generally because these instances are child classes of the bean class. + * @throws BeanInstanciationException If the bean cannot be instanciated. + */ + private T getBean(final Class beanClass, final int nestedLevel) + throws BeanInstanciationException, TooManyCandidateBeansException, ZeroOrMultiplePublicConstructorsException { + final String logEnteringPrefix = " ".repeat(nestedLevel + 1); final T existingBean = (T) singletons.get(beanClass); if (existingBean != null) { + LOGGER.debug("{}< Found: {}", logEnteringPrefix, beanClass.getSimpleName()); return existingBean; } final Class assignableClass = getAssignableClass(beanClass); if (assignableClass != null) { - return getBean(assignableClass); + return getBean(assignableClass, nestedLevel); } + LOGGER.debug("{}+ Instantiating: {}", logEnteringPrefix, beanClass.getSimpleName()); assertBeanClassIsInstanciable(beanClass); - final T newBean = createInstance(beanClass); + final T newBean = createInstance(beanClass, nestedLevel); registerBean(beanClass, newBean); return newBean; } @@ -99,8 +134,9 @@ public T getBean(final Class beanClass) * @param beanClass Bean class. * @param Type of bean. */ - public void registerBean(final Class beanClass) { - if (isBeanRegistered(beanClass)) { + public void registerBeanClass(final Class beanClass) { + LOGGER.debug("Registering class: {}", beanClass.getSimpleName()); + if (isBeanClassRegistered(beanClass)) { return; } assertBeanClassIsInstanciable(beanClass); @@ -116,7 +152,8 @@ public void registerBean(final Class beanClass) { */ public void registerBean(final T bean) { final Class beanClass = (Class) bean.getClass(); - if (isBeanRegistered(beanClass)) { + LOGGER.debug("Registering bean: {}", beanClass.getSimpleName()); + if (isBeanClassRegistered(beanClass)) { return; } registerBean(beanClass, bean); @@ -145,7 +182,7 @@ private void registerBean(final Class beanClass, final T bean) { * @param Type of bean. * @return {@code true} if the bean class is already registered, or is an instance of a bean registry. */ - private boolean isBeanRegistered(final Class beanClass) { + private boolean isBeanClassRegistered(final Class beanClass) { return getClass().isAssignableFrom(beanClass) || registeredBeanTypes.contains(beanClass); } @@ -202,7 +239,7 @@ private Class getAssignableClass(final Class beanClass) * @throws TooManyCandidateBeansException If multiple valid child candidates were found in the registry for a * constructor parameter. */ - private T createInstance(final Class beanClass) + private T createInstance(final Class beanClass, final int nestedLevel) throws BeanInstanciationException, ZeroOrMultiplePublicConstructorsException, TooManyCandidateBeansException { final Constructor[] constructors = beanClass.getConstructors(); if (constructors.length != 1) { @@ -212,7 +249,7 @@ private T createInstance(final Class beanClass) final Class[] parameterTypes = constructors[0].getParameterTypes(); final Object[] parameters = new Object[parameterTypes.length]; for (int i = 0; i < parameterTypes.length; i++) { - parameters[i] = getBean(parameterTypes[i]); + parameters[i] = getBean(parameterTypes[i], nestedLevel + 1); } try { return (T) beanClass.getConstructors()[0].newInstance(parameters); @@ -220,4 +257,11 @@ private T createInstance(final Class beanClass) throw new BeanInstanciationException(beanClass, e); } } + + @Override + public String toString() { + return BeanRegistry.class.getName() + " {\n" + "registeredBeanTypes=" + Arrays.toString( + registeredBeanTypes.stream().map(Class::getSimpleName).toArray()) + ", singletons=" + Arrays.toString( + singletons.keySet().stream().map(Class::getSimpleName).toArray()) + "}"; + } } diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/bean/Beans.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/bean/Beans.java deleted file mode 100644 index 468b89c2..00000000 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/bean/Beans.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.siouan.frontendgradleplugin.infrastructure.bean; - -import java.nio.charset.StandardCharsets; -import java.util.Base64; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; - -/** - * This class consists exclusively of static methods, that will delegate to a singleton bean registry instance. It - * allows to access a bean registry where dependency injection is not possible. - * - * @since 2.0.0 - */ -public final class Beans { - - private static final Beans INSTANCE = new Beans(); - - private final Map beanRegistryByIds; - - private Beans() { - beanRegistryByIds = new ConcurrentHashMap<>(); - } - - /** - * Instantiates and initializes a new bean registry. If a registry already exists with the given ID, it is reset. - * - * @param registryId Registry ID. - */ - public static void initBeanRegistry(final String registryId) { - INSTANCE - .findBeanRegistryById(registryId) - .ifPresentOrElse(BeanRegistry::init, () -> INSTANCE.addBeanRegistry(registryId, new BeanRegistry())); - } - - public static String getBeanRegistryId(final String decodedId) { - return Base64.getEncoder().encodeToString(decodedId.getBytes(StandardCharsets.UTF_8)); - } - - public static T getBean(final String registryId, final Class beanClass) - throws BeanInstanciationException, TooManyCandidateBeansException, ZeroOrMultiplePublicConstructorsException { - return INSTANCE.findBeanRegistryByIdOrFail(registryId).getBean(beanClass); - } - - public static void registerBean(final String registryId, final Class beanClass) { - INSTANCE.findBeanRegistryByIdOrFail(registryId).registerBean(beanClass); - } - - public static void registerBean(final String registryId, final T bean) { - INSTANCE.findBeanRegistryByIdOrFail(registryId).registerBean(bean); - } - - public void addBeanRegistry(final String registryId, final BeanRegistry beanRegistry) { - beanRegistryByIds.put(registryId, beanRegistry); - } - - public BeanRegistry findBeanRegistryByIdOrFail(final String registryId) { - return findBeanRegistryById(registryId).orElseThrow( - () -> new IllegalArgumentException("No registry was found with ID " + registryId)); - } - - public Optional findBeanRegistryById(final String registryId) { - return Optional.ofNullable(beanRegistryByIds.get(registryId)); - } -} diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/AbstractRunCommandTask.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/AbstractRunCommandTask.java index 8dc45c65..eed1b694 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/AbstractRunCommandTask.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/AbstractRunCommandTask.java @@ -3,7 +3,6 @@ import java.io.File; import org.gradle.api.DefaultTask; -import org.gradle.api.file.ProjectLayout; import org.gradle.api.model.ObjectFactory; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; @@ -12,9 +11,8 @@ import org.gradle.process.ExecOperations; import org.siouan.frontendgradleplugin.domain.ExecutableType; import org.siouan.frontendgradleplugin.domain.Platform; -import org.siouan.frontendgradleplugin.domain.PlatformProvider; +import org.siouan.frontendgradleplugin.infrastructure.bean.BeanRegistry; import org.siouan.frontendgradleplugin.infrastructure.bean.BeanRegistryException; -import org.siouan.frontendgradleplugin.infrastructure.bean.Beans; /** * This abstract class provides the reusable logic to run a command with an executable. Sub-classes must expose inputs @@ -25,11 +23,11 @@ public abstract class AbstractRunCommandTask extends DefaultTask { protected final ExecOperations execOperations; /** - * Bean registry ID. + * Bean registry service provider. * - * @since 5.2.0 + * @since 8.1.0 */ - protected final String beanRegistryId; + protected final Property beanRegistryBuildService; /** * Directory where the 'package.json' file is located. @@ -51,14 +49,42 @@ public abstract class AbstractRunCommandTask extends DefaultTask { */ protected final Property script; - AbstractRunCommandTask(final ProjectLayout projectLayout, final ObjectFactory objectFactory, - final ExecOperations execOperations) { + /** + * Whether the task should produce log messages for the end-user. + * + * @since 8.1.0 + */ + protected final Property verboseModeEnabled; + + /** + * Architecture of the underlying JVM. + * + * @since 8.1.0 + */ + protected final Property systemJvmArch; + + /** + * System name of the O/S. + * + * @since 8.1.0 + */ + protected final Property systemOsName; + + AbstractRunCommandTask(final ObjectFactory objectFactory, final ExecOperations execOperations) { this.execOperations = execOperations; - this.beanRegistryId = Beans.getBeanRegistryId(projectLayout.getProjectDirectory().toString()); + this.beanRegistryBuildService = objectFactory.property(BeanRegistryBuildService.class); this.packageJsonDirectory = objectFactory.property(File.class); this.nodeInstallDirectory = objectFactory.property(File.class); this.executableType = objectFactory.property(ExecutableType.class); this.script = objectFactory.property(String.class); + this.verboseModeEnabled = objectFactory.property(Boolean.class); + this.systemJvmArch = objectFactory.property(String.class); + this.systemOsName = objectFactory.property(String.class); + } + + @Internal + public Property getBeanRegistryBuildService() { + return beanRegistryBuildService; } @Internal @@ -66,6 +92,21 @@ public Property getExecutableType() { return executableType; } + @Internal + public Property getVerboseModeEnabled() { + return verboseModeEnabled; + } + + @Internal + public Property getSystemJvmArch() { + return systemJvmArch; + } + + @Internal + public Property getSystemOsName() { + return systemOsName; + } + @Input public Property getPackageJsonDirectory() { return packageJsonDirectory; @@ -98,12 +139,14 @@ protected void assertThatTaskIsRunnable() throws NonRunnableTaskException { public void execute() throws NonRunnableTaskException, BeanRegistryException { assertThatTaskIsRunnable(); - Beans.getBean(beanRegistryId, TaskLoggerConfigurer.class).initLoggerAdapter(this); + final BeanRegistry beanRegistry = beanRegistryBuildService.get().getBeanRegistry(); + TaskLoggerInitializer.initAdapter(this, verboseModeEnabled.get(), + beanRegistry.getBean(GradleLoggerAdapter.class), beanRegistry.getBean(GradleSettings.class)); - final Platform platform = Beans.getBean(beanRegistryId, PlatformProvider.class).getPlatform(); + final Platform platform = Platform.builder().jvmArch(systemJvmArch.get()).osName(systemOsName.get()).build(); getLogger().debug("Platform: {}", platform); - Beans - .getBean(beanRegistryId, GradleScriptRunnerAdapter.class) + beanRegistry + .getBean(GradleScriptRunnerAdapter.class) .execute(ScriptProperties .builder() .execOperations(execOperations) diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/AbstractRunCommandTaskType.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/AbstractRunCommandTaskType.java index 230c6625..4599b013 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/AbstractRunCommandTaskType.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/AbstractRunCommandTaskType.java @@ -1,11 +1,12 @@ package org.siouan.frontendgradleplugin.infrastructure.gradle; -import org.gradle.api.GradleException; -import org.gradle.api.file.ProjectLayout; +import static org.siouan.frontendgradleplugin.FrontendGradlePlugin.BEAN_REGISTRY_BUILD_SERVICE_NAME_PREFIX; + +import org.gradle.api.Project; import org.gradle.api.model.ObjectFactory; +import org.gradle.api.provider.Provider; +import org.gradle.api.services.BuildServiceRegistration; import org.gradle.process.ExecOperations; -import org.siouan.frontendgradleplugin.infrastructure.bean.BeanRegistryException; -import org.siouan.frontendgradleplugin.infrastructure.bean.Beans; /** * This abstract class provides the required bindings for task types, to run a command with an executable. Sub-classes @@ -13,16 +14,22 @@ */ public abstract class AbstractRunCommandTaskType extends AbstractRunCommandTask { - AbstractRunCommandTaskType(final ProjectLayout projectLayout, final ObjectFactory objectFactory, - final ExecOperations execOperations) { - super(projectLayout, objectFactory, execOperations); - final FrontendExtension frontendExtension; - try { - frontendExtension = Beans.getBean(beanRegistryId, FrontendExtension.class); - } catch (final BeanRegistryException e) { - throw new GradleException(e.getClass().getName() + ": " + e.getMessage(), e); - } - this.packageJsonDirectory.set(frontendExtension.getPackageJsonDirectory().getAsFile()); - this.nodeInstallDirectory.set(frontendExtension.getNodeInstallDirectory().getAsFile()); + AbstractRunCommandTaskType(final ObjectFactory objectFactory, final ExecOperations execOperations) { + super(objectFactory, execOperations); + final Project project = getProject(); + final FrontendExtension frontendExtension = project.getExtensions().getByType(FrontendExtension.class); + final Provider beanRegistryBuildServiceProvider = (Provider) project + .getGradle() + .getSharedServices() + .getRegistrations() + .named(BEAN_REGISTRY_BUILD_SERVICE_NAME_PREFIX + project.getLayout().getProjectDirectory()) + .flatMap(BuildServiceRegistration::getService); + beanRegistryBuildService.set(beanRegistryBuildServiceProvider); + packageJsonDirectory.set(frontendExtension.getPackageJsonDirectory().getAsFile()); + nodeInstallDirectory.set(frontendExtension.getNodeInstallDirectory().getAsFile()); + verboseModeEnabled.set(frontendExtension.getVerboseModeEnabled()); + final SystemProviders systemProviders = new SystemProviders(project.getProviders()); + systemJvmArch.set(systemProviders.getJvmArch()); + systemOsName.set(systemProviders.getOsName()); } } diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/AssembleTask.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/AssembleTask.java index 55efc3fe..ba8c2826 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/AssembleTask.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/AssembleTask.java @@ -2,7 +2,6 @@ import javax.inject.Inject; -import org.gradle.api.file.ProjectLayout; import org.gradle.api.model.ObjectFactory; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; @@ -10,14 +9,15 @@ import org.gradle.process.ExecOperations; /** - * This task assembles frontend artifacts. + * This task assembles project artifacts. + *

+ * NOTE: this task will be renamed {@code AssembleFrontendTask} to conform with naming convention of other tasks. */ public class AssembleTask extends AbstractRunCommandTask { @Inject - public AssembleTask(final ProjectLayout projectLayout, final ObjectFactory objectFactory, - final ExecOperations execOperations) { - super(projectLayout, objectFactory, execOperations); + public AssembleTask(final ObjectFactory objectFactory, final ExecOperations execOperations) { + super(objectFactory, execOperations); } @Input diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/BeanRegistryBuildService.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/BeanRegistryBuildService.java new file mode 100644 index 00000000..f5bbf1c6 --- /dev/null +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/BeanRegistryBuildService.java @@ -0,0 +1,30 @@ +package org.siouan.frontendgradleplugin.infrastructure.gradle; + +import lombok.Getter; +import org.gradle.api.provider.Property; +import org.gradle.api.services.BuildService; +import org.gradle.api.services.BuildServiceParameters; +import org.siouan.frontendgradleplugin.infrastructure.bean.BeanRegistry; + +/** + * Build service providing the project bean registry to tasks. + * + * @since 8.1.0 + */ +@Getter +public abstract class BeanRegistryBuildService implements BuildService { + + /** + * The project bean registry. + */ + private final BeanRegistry beanRegistry; + + public BeanRegistryBuildService() { + beanRegistry = getParameters().getBeanRegistry().get(); + } + + public interface Params extends BuildServiceParameters { + + Property getBeanRegistry(); + } +} diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/CheckTask.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/CheckTask.java index 42030f68..1efe1294 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/CheckTask.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/CheckTask.java @@ -2,7 +2,6 @@ import javax.inject.Inject; -import org.gradle.api.file.ProjectLayout; import org.gradle.api.model.ObjectFactory; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; @@ -10,14 +9,15 @@ import org.gradle.process.ExecOperations; /** - * This task executes frontend tests. + * This task checks the project. + *

+ * NOTE: this task will be renamed {@code CheckFrontendTask} to conform with naming convention of other tasks. */ public class CheckTask extends AbstractRunCommandTask { @Inject - public CheckTask(final ProjectLayout projectLayout, final ObjectFactory objectFactory, - final ExecOperations execOperations) { - super(projectLayout, objectFactory, execOperations); + public CheckTask(final ObjectFactory objectFactory, final ExecOperations execOperations) { + super(objectFactory, execOperations); } @Input diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/CleanTask.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/CleanTask.java index 54757025..b77c27c7 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/CleanTask.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/CleanTask.java @@ -2,7 +2,6 @@ import javax.inject.Inject; -import org.gradle.api.file.ProjectLayout; import org.gradle.api.model.ObjectFactory; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; @@ -10,14 +9,15 @@ import org.gradle.process.ExecOperations; /** - * This task cleans frontend resources, using a custom script. + * This task cleans project artifacts. + *

+ * NOTE: this task will be renamed {@code CleanFrontendTask} to conform with naming convention of other tasks. */ public class CleanTask extends AbstractRunCommandTask { @Inject - public CleanTask(final ProjectLayout projectLayout, final ObjectFactory objectFactory, - final ExecOperations execOperations) { - super(projectLayout, objectFactory, execOperations); + public CleanTask(final ObjectFactory objectFactory, final ExecOperations execOperations) { + super(objectFactory, execOperations); } @Input diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/FrontendExtension.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/FrontendExtension.java index 79c12ef9..a448457a 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/FrontendExtension.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/FrontendExtension.java @@ -2,7 +2,6 @@ import lombok.Getter; import org.gradle.api.file.DirectoryProperty; -import org.gradle.api.file.RegularFileProperty; import org.gradle.api.model.ObjectFactory; import org.gradle.api.provider.Property; import org.gradle.api.provider.SetProperty; @@ -186,24 +185,6 @@ public class FrontendExtension { */ private final DirectoryProperty cacheDirectory; - /** - * WARNING: THIS IS AN INTERNAL PROPERTY, WHICH MUST NOT BE USED/OVERRIDDEN IN GRADLE BUILD FILES. - *

File derived from the {@link #cacheDirectory} property where task "resolvePackageManager" stores the name and - * the version of the package manager.

- * - * @since 7.0.0 - */ - private final RegularFileProperty internalPackageManagerSpecificationFile; - - /** - * WARNING: THIS IS AN INTERNAL PROPERTY, WHICH MUST NOT BE USED/OVERRIDDEN IN GRADLE BUILD FILES. - *

File derived from the {@link #cacheDirectory} property where task "resolvePackageManager" stores the path of - * the package manager executable.

- * - * @since 7.0.0 - */ - private final RegularFileProperty internalPackageManagerExecutablePathFile; - /** * Whether verbose mode is enabled. * @@ -239,8 +220,6 @@ public FrontendExtension(final ObjectFactory objectFactory) { retryIntervalMultiplier = objectFactory.property(Double.class); retryMaxIntervalMs = objectFactory.property(Integer.class); cacheDirectory = objectFactory.directoryProperty(); - internalPackageManagerSpecificationFile = objectFactory.fileProperty(); - internalPackageManagerExecutablePathFile = objectFactory.fileProperty(); verboseModeEnabled = objectFactory.property(Boolean.class); } } diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/GradleLoggerAdapter.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/GradleLoggerAdapter.java index 76143ae5..2993de35 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/GradleLoggerAdapter.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/GradleLoggerAdapter.java @@ -1,5 +1,8 @@ package org.siouan.frontendgradleplugin.infrastructure.gradle; +import java.io.Serial; +import java.io.Serializable; + import org.gradle.api.logging.LogLevel; import org.siouan.frontendgradleplugin.domain.Logger; @@ -8,15 +11,18 @@ * * @since 2.0.0 */ -public class GradleLoggerAdapter implements Logger { +public class GradleLoggerAdapter implements Logger, Serializable { + + @Serial + private static final long serialVersionUID = 7961046158160963130L; - private org.gradle.api.logging.Logger gradleLogger; + private transient org.gradle.api.logging.Logger gradleLogger; private LogLevel loggingLevel; private boolean verboseModeEnabled; - private String prefix; + private transient String prefix; public GradleLoggerAdapter() { this.loggingLevel = LogLevel.LIFECYCLE; diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/GradleSettings.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/GradleSettings.java index 1dcfad63..c3bc7673 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/GradleSettings.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/GradleSettings.java @@ -1,5 +1,11 @@ package org.siouan.frontendgradleplugin.infrastructure.gradle; +import java.io.Serial; +import java.io.Serializable; + +import lombok.Builder; +import lombok.Getter; +import org.gradle.api.Project; import org.gradle.api.logging.LogLevel; /** @@ -7,4 +13,19 @@ * * @since 5.2.0 */ -public record GradleSettings(LogLevel projectLogLevel, LogLevel commandLineLogLevel) {} +@Getter +@Builder +public class GradleSettings implements Serializable { + + @Serial + private static final long serialVersionUID = 8563728350729514612L; + + private final LogLevel projectLogLevel; + + private final LogLevel commandLineLogLevel; + + public static GradleSettings ofProject(final Project project) { + return new GradleSettings(project.getLogging().getLevel(), + project.getGradle().getStartParameter().getLogLevel()); + } +} diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/InstallFrontendTask.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/InstallFrontendTask.java index fca8531e..e96f5f27 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/InstallFrontendTask.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/InstallFrontendTask.java @@ -2,7 +2,6 @@ import javax.inject.Inject; -import org.gradle.api.file.ProjectLayout; import org.gradle.api.model.ObjectFactory; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; @@ -15,9 +14,8 @@ public class InstallFrontendTask extends AbstractRunCommandTask { @Inject - public InstallFrontendTask(final ProjectLayout projectLayout, final ObjectFactory objectFactory, - final ExecOperations execOperations) { - super(projectLayout, objectFactory, execOperations); + public InstallFrontendTask(final ObjectFactory objectFactory, final ExecOperations execOperations) { + super(objectFactory, execOperations); } @Input diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/InstallNodeTask.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/InstallNodeTask.java index d17b80a2..8d3ced38 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/InstallNodeTask.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/InstallNodeTask.java @@ -6,7 +6,6 @@ import javax.inject.Inject; import org.gradle.api.DefaultTask; -import org.gradle.api.file.ProjectLayout; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.model.ObjectFactory; import org.gradle.api.provider.Property; @@ -17,7 +16,6 @@ import org.gradle.api.tasks.TaskAction; import org.siouan.frontendgradleplugin.domain.FrontendException; import org.siouan.frontendgradleplugin.domain.Platform; -import org.siouan.frontendgradleplugin.domain.PlatformProvider; import org.siouan.frontendgradleplugin.domain.UnsupportedPlatformException; import org.siouan.frontendgradleplugin.domain.installer.Credentials; import org.siouan.frontendgradleplugin.domain.installer.InstallNodeDistribution; @@ -30,8 +28,8 @@ import org.siouan.frontendgradleplugin.domain.installer.RetrySettings; import org.siouan.frontendgradleplugin.domain.installer.UnsupportedDistributionArchiveException; import org.siouan.frontendgradleplugin.domain.installer.archiver.ArchiverException; +import org.siouan.frontendgradleplugin.infrastructure.bean.BeanRegistry; import org.siouan.frontendgradleplugin.infrastructure.bean.BeanRegistryException; -import org.siouan.frontendgradleplugin.infrastructure.bean.Beans; /** * Task that downloads and installs a Node distribution. @@ -39,11 +37,11 @@ public class InstallNodeTask extends DefaultTask { /** - * Bean registry ID. + * Bean registry service provider. * - * @since 5.2.0 + * @since 8.1.0 */ - protected final String beanRegistryId; + protected final Property beanRegistryBuildService; /** * Version of the Node distribution to download. @@ -174,6 +172,13 @@ public class InstallNodeTask extends DefaultTask { */ private final Property retryIntervalMultiplier; + /** + * Whether the task should produce log messages for the end-user. + * + * @since 8.1.0 + */ + private final Property verboseModeEnabled; + /** * Maximum interval between retry attempts. * @@ -181,9 +186,58 @@ public class InstallNodeTask extends DefaultTask { */ private final Property retryMaxIntervalMs; + /** + * System-level configuration of the HTTP proxy host. + * + * @since 8.1.0 + */ + private final Property systemHttpProxyHost; + + /** + * System-level configuration of the HTTP proxy port. + * + * @since 8.1.0 + */ + private final Property systemHttpProxyPort; + + /** + * System-level configuration of the HTTPS proxy host. + * + * @since 8.1.0 + */ + private final Property systemHttpsProxyHost; + + /** + * System-level configuration of the HTTPS proxy port + * + * @since 8.1.0 + */ + private final Property systemHttpsProxyPort; + + /** + * System-level configuration of hosts for which direct connection is requested. + * + * @since 8.1.0 + */ + private final SetProperty systemNonProxyHosts; + + /** + * Architecture of the underlying JVM. + * + * @since 8.1.0 + */ + private final Property systemJvmArch; + + /** + * System name of the O/S. + * + * @since 8.1.0 + */ + private final Property systemOsName; + @Inject - public InstallNodeTask(final ProjectLayout projectLayout, final ObjectFactory objectFactory) { - this.beanRegistryId = Beans.getBeanRegistryId(projectLayout.getProjectDirectory().toString()); + public InstallNodeTask(final ObjectFactory objectFactory) { + this.beanRegistryBuildService = objectFactory.property(BeanRegistryBuildService.class); this.nodeVersion = objectFactory.property(String.class); this.nodeInstallDirectory = objectFactory.property(File.class); this.nodeDistributionUrlRoot = objectFactory.property(String.class); @@ -204,6 +258,19 @@ public InstallNodeTask(final ProjectLayout projectLayout, final ObjectFactory ob this.retryInitialIntervalMs = objectFactory.property(Integer.class); this.retryIntervalMultiplier = objectFactory.property(Double.class); this.retryMaxIntervalMs = objectFactory.property(Integer.class); + this.verboseModeEnabled = objectFactory.property(Boolean.class); + this.systemHttpProxyHost = objectFactory.property(String.class); + this.systemHttpProxyPort = objectFactory.property(Integer.class); + this.systemHttpsProxyHost = objectFactory.property(String.class); + this.systemHttpsProxyPort = objectFactory.property(Integer.class); + this.systemNonProxyHosts = objectFactory.setProperty(String.class); + this.systemJvmArch = objectFactory.property(String.class); + this.systemOsName = objectFactory.property(String.class); + } + + @Internal + public Property getBeanRegistryBuildService() { + return beanRegistryBuildService; } @Input @@ -301,6 +368,46 @@ public Property getRetryMaxIntervalMs() { return retryMaxIntervalMs; } + @Internal + public Property getVerboseModeEnabled() { + return verboseModeEnabled; + } + + @Internal + public Property getSystemHttpProxyHost() { + return systemHttpProxyHost; + } + + @Internal + public Property getSystemHttpProxyPort() { + return systemHttpProxyPort; + } + + @Internal + public Property getSystemHttpsProxyHost() { + return systemHttpsProxyHost; + } + + @Internal + public Property getSystemHttpsProxyPort() { + return systemHttpsProxyPort; + } + + @Internal + public SetProperty getSystemNonProxyHosts() { + return systemNonProxyHosts; + } + + @Internal + public Property getSystemJvmArch() { + return systemJvmArch; + } + + @Internal + public Property getSystemOsName() { + return systemOsName; + } + @OutputFile public RegularFileProperty getNodeExecutableFile() { return nodeExecutableFile; @@ -319,7 +426,9 @@ public RegularFileProperty getNodeExecutableFile() { */ @TaskAction public void execute() throws BeanRegistryException, FrontendException, IOException { - Beans.getBean(beanRegistryId, TaskLoggerConfigurer.class).initLoggerAdapter(this); + final BeanRegistry beanRegistry = beanRegistryBuildService.get().getBeanRegistry(); + TaskLoggerInitializer.initAdapter(this, verboseModeEnabled.get(), + beanRegistry.getBean(GradleLoggerAdapter.class), beanRegistry.getBean(GradleSettings.class)); final Credentials distributionServerCredentials = nodeDistributionServerUsername .map(username -> Credentials @@ -335,10 +444,10 @@ public void execute() throws BeanRegistryException, FrontendException, IOExcepti .map(username -> Credentials.builder().username(username).password(httpsProxyPassword.get()).build()) .getOrNull(); - final Platform platform = Beans.getBean(beanRegistryId, PlatformProvider.class).getPlatform(); + final Platform platform = Platform.builder().jvmArch(systemJvmArch.get()).osName(systemOsName.get()).build(); getLogger().debug("Platform: {}", platform); - final ProxySettings proxySettings = Beans - .getBean(beanRegistryId, ResolveProxySettingsByUrl.class) + final ProxySettings proxySettings = beanRegistry + .getBean(ResolveProxySettingsByUrl.class) .execute(ResolveProxySettingsByUrlCommand .builder() .httpsProxyHost(httpsProxyHost.getOrNull()) @@ -348,9 +457,14 @@ public void execute() throws BeanRegistryException, FrontendException, IOExcepti .httpProxyPort(httpProxyPort.get()) .httpProxyCredentials(httpProxyCredentials) .resourceUrl(new URL(nodeDistributionUrlRoot.get())) + .systemHttpProxyHost(systemHttpProxyHost.getOrNull()) + .systemHttpProxyPort(systemHttpProxyPort.get()) + .systemHttpsProxyHost(systemHttpsProxyHost.getOrNull()) + .systemHttpsProxyPort(systemHttpsProxyPort.get()) + .systemNonProxyHosts(systemNonProxyHosts.get()) .build()); - Beans - .getBean(beanRegistryId, InstallNodeDistribution.class) + beanRegistry + .getBean(InstallNodeDistribution.class) .execute(InstallNodeDistributionCommand .builder() .platform(platform) diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/InstallPackageManagerTask.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/InstallPackageManagerTask.java index fae55213..36aa46ad 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/InstallPackageManagerTask.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/InstallPackageManagerTask.java @@ -5,7 +5,6 @@ import javax.inject.Inject; import org.gradle.api.GradleException; -import org.gradle.api.file.ProjectLayout; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.model.ObjectFactory; import org.gradle.api.tasks.InputFile; @@ -13,8 +12,8 @@ import org.gradle.process.ExecOperations; import org.siouan.frontendgradleplugin.domain.ExecutableType; import org.siouan.frontendgradleplugin.domain.FileManager; +import org.siouan.frontendgradleplugin.infrastructure.bean.BeanRegistry; import org.siouan.frontendgradleplugin.infrastructure.bean.BeanRegistryException; -import org.siouan.frontendgradleplugin.infrastructure.bean.Beans; /** * This task installs the relevant package manager for the current project (by executing command @@ -38,20 +37,10 @@ public class InstallPackageManagerTask extends AbstractRunCommandTask { private final RegularFileProperty packageManagerExecutableFile; @Inject - public InstallPackageManagerTask(final ProjectLayout projectLayout, final ObjectFactory objectFactory, - final ExecOperations execOperations) { - super(projectLayout, objectFactory, execOperations); + public InstallPackageManagerTask(final ObjectFactory objectFactory, final ExecOperations execOperations) { + super(objectFactory, execOperations); this.executableType.set(ExecutableType.COREPACK); this.packageManagerSpecificationFile = objectFactory.fileProperty(); - this.script.set(packageManagerSpecificationFile.getAsFile().map(f -> { - try { - return String.join(" ", COREPACK_ENABLE_COMMAND, - Beans.getBean(beanRegistryId, FileManager.class).readString(f.toPath(), StandardCharsets.UTF_8) - .split("@")[0]); - } catch (final BeanRegistryException | IOException e) { - throw new GradleException(e.getClass().getName() + ": " + e.getMessage(), e); - } - })); this.packageManagerExecutableFile = objectFactory.fileProperty(); } @@ -64,4 +53,22 @@ public RegularFileProperty getPackageManagerSpecificationFile() { public RegularFileProperty getPackageManagerExecutableFile() { return packageManagerExecutableFile; } + + @Override + public void execute() throws NonRunnableTaskException, BeanRegistryException { + final BeanRegistry beanRegistry = beanRegistryBuildService.get().getBeanRegistry(); + + this.script.set(packageManagerSpecificationFile.getAsFile().map(f -> { + try { + return String.join(" ", COREPACK_ENABLE_COMMAND, beanRegistry + .getBean(FileManager.class) + .readString(f.toPath(), StandardCharsets.UTF_8) + .split("@")[0]); + } catch (final BeanRegistryException | IOException e) { + throw new GradleException(e.getClass().getName() + ": " + e.getMessage(), e); + } + })); + + super.execute(); + } } diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/PublishTask.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/PublishTask.java index b6b457f0..80bbeb3c 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/PublishTask.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/PublishTask.java @@ -2,7 +2,6 @@ import javax.inject.Inject; -import org.gradle.api.file.ProjectLayout; import org.gradle.api.model.ObjectFactory; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; @@ -10,16 +9,17 @@ import org.gradle.process.ExecOperations; /** - * This task publishes frontend artifacts. + * This task publishes project artifacts. + *

+ * NOTE: this task will be renamed {@code PublishFrontendTask} to conform with naming convention of other tasks. * * @since 1.4.0 */ public class PublishTask extends AbstractRunCommandTask { @Inject - public PublishTask(final ProjectLayout projectLayout, final ObjectFactory objectFactory, - final ExecOperations execOperations) { - super(projectLayout, objectFactory, execOperations); + public PublishTask(final ObjectFactory objectFactory, final ExecOperations execOperations) { + super(objectFactory, execOperations); } @Input diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/ResolvePackageManagerTask.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/ResolvePackageManagerTask.java index 0d3fdeb8..2d37654b 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/ResolvePackageManagerTask.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/ResolvePackageManagerTask.java @@ -5,13 +5,14 @@ import javax.inject.Inject; import org.gradle.api.DefaultTask; -import org.gradle.api.file.ProjectLayout; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.model.ObjectFactory; import org.gradle.api.provider.Property; import org.gradle.api.tasks.CacheableTask; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.Internal; +import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.PathSensitive; import org.gradle.api.tasks.PathSensitivity; @@ -19,12 +20,11 @@ import org.siouan.frontendgradleplugin.domain.InvalidJsonFileException; import org.siouan.frontendgradleplugin.domain.MalformedPackageManagerSpecification; import org.siouan.frontendgradleplugin.domain.Platform; -import org.siouan.frontendgradleplugin.domain.PlatformProvider; import org.siouan.frontendgradleplugin.domain.ResolvePackageManager; import org.siouan.frontendgradleplugin.domain.ResolvePackageManagerCommand; import org.siouan.frontendgradleplugin.domain.UnsupportedPackageManagerException; import org.siouan.frontendgradleplugin.infrastructure.bean.BeanInstanciationException; -import org.siouan.frontendgradleplugin.infrastructure.bean.Beans; +import org.siouan.frontendgradleplugin.infrastructure.bean.BeanRegistry; import org.siouan.frontendgradleplugin.infrastructure.bean.TooManyCandidateBeansException; import org.siouan.frontendgradleplugin.infrastructure.bean.ZeroOrMultiplePublicConstructorsException; @@ -38,9 +38,11 @@ public class ResolvePackageManagerTask extends DefaultTask { /** - * Bean registry ID. + * Bean registry service provider. + * + * @since 8.1.0 */ - private final String beanRegistryId; + protected final Property beanRegistryBuildService; /** * The 'package.json' file. @@ -53,7 +55,7 @@ public class ResolvePackageManagerTask extends DefaultTask { private final Property nodeInstallDirectory; /** - * File that will contain the specification of the package manager for the project. + * File that will contain the specification (name and version) of the package manager for the project. */ private final RegularFileProperty packageManagerSpecificationFile; @@ -62,17 +64,47 @@ public class ResolvePackageManagerTask extends DefaultTask { */ private final RegularFileProperty packageManagerExecutablePathFile; + /** + * Whether the task should produce log messages for the end-user. + * + * @since 8.1.0 + */ + private final Property verboseModeEnabled; + + /** + * Architecture of the underlying JVM. + * + * @since 8.1.0 + */ + private final Property systemJvmArch; + + /** + * System name of the O/S. + * + * @since 8.1.0 + */ + private final Property systemOsName; + @Inject - public ResolvePackageManagerTask(final ProjectLayout projectLayout, final ObjectFactory objectFactory) { - this.beanRegistryId = Beans.getBeanRegistryId(projectLayout.getProjectDirectory().toString()); + public ResolvePackageManagerTask(final ObjectFactory objectFactory) { + this.beanRegistryBuildService = objectFactory.property(BeanRegistryBuildService.class); this.packageJsonFile = objectFactory.fileProperty(); this.nodeInstallDirectory = objectFactory.property(File.class); this.packageManagerSpecificationFile = objectFactory.fileProperty(); this.packageManagerExecutablePathFile = objectFactory.fileProperty(); + this.verboseModeEnabled = objectFactory.property(Boolean.class); + this.systemJvmArch = objectFactory.property(String.class); + this.systemOsName = objectFactory.property(String.class); + } + + @Internal + public Property getBeanRegistryBuildService() { + return beanRegistryBuildService; } @InputFile @PathSensitive(PathSensitivity.ABSOLUTE) + @Optional public RegularFileProperty getPackageJsonFile() { return packageJsonFile; } @@ -82,6 +114,21 @@ public Property getNodeInstallDirectory() { return nodeInstallDirectory; } + @Internal + public Property getVerboseModeEnabled() { + return verboseModeEnabled; + } + + @Internal + public Property getSystemJvmArch() { + return systemJvmArch; + } + + @Internal + public Property getSystemOsName() { + return systemOsName; + } + @OutputFile public RegularFileProperty getPackageManagerSpecificationFile() { return packageManagerSpecificationFile; @@ -97,18 +144,20 @@ public void execute() throws BeanInstanciationException, TooManyCandidateBeansException, ZeroOrMultiplePublicConstructorsException, IOException, InvalidJsonFileException, MalformedPackageManagerSpecification, UnsupportedPackageManagerException { - Beans.getBean(beanRegistryId, TaskLoggerConfigurer.class).initLoggerAdapter(this); + final BeanRegistry beanRegistry = beanRegistryBuildService.get().getBeanRegistry(); + TaskLoggerInitializer.initAdapter(this, verboseModeEnabled.get(), + beanRegistry.getBean(GradleLoggerAdapter.class), beanRegistry.getBean(GradleSettings.class)); - final Platform platform = Beans.getBean(beanRegistryId, PlatformProvider.class).getPlatform(); + final Platform platform = Platform.builder().jvmArch(systemJvmArch.get()).osName(systemOsName.get()).build(); getLogger().debug("Platform: {}", platform); // Though it is not used by the plugin later, the version of the package manager is written in the specification // file so as other tasks using this file as an input are re-executed if the package manager is upgraded (same // package manager, different version). - Beans - .getBean(beanRegistryId, ResolvePackageManager.class) + beanRegistry + .getBean(ResolvePackageManager.class) .execute(ResolvePackageManagerCommand .builder() - .packageJsonFilePath(packageJsonFile.getAsFile().get().toPath()) + .packageJsonFilePath(packageJsonFile.getAsFile().map(File::toPath).getOrNull()) .nodeInstallDirectoryPath(nodeInstallDirectory.map(File::toPath).get()) .platform(platform) .packageManagerSpecificationFilePath(packageManagerSpecificationFile.getAsFile().get().toPath()) diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/RunCorepack.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/RunCorepack.java index 28da0232..ddfaa2bc 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/RunCorepack.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/RunCorepack.java @@ -2,7 +2,6 @@ import javax.inject.Inject; -import org.gradle.api.file.ProjectLayout; import org.gradle.api.model.ObjectFactory; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; @@ -25,12 +24,11 @@ * * @since 7.0.0 */ -public class RunCorepack extends AbstractRunCommandTaskType { +public abstract class RunCorepack extends AbstractRunCommandTaskType { @Inject - public RunCorepack(final ProjectLayout projectLayout, final ObjectFactory objectFactory, - final ExecOperations execOperations) { - super(projectLayout, objectFactory, execOperations); + public RunCorepack(final ObjectFactory objectFactory, final ExecOperations execOperations) { + super(objectFactory, execOperations); this.executableType.set(ExecutableType.COREPACK); } diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/RunNode.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/RunNode.java index 606f98c6..c5b1b760 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/RunNode.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/RunNode.java @@ -2,7 +2,6 @@ import javax.inject.Inject; -import org.gradle.api.file.ProjectLayout; import org.gradle.api.model.ObjectFactory; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; @@ -25,12 +24,11 @@ * * @since 1.2.0 */ -public class RunNode extends AbstractRunCommandTaskType { +public abstract class RunNode extends AbstractRunCommandTaskType { @Inject - public RunNode(final ProjectLayout projectLayout, final ObjectFactory objectFactory, - final ExecOperations execOperations) { - super(projectLayout, objectFactory, execOperations); + public RunNode(final ObjectFactory objectFactory, final ExecOperations execOperations) { + super(objectFactory, execOperations); executableType.set(ExecutableType.NODE); } diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/RunNpm.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/RunNpm.java index f3f672cf..e1755bf1 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/RunNpm.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/RunNpm.java @@ -2,7 +2,6 @@ import javax.inject.Inject; -import org.gradle.api.file.ProjectLayout; import org.gradle.api.model.ObjectFactory; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; @@ -25,12 +24,11 @@ * * @since 6.0.0 */ -public class RunNpm extends AbstractRunCommandTaskType { +public abstract class RunNpm extends AbstractRunCommandTaskType { @Inject - public RunNpm(final ProjectLayout projectLayout, final ObjectFactory objectFactory, - final ExecOperations execOperations) { - super(projectLayout, objectFactory, execOperations); + public RunNpm(final ObjectFactory objectFactory, final ExecOperations execOperations) { + super(objectFactory, execOperations); executableType.set(ExecutableType.NPM); } diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/RunPnpm.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/RunPnpm.java index a0882cdc..22e40d3e 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/RunPnpm.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/RunPnpm.java @@ -2,7 +2,6 @@ import javax.inject.Inject; -import org.gradle.api.file.ProjectLayout; import org.gradle.api.model.ObjectFactory; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; @@ -25,12 +24,11 @@ * * @since 7.0.0 */ -public class RunPnpm extends AbstractRunCommandTaskType { +public abstract class RunPnpm extends AbstractRunCommandTaskType { @Inject - public RunPnpm(final ProjectLayout projectLayout, final ObjectFactory objectFactory, - final ExecOperations execOperations) { - super(projectLayout, objectFactory, execOperations); + public RunPnpm(final ObjectFactory objectFactory, final ExecOperations execOperations) { + super(objectFactory, execOperations); executableType.set(ExecutableType.PNPM); } diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/RunYarn.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/RunYarn.java index fb0c8817..98f16901 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/RunYarn.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/RunYarn.java @@ -2,7 +2,6 @@ import javax.inject.Inject; -import org.gradle.api.file.ProjectLayout; import org.gradle.api.model.ObjectFactory; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; @@ -25,12 +24,11 @@ * * @since 6.0.0 */ -public class RunYarn extends AbstractRunCommandTaskType { +public abstract class RunYarn extends AbstractRunCommandTaskType { @Inject - public RunYarn(final ProjectLayout projectLayout, final ObjectFactory objectFactory, - final ExecOperations execOperations) { - super(projectLayout, objectFactory, execOperations); + public RunYarn(final ObjectFactory objectFactory, final ExecOperations execOperations) { + super(objectFactory, execOperations); executableType.set(ExecutableType.YARN); } diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/SystemExtension.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/SystemExtension.java deleted file mode 100644 index 29113126..00000000 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/SystemExtension.java +++ /dev/null @@ -1,77 +0,0 @@ -package org.siouan.frontendgradleplugin.infrastructure.gradle; - -import org.gradle.api.provider.Provider; -import org.gradle.api.provider.ProviderFactory; -import org.siouan.frontendgradleplugin.domain.SystemProperties; - -/** - * Extension providing system settings. - * - * @since 5.2.0 - */ -public class SystemExtension { - - /** - * Proxy host used to download resources with HTTP protocol. - */ - private final Provider httpProxyHost; - - /** - * Proxy port used to download resources with HTTP protocol. - */ - private final Provider httpProxyPort; - - /** - * Proxy host used to download resources with HTTPS protocol. - */ - private final Provider httpsProxyHost; - - /** - * Proxy port used to download resources with HTTPS protocol. - */ - private final Provider httpsProxyPort; - - private final Provider nonProxyHosts; - - private final Provider jvmArch; - - private final Provider osName; - - public SystemExtension(final ProviderFactory providerFactory) { - this.httpProxyHost = providerFactory.systemProperty(SystemProperties.HTTP_PROXY_HOST); - this.httpProxyPort = providerFactory.systemProperty(SystemProperties.HTTP_PROXY_PORT); - this.httpsProxyHost = providerFactory.systemProperty(SystemProperties.HTTPS_PROXY_HOST); - this.httpsProxyPort = providerFactory.systemProperty(SystemProperties.HTTPS_PROXY_PORT); - this.nonProxyHosts = providerFactory.systemProperty(SystemProperties.NON_PROXY_HOSTS); - this.jvmArch = providerFactory.systemProperty(SystemProperties.JVM_ARCH_PROPERTY); - this.osName = providerFactory.systemProperty(SystemProperties.OS_NAME_PROPERTY); - } - - public Provider getHttpProxyHost() { - return httpProxyHost; - } - - public Provider getHttpProxyPort() { - return httpProxyPort; - } - - public Provider getHttpsProxyHost() { - return httpsProxyHost; - } - - public Provider getHttpsProxyPort() { - return httpsProxyPort; - } - - public Provider getNonProxyHosts() { - return nonProxyHosts; - } - - public Provider getJvmArch() { - return jvmArch; - } - - public Provider getOsName() { - return osName; - } -} diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/SystemProviders.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/SystemProviders.java new file mode 100644 index 00000000..b5d6c62c --- /dev/null +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/SystemProviders.java @@ -0,0 +1,54 @@ +package org.siouan.frontendgradleplugin.infrastructure.gradle; + +import lombok.Getter; +import org.gradle.api.provider.Provider; +import org.gradle.api.provider.ProviderFactory; +import org.siouan.frontendgradleplugin.domain.SystemProperties; + +/** + * Providers for system properties. + * + * @since 8.1.0 + */ +@Getter +public class SystemProviders { + + /** + * Proxy host used to download resources with HTTP protocol. + */ + private final Provider httpProxyHost; + + /** + * Proxy port used to download resources with HTTP protocol. + */ + private final Provider httpProxyPort; + + /** + * Proxy host used to download resources with HTTPS protocol. + */ + private final Provider httpsProxyHost; + + /** + * Proxy port used to download resources with HTTPS protocol. + */ + private final Provider httpsProxyPort; + + /** + * List of hosts that should be reached directly, bypassing the proxy. + */ + private final Provider nonProxyHosts; + + private final Provider jvmArch; + + private final Provider osName; + + public SystemProviders(final ProviderFactory providerFactory) { + httpProxyHost = providerFactory.systemProperty(SystemProperties.HTTP_PROXY_HOST); + httpProxyPort = providerFactory.systemProperty(SystemProperties.HTTP_PROXY_PORT); + httpsProxyHost = providerFactory.systemProperty(SystemProperties.HTTPS_PROXY_HOST); + httpsProxyPort = providerFactory.systemProperty(SystemProperties.HTTPS_PROXY_PORT); + nonProxyHosts = providerFactory.systemProperty(SystemProperties.NON_PROXY_HOSTS); + jvmArch = providerFactory.systemProperty(SystemProperties.JVM_ARCH); + osName = providerFactory.systemProperty(SystemProperties.OS_NAME); + } +} diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/SystemSettingsProviderImpl.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/SystemSettingsProviderImpl.java deleted file mode 100644 index d325e032..00000000 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/SystemSettingsProviderImpl.java +++ /dev/null @@ -1,96 +0,0 @@ -package org.siouan.frontendgradleplugin.infrastructure.gradle; - -import java.util.Optional; -import java.util.Set; -import java.util.function.Predicate; - -import org.gradle.api.provider.Provider; -import org.siouan.frontendgradleplugin.domain.SystemProperties; -import org.siouan.frontendgradleplugin.domain.SystemSettingsProvider; - -/** - * System-wide proxy settings. - * - * @since 5.2.0 - */ -public class SystemSettingsProviderImpl implements SystemSettingsProvider { - - private final Provider httpProxyHost; - - private final Provider httpProxyPort; - - private final Provider httpsProxyHost; - - private final Provider httpsProxyPort; - - private final Provider nonProxyHosts; - - private final Provider jvmArch; - - private final Provider osName; - - private final int defaultHttpProxyPort; - - private final int defaultHttpsProxyPort; - - public SystemSettingsProviderImpl(final SystemExtension systemExtension, final int defaultHttpProxyPort, - final int defaultHttpsProxyPort) { - this.httpProxyHost = systemExtension.getHttpProxyHost(); - this.httpProxyPort = systemExtension.getHttpProxyPort(); - this.httpsProxyHost = systemExtension.getHttpsProxyHost(); - this.httpsProxyPort = systemExtension.getHttpsProxyPort(); - this.nonProxyHosts = systemExtension.getNonProxyHosts(); - this.jvmArch = systemExtension.getJvmArch(); - this.osName = systemExtension.getOsName(); - this.defaultHttpProxyPort = defaultHttpProxyPort; - this.defaultHttpsProxyPort = defaultHttpsProxyPort; - } - - @Override - public String getHttpProxyHost() { - return httpProxyHost.getOrNull(); - } - - @Override - public int getHttpProxyPort() { - return Optional - .ofNullable(httpProxyPort.getOrNull()) - .filter(port -> !port.isBlank()) - .map(Integer::parseInt) - .orElse(defaultHttpProxyPort); - } - - @Override - public String getHttpsProxyHost() { - return httpsProxyHost.getOrNull(); - } - - @Override - public int getHttpsProxyPort() { - return Optional - .ofNullable(httpsProxyPort.getOrNull()) - .filter(port -> !port.isBlank()) - .map(Integer::parseInt) - .orElse(defaultHttpsProxyPort); - } - - @Override - public Set getNonProxyHosts() { - return Optional - .ofNullable(nonProxyHosts.getOrNull()) - .filter(Predicate.not(String::isBlank)) - .map(hosts -> hosts.split(SystemProperties.NON_PROXY_HOSTS_SPLIT_PATTERN)) - .map(Set::of) - .orElseGet(Set::of); - } - - @Override - public String getSystemJvmArch() { - return jvmArch.get(); - } - - @Override - public String getSystemOsName() { - return osName.get(); - } -} diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/TaskLoggerConfigurer.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/TaskLoggerInitializer.java similarity index 59% rename from plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/TaskLoggerConfigurer.java rename to plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/TaskLoggerInitializer.java index 4f3f7fce..939be719 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/TaskLoggerConfigurer.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/gradle/TaskLoggerInitializer.java @@ -1,5 +1,7 @@ package org.siouan.frontendgradleplugin.infrastructure.gradle; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; import org.gradle.api.Task; import org.gradle.api.logging.LogLevel; import org.gradle.api.logging.LoggingManager; @@ -9,27 +11,15 @@ * * @since 2.0.0 */ -public class TaskLoggerConfigurer { +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class TaskLoggerInitializer { - private final FrontendExtension extension; - - private final GradleLoggerAdapter gradleLoggerAdapter; - - private final GradleSettings gradleSettings; - - public TaskLoggerConfigurer(final FrontendExtension extension, final GradleLoggerAdapter gradleLoggerAdapter, - final GradleSettings gradleSettings) { - this.extension = extension; - this.gradleLoggerAdapter = gradleLoggerAdapter; - this.gradleSettings = gradleSettings; - } - - public void initLoggerAdapter(final Task task) { + public static void initAdapter(final Task task, final boolean verboseModeEnabled, + final GradleLoggerAdapter gradleLoggerAdapter, final GradleSettings gradleSettings) { task .getLogger() - .debug("Configuring logger for task '{}': verboseModeEnabled={}", task.getName(), - extension.getVerboseModeEnabled().get()); - gradleLoggerAdapter.init(task.getLogger(), resolveLogLevel(task), extension.getVerboseModeEnabled().get(), + .debug("Configuring logger for task '{}': verboseModeEnabled={}", task.getName(), verboseModeEnabled); + gradleLoggerAdapter.init(task.getLogger(), resolveLogLevel(task, gradleSettings), verboseModeEnabled, '[' + task.getName() + "] "); } @@ -42,17 +32,17 @@ public void initLoggerAdapter(final Task task) { * @param task Task. * @return Logging level. */ - private LogLevel resolveLogLevel(final Task task) { + private static LogLevel resolveLogLevel(final Task task, final GradleSettings gradleSettings) { LogLevel loggingLevel = task.getLogging().getLevel(); if (loggingLevel != null) { return loggingLevel; } - loggingLevel = gradleSettings.projectLogLevel(); + loggingLevel = gradleSettings.getProjectLogLevel(); if (loggingLevel != null) { return loggingLevel; } - return gradleSettings.commandLineLogLevel(); + return gradleSettings.getCommandLineLogLevel(); } } diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/httpclient/HttpClientProviderImpl.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/httpclient/HttpClientProviderImpl.java index 32a7efe0..c4fef80c 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/httpclient/HttpClientProviderImpl.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/httpclient/HttpClientProviderImpl.java @@ -1,5 +1,8 @@ package org.siouan.frontendgradleplugin.infrastructure.httpclient; +import java.io.Serial; +import java.io.Serializable; + import org.siouan.frontendgradleplugin.domain.installer.HttpClient; import org.siouan.frontendgradleplugin.domain.installer.HttpClientProvider; @@ -8,10 +11,13 @@ * * @since 4.0.1 */ -public class HttpClientProviderImpl implements HttpClientProvider { +public class HttpClientProviderImpl implements HttpClientProvider, Serializable { private static final HttpClient INSTANCE = new ApacheHttpClient(); + @Serial + private static final long serialVersionUID = -5442300705570408127L; + @Override public HttpClient getInstance() { return INSTANCE; diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/system/ChannelProviderImpl.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/system/ChannelProviderImpl.java index 566a3a28..7310397c 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/system/ChannelProviderImpl.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/system/ChannelProviderImpl.java @@ -2,6 +2,8 @@ import java.io.IOException; import java.io.InputStream; +import java.io.Serial; +import java.io.Serializable; import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; @@ -17,7 +19,10 @@ * * @since 2.0.0 */ -public class ChannelProviderImpl implements ChannelProvider { +public class ChannelProviderImpl implements ChannelProvider, Serializable { + + @Serial + private static final long serialVersionUID = -5977105709250111639L; @Override public ReadableByteChannel getReadableByteChannel(final InputStream inputStream) { diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/system/FileManagerImpl.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/system/FileManagerImpl.java index 5321c207..52a4883c 100644 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/system/FileManagerImpl.java +++ b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/system/FileManagerImpl.java @@ -7,6 +7,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.Serial; import java.nio.charset.Charset; import java.nio.file.CopyOption; import java.nio.file.FileVisitResult; @@ -30,6 +31,9 @@ */ public class FileManagerImpl implements FileManager { + @Serial + private static final long serialVersionUID = -4492951623732511344L; + @Override public long copy(final InputStream inputStream, final Path filePath) throws IOException { return Files.copy(inputStream, filePath); diff --git a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/system/PlatformProviderImpl.java b/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/system/PlatformProviderImpl.java deleted file mode 100644 index 4c996c6d..00000000 --- a/plugin/src/main/java/org/siouan/frontendgradleplugin/infrastructure/system/PlatformProviderImpl.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.siouan.frontendgradleplugin.infrastructure.system; - -import lombok.RequiredArgsConstructor; -import org.siouan.frontendgradleplugin.domain.Platform; -import org.siouan.frontendgradleplugin.domain.PlatformProvider; -import org.siouan.frontendgradleplugin.domain.SystemSettingsProvider; - -/** - * Implementation of a platform provider. - * - * @since 7.0.0 - */ -@RequiredArgsConstructor -public class PlatformProviderImpl implements PlatformProvider { - - private final SystemSettingsProvider systemSettingsProvider; - - @Override - public Platform getPlatform() { - return Platform - .builder() - .jvmArch(systemSettingsProvider.getSystemJvmArch()) - .osName(systemSettingsProvider.getSystemOsName()) - .build(); - } -} diff --git a/plugin/src/test/java/org/siouan/frontendgradleplugin/FrontendGradlePluginTest.java b/plugin/src/test/java/org/siouan/frontendgradleplugin/FrontendGradlePluginTest.java index cd4d63a8..28ec2958 100644 --- a/plugin/src/test/java/org/siouan/frontendgradleplugin/FrontendGradlePluginTest.java +++ b/plugin/src/test/java/org/siouan/frontendgradleplugin/FrontendGradlePluginTest.java @@ -1,11 +1,22 @@ package org.siouan.frontendgradleplugin; import static org.assertj.core.api.Assertions.assertThat; +import static org.siouan.frontendgradleplugin.FrontendGradlePlugin.*; +import static org.siouan.frontendgradleplugin.domain.SystemProperties.HTTPS_PROXY_HOST; +import static org.siouan.frontendgradleplugin.domain.SystemProperties.HTTPS_PROXY_PORT; +import static org.siouan.frontendgradleplugin.domain.SystemProperties.HTTP_PROXY_HOST; +import static org.siouan.frontendgradleplugin.domain.SystemProperties.HTTP_PROXY_PORT; +import static org.siouan.frontendgradleplugin.domain.SystemProperties.JVM_ARCH; +import static org.siouan.frontendgradleplugin.domain.SystemProperties.NON_PROXY_HOSTS; +import static org.siouan.frontendgradleplugin.domain.SystemProperties.NON_PROXY_HOSTS_SPLIT_PATTERN; +import static org.siouan.frontendgradleplugin.domain.SystemProperties.OS_NAME; import java.io.IOException; import java.nio.file.Files; -import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import org.gradle.api.Project; @@ -17,6 +28,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junitpioneer.jupiter.SetSystemProperty; import org.mockito.junit.jupiter.MockitoExtension; import org.siouan.frontendgradleplugin.infrastructure.gradle.AssembleTask; import org.siouan.frontendgradleplugin.infrastructure.gradle.CheckTask; @@ -46,7 +58,7 @@ void setUp() { @Test void should_register_tasks_with_default_extension_values_applied() throws IOException { - Files.createDirectory(project.file(FrontendGradlePlugin.DEFAULT_NODE_INSTALL_DIRECTORY_NAME).toPath()); + Files.createDirectory(project.file(DEFAULT_NODE_INSTALL_DIRECTORY_NAME).toPath()); plugin.apply(project); final FrontendExtension extension = Objects.requireNonNull( @@ -54,61 +66,58 @@ void should_register_tasks_with_default_extension_values_applied() throws IOExce assertThat(extension.getNodeDistributionProvided().get()).isFalse(); assertThat(extension.getNodeVersion().isPresent()).isFalse(); - assertThat(extension.getNodeInstallDirectory().getAsFile().get()).isEqualTo(project - .getLayout() - .getProjectDirectory() - .dir(FrontendGradlePlugin.DEFAULT_NODE_INSTALL_DIRECTORY_NAME) - .getAsFile()); - assertThat(extension.getNodeDistributionUrlRoot().get()).isEqualTo( - FrontendGradlePlugin.DEFAULT_NODE_DISTRIBUTION_URL_ROOT); + assertThat(extension.getNodeInstallDirectory().getAsFile().get()).isEqualTo( + project.getLayout().getProjectDirectory().dir(DEFAULT_NODE_INSTALL_DIRECTORY_NAME).getAsFile()); + assertThat(extension.getNodeDistributionUrlRoot().get()).isEqualTo(DEFAULT_NODE_DISTRIBUTION_URL_ROOT); assertThat(extension.getNodeDistributionUrlPathPattern().get()).isEqualTo( - FrontendGradlePlugin.DEFAULT_NODE_DISTRIBUTION_URL_PATH_PATTERN); + DEFAULT_NODE_DISTRIBUTION_URL_PATH_PATTERN); assertThat(extension.getNodeDistributionServerUsername().isPresent()).isFalse(); assertThat(extension.getNodeDistributionServerPassword().isPresent()).isFalse(); - assertThat(extension.getInstallScript().get()).isEqualTo(FrontendGradlePlugin.DEFAULT_INSTALL_SCRIPT); + assertThat(extension.getInstallScript().get()).isEqualTo(DEFAULT_INSTALL_SCRIPT); assertThat(extension.getCleanScript().isPresent()).isFalse(); assertThat(extension.getAssembleScript().isPresent()).isFalse(); assertThat(extension.getCheckScript().isPresent()).isFalse(); assertThat(extension.getPublishScript().isPresent()).isFalse(); assertThat(extension.getPackageJsonDirectory().getAsFile().get()).isEqualTo(project.getProjectDir()); assertThat(extension.getHttpProxyHost().isPresent()).isFalse(); - assertThat(extension.getHttpProxyPort().get()).isEqualTo(FrontendGradlePlugin.DEFAULT_HTTP_PROXY_PORT); + assertThat(extension.getHttpProxyPort().get()).isEqualTo(DEFAULT_HTTP_PROXY_PORT); assertThat(extension.getHttpProxyUsername().isPresent()).isFalse(); assertThat(extension.getHttpProxyPassword().isPresent()).isFalse(); assertThat(extension.getHttpsProxyHost().isPresent()).isFalse(); - assertThat(extension.getHttpsProxyPort().get()).isEqualTo(FrontendGradlePlugin.DEFAULT_HTTPS_PROXY_PORT); + assertThat(extension.getHttpsProxyPort().get()).isEqualTo(DEFAULT_HTTPS_PROXY_PORT); assertThat(extension.getHttpsProxyUsername().isPresent()).isFalse(); assertThat(extension.getHttpsProxyPassword().isPresent()).isFalse(); - assertThat(extension.getMaxDownloadAttempts().get()).isEqualTo( - FrontendGradlePlugin.DEFAULT_MAX_DOWNLOAD_ATTEMPTS); + assertThat(extension.getMaxDownloadAttempts().get()).isEqualTo(DEFAULT_MAX_DOWNLOAD_ATTEMPTS); assertThat(extension.getRetryHttpStatuses().get()).containsExactlyInAnyOrderElementsOf( - FrontendGradlePlugin.DEFAULT_RETRY_HTTP_STATUSES); - assertThat(extension.getRetryInitialIntervalMs().get()).isEqualTo( - FrontendGradlePlugin.DEFAULT_RETRY_INITIAL_INTERVAL_MS); - assertThat(extension.getRetryIntervalMultiplier().get()).isEqualTo( - FrontendGradlePlugin.DEFAULT_RETRY_INTERVAL_MULTIPLIER); - assertThat(extension.getRetryMaxIntervalMs().get()).isEqualTo( - FrontendGradlePlugin.DEFAULT_RETRY_MAX_INTERVAL_MS); - assertThat(extension.getInternalPackageManagerSpecificationFile().getAsFile().get()).isEqualTo(project - .getProjectDir() - .toPath() - .resolve(Paths.get(FrontendGradlePlugin.DEFAULT_CACHE_DIRECTORY_NAME, - FrontendGradlePlugin.RESOLVE_PACKAGE_MANAGER_TASK_NAME, - FrontendGradlePlugin.PACKAGE_MANAGER_SPECIFICATION_FILE_NAME)) - .toFile()); - assertThat(extension.getInternalPackageManagerExecutablePathFile().getAsFile().get()).isEqualTo(project - .getProjectDir() - .toPath() - .resolve(Paths.get(FrontendGradlePlugin.DEFAULT_CACHE_DIRECTORY_NAME, - FrontendGradlePlugin.RESOLVE_PACKAGE_MANAGER_TASK_NAME, - FrontendGradlePlugin.PACKAGE_MANAGER_EXECUTABLE_PATH_FILE_NAME)) - .toFile()); + DEFAULT_RETRY_HTTP_STATUSES); + assertThat(extension.getRetryInitialIntervalMs().get()).isEqualTo(DEFAULT_RETRY_INITIAL_INTERVAL_MS); + assertThat(extension.getRetryIntervalMultiplier().get()).isEqualTo(DEFAULT_RETRY_INTERVAL_MULTIPLIER); + assertThat(extension.getRetryMaxIntervalMs().get()).isEqualTo(DEFAULT_RETRY_MAX_INTERVAL_MS); + assertThat(extension.getCacheDirectory().getAsFile().get()).isEqualTo( + project.getLayout().getProjectDirectory().dir(DEFAULT_CACHE_DIRECTORY_NAME).getAsFile()); assertThat(extension.getVerboseModeEnabled().get()).isFalse(); - assertThatTasksAreConfigured(project, extension); + final Map expectedSystemProperties = new HashMap<>(); + Set + .of(HTTP_PROXY_HOST, HTTP_PROXY_PORT, HTTPS_PROXY_HOST, HTTPS_PROXY_PORT, NON_PROXY_HOSTS, JVM_ARCH, + OS_NAME) + .forEach(systemPropertyName -> { + final String systemPropertyValue = System.getProperty(systemPropertyName); + if (systemPropertyValue != null) { + expectedSystemProperties.put(systemPropertyName, systemPropertyValue); + } + }); + assertThatTasksAreConfigured(project, extension, expectedSystemProperties); } @Test + @SetSystemProperty(key = HTTP_PROXY_HOST, value = "104.53.49.1") + @SetSystemProperty(key = HTTP_PROXY_PORT, value = "52") + @SetSystemProperty(key = HTTPS_PROXY_HOST, value = "239.19.0.5") + @SetSystemProperty(key = HTTPS_PROXY_PORT, value = "492") + @SetSystemProperty(key = NON_PROXY_HOSTS, value = "localhost|48.8.*|127.0.0.1") + @SetSystemProperty(key = JVM_ARCH, value = "x86 64 bits") + @SetSystemProperty(key = OS_NAME, value = "FreeOS") void should_register_tasks_with_custom_extension_values_applied() throws IOException { final String nodeInstallDirectoryName = "node-dist"; Files.createDirectory(project.file(nodeInstallDirectoryName).toPath()); @@ -140,17 +149,23 @@ void should_register_tasks_with_custom_extension_values_applied() throws IOExcep extension.getRetryInitialIntervalMs().set(539); extension.getRetryIntervalMultiplier().set(7.3); extension.getRetryMaxIntervalMs().set(9623); + extension.getRetryMaxIntervalMs().set(9623); + extension.getCacheDirectory().set(project.file("cache")); extension.getVerboseModeEnabled().set(true); - assertThatTasksAreConfigured(project, extension); + assertThatTasksAreConfigured(project, extension, + Map.of(HTTP_PROXY_HOST, "104.53.49.1", HTTP_PROXY_PORT, "52", HTTPS_PROXY_HOST, "239.19.0.5", + HTTPS_PROXY_PORT, "492", NON_PROXY_HOSTS, "localhost|48.8.*|127.0.0.1", JVM_ARCH, "x86 64 bits", + OS_NAME, "FreeOS")); } - private void assertThatTasksAreConfigured(final Project project, final FrontendExtension extension) { - + private void assertThatTasksAreConfigured(final Project project, final FrontendExtension extension, + final Map expectedSystemProperties) { final InstallNodeTask installNodeTask = project .getTasks() - .named(FrontendGradlePlugin.INSTALL_NODE_TASK_NAME, InstallNodeTask.class) + .named(INSTALL_NODE_TASK_NAME, InstallNodeTask.class) .get(); + assertThat(installNodeTask.getBeanRegistryBuildService().get().getBeanRegistry()).isNotNull(); assertThat(installNodeTask.getNodeVersion().getOrNull()).isEqualTo(extension.getNodeVersion().getOrNull()); assertThat(installNodeTask.getNodeDistributionUrlRoot().get()).isEqualTo( extension.getNodeDistributionUrlRoot().get()); @@ -184,93 +199,155 @@ private void assertThatTasksAreConfigured(final Project project, final FrontendE extension.getRetryIntervalMultiplier().getOrNull()); assertThat(installNodeTask.getRetryMaxIntervalMs().getOrNull()).isEqualTo( extension.getRetryMaxIntervalMs().getOrNull()); + assertThat(installNodeTask.getVerboseModeEnabled().get()).isEqualTo(extension.getVerboseModeEnabled().get()); + assertThat(installNodeTask.getSystemHttpProxyHost().getOrNull()).isEqualTo( + expectedSystemProperties.get(HTTP_PROXY_HOST)); + assertThat(installNodeTask.getSystemHttpProxyPort().get()).isEqualTo(Optional + .ofNullable(expectedSystemProperties.get(HTTP_PROXY_PORT)) + .map(Integer::parseInt) + .orElse(DEFAULT_HTTP_PROXY_PORT)); + assertThat(installNodeTask.getSystemHttpsProxyHost().getOrNull()).isEqualTo( + expectedSystemProperties.get(HTTPS_PROXY_HOST)); + assertThat(installNodeTask.getSystemHttpsProxyPort().get()).isEqualTo(Optional + .ofNullable(expectedSystemProperties.get(HTTPS_PROXY_PORT)) + .map(Integer::parseInt) + .orElse(DEFAULT_HTTPS_PROXY_PORT)); + assertThat(installNodeTask.getSystemNonProxyHosts().get()).isEqualTo(Optional + .ofNullable(expectedSystemProperties.get(NON_PROXY_HOSTS)) + .map(nonProxyHosts -> Set.of(nonProxyHosts.split(NON_PROXY_HOSTS_SPLIT_PATTERN))) + .orElseGet(Set::of)); + assertThat(installNodeTask.getSystemJvmArch().get()).isEqualTo(expectedSystemProperties.get(JVM_ARCH)); + assertThat(installNodeTask.getSystemOsName().get()).isEqualTo(expectedSystemProperties.get(OS_NAME)); assertThat(installNodeTask.getDependsOn()).isEmpty(); final ResolvePackageManagerTask resolvePackageManagerTask = project .getTasks() - .named(FrontendGradlePlugin.RESOLVE_PACKAGE_MANAGER_TASK_NAME, ResolvePackageManagerTask.class) + .named(RESOLVE_PACKAGE_MANAGER_TASK_NAME, ResolvePackageManagerTask.class) .get(); - assertThat(resolvePackageManagerTask.getPackageJsonFile().getAsFile().get()).isEqualTo(extension + assertThat(resolvePackageManagerTask.getBeanRegistryBuildService().get().getBeanRegistry()).isNotNull(); + assertThat(resolvePackageManagerTask.getPackageJsonFile().getAsFile().getOrNull()).isEqualTo(extension .getPackageJsonDirectory() - .file(FrontendGradlePlugin.PACKAGE_JSON_FILE_NAME) + .file(PACKAGE_JSON_FILE_NAME) .map(RegularFile::getAsFile) + .filter(packageJsonFile -> Files.isRegularFile(packageJsonFile.toPath())) + .getOrNull()); + assertThat(resolvePackageManagerTask.getNodeInstallDirectory().get()).isEqualTo( + extension.getNodeInstallDirectory().getAsFile().get()); + assertThat(resolvePackageManagerTask.getPackageManagerSpecificationFile().getAsFile().get()).isEqualTo(extension + .getCacheDirectory() + .dir(RESOLVE_PACKAGE_MANAGER_TASK_NAME) + .map(directory -> directory.file(PACKAGE_MANAGER_SPECIFICATION_FILE_NAME).getAsFile()) .get()); - assertThat(resolvePackageManagerTask.getNodeInstallDirectory().isPresent()).isTrue(); - assertThat(resolvePackageManagerTask.getPackageManagerSpecificationFile().getAsFile().get()).isEqualTo( - extension.getInternalPackageManagerSpecificationFile().getAsFile().get()); assertThat(resolvePackageManagerTask.getPackageManagerExecutablePathFile().getAsFile().get()).isEqualTo( - extension.getInternalPackageManagerExecutablePathFile().getAsFile().get()); - assertThat(installNodeTask.getDependsOn()).isEmpty(); - assertThat(resolvePackageManagerTask.getDependsOn()).containsExactlyInAnyOrder( - FrontendGradlePlugin.INSTALL_NODE_TASK_NAME); + extension + .getCacheDirectory() + .dir(RESOLVE_PACKAGE_MANAGER_TASK_NAME) + .map(directory -> directory.file(PACKAGE_MANAGER_EXECUTABLE_PATH_FILE_NAME).getAsFile()) + .get()); + assertThat(resolvePackageManagerTask.getVerboseModeEnabled().get()).isEqualTo( + extension.getVerboseModeEnabled().get()); + assertThat(resolvePackageManagerTask.getSystemJvmArch().get()).isEqualTo( + expectedSystemProperties.get(JVM_ARCH)); + assertThat(resolvePackageManagerTask.getSystemOsName().get()).isEqualTo(expectedSystemProperties.get(OS_NAME)); + assertThat(resolvePackageManagerTask.getDependsOn()).containsExactlyInAnyOrder(INSTALL_NODE_TASK_NAME); final InstallPackageManagerTask installPackageManagerTask = project .getTasks() - .named(FrontendGradlePlugin.INSTALL_PACKAGE_MANAGER_TASK_NAME, InstallPackageManagerTask.class) + .named(INSTALL_PACKAGE_MANAGER_TASK_NAME, InstallPackageManagerTask.class) .get(); + assertThat(installPackageManagerTask.getBeanRegistryBuildService().get().getBeanRegistry()).isNotNull(); assertThat(installPackageManagerTask.getPackageJsonDirectory().get()).isEqualTo( extension.getPackageJsonDirectory().getAsFile().get()); - assertThat(installPackageManagerTask.getNodeInstallDirectory().isPresent()).isTrue(); + assertThat(installPackageManagerTask.getNodeInstallDirectory().get()).isEqualTo( + extension.getNodeInstallDirectory().getAsFile().get()); assertThat(installPackageManagerTask.getPackageManagerSpecificationFile().getAsFile().get()).isEqualTo( - extension.getInternalPackageManagerSpecificationFile().getAsFile().get()); + resolvePackageManagerTask.getPackageManagerSpecificationFile().getAsFile().get()); + //installPackageManagerTask.getPackageManagerExecutableFile() + assertThat(installPackageManagerTask.getVerboseModeEnabled().get()).isEqualTo( + extension.getVerboseModeEnabled().get()); + assertThat(installPackageManagerTask.getSystemJvmArch().get()).isEqualTo( + expectedSystemProperties.get(JVM_ARCH)); + assertThat(installPackageManagerTask.getSystemOsName().get()).isEqualTo(expectedSystemProperties.get(OS_NAME)); assertThat(installPackageManagerTask.getDependsOn()).isEmpty(); final InstallFrontendTask installFrontendTask = project .getTasks() - .named(FrontendGradlePlugin.INSTALL_FRONTEND_TASK_NAME, InstallFrontendTask.class) + .named(INSTALL_FRONTEND_TASK_NAME, InstallFrontendTask.class) .get(); + assertThat(installFrontendTask.getBeanRegistryBuildService().get().getBeanRegistry()).isNotNull(); assertThat(installFrontendTask.getPackageJsonDirectory().get()).isEqualTo( extension.getPackageJsonDirectory().getAsFile().get()); - assertThat(installFrontendTask.getNodeInstallDirectory().isPresent()).isTrue(); + assertThat(installFrontendTask.getNodeInstallDirectory().get()).isEqualTo( + extension.getNodeInstallDirectory().getAsFile().get()); + //installFrontendTask.getPackageManagerExecutableFile() assertThat(installFrontendTask.getInstallScript().get()).isEqualTo(extension.getInstallScript().get()); + assertThat(installFrontendTask.getVerboseModeEnabled().get()).isEqualTo( + extension.getVerboseModeEnabled().get()); + assertThat(installFrontendTask.getSystemJvmArch().get()).isEqualTo(expectedSystemProperties.get(JVM_ARCH)); + assertThat(installFrontendTask.getSystemOsName().get()).isEqualTo(expectedSystemProperties.get(OS_NAME)); assertThat(installFrontendTask.getDependsOn()).containsExactlyInAnyOrder(installPackageManagerTask.getName()); - final CleanTask frontendCleanTask = project - .getTasks() - .named(FrontendGradlePlugin.CLEAN_TASK_NAME, CleanTask.class) - .get(); + final CleanTask frontendCleanTask = project.getTasks().named(CLEAN_TASK_NAME, CleanTask.class).get(); + assertThat(frontendCleanTask.getBeanRegistryBuildService().get().getBeanRegistry()).isNotNull(); assertThat(frontendCleanTask.getPackageJsonDirectory().get()).isEqualTo( extension.getPackageJsonDirectory().getAsFile().get()); - assertThat(frontendCleanTask.getNodeInstallDirectory().isPresent()).isTrue(); + assertThat(frontendCleanTask.getNodeInstallDirectory().get()).isEqualTo( + extension.getNodeInstallDirectory().getAsFile().get()); + //frontendCleanTask.getPackageManagerExecutableFile() assertThat(frontendCleanTask.getCleanScript().getOrNull()).isEqualTo(extension.getCleanScript().getOrNull()); + assertThat(frontendCleanTask.getVerboseModeEnabled().get()).isEqualTo(extension.getVerboseModeEnabled().get()); + assertThat(frontendCleanTask.getSystemJvmArch().get()).isEqualTo(expectedSystemProperties.get(JVM_ARCH)); + assertThat(frontendCleanTask.getSystemOsName().get()).isEqualTo(expectedSystemProperties.get(OS_NAME)); assertThat(frontendCleanTask.getDependsOn()).containsExactlyInAnyOrder(installFrontendTask.getName()); assertThat(project.getTasks().named(BasePlugin.CLEAN_TASK_NAME).get().getDependsOn()).contains( frontendCleanTask.getName()); final AssembleTask frontendAssembleTask = project .getTasks() - .named(FrontendGradlePlugin.ASSEMBLE_TASK_NAME, AssembleTask.class) + .named(ASSEMBLE_TASK_NAME, AssembleTask.class) .get(); + assertThat(frontendAssembleTask.getBeanRegistryBuildService().get().getBeanRegistry()).isNotNull(); assertThat(frontendAssembleTask.getPackageJsonDirectory().get()).isEqualTo( extension.getPackageJsonDirectory().getAsFile().get()); - assertThat(frontendAssembleTask.getNodeInstallDirectory().isPresent()).isTrue(); + //frontendAssembleTask.getPackageManagerExecutableFile() assertThat(frontendAssembleTask.getAssembleScript().getOrNull()).isEqualTo( extension.getAssembleScript().getOrNull()); + assertThat(frontendAssembleTask.getVerboseModeEnabled().get()).isEqualTo( + extension.getVerboseModeEnabled().get()); + assertThat(frontendAssembleTask.getSystemJvmArch().get()).isEqualTo(expectedSystemProperties.get(JVM_ARCH)); + assertThat(frontendAssembleTask.getSystemOsName().get()).isEqualTo(expectedSystemProperties.get(OS_NAME)); assertThat(frontendAssembleTask.getDependsOn()).containsExactlyInAnyOrder(installFrontendTask.getName()); assertThat(project.getTasks().named(BasePlugin.ASSEMBLE_TASK_NAME).get().getDependsOn()).contains( frontendAssembleTask.getName()); - final CheckTask frontendCheckTask = project - .getTasks() - .named(FrontendGradlePlugin.CHECK_TASK_NAME, CheckTask.class) - .get(); + final CheckTask frontendCheckTask = project.getTasks().named(CHECK_TASK_NAME, CheckTask.class).get(); + assertThat(frontendCheckTask.getBeanRegistryBuildService().get().getBeanRegistry()).isNotNull(); assertThat(frontendCheckTask.getPackageJsonDirectory().get()).isEqualTo( extension.getPackageJsonDirectory().getAsFile().get()); - assertThat(frontendCheckTask.getNodeInstallDirectory().isPresent()).isTrue(); + assertThat(frontendCheckTask.getNodeInstallDirectory().get()).isEqualTo( + extension.getNodeInstallDirectory().getAsFile().get()); + //frontendCheckTask.getPackageManagerExecutableFile() assertThat(frontendCheckTask.getCheckScript().getOrNull()).isEqualTo(extension.getCheckScript().getOrNull()); + assertThat(frontendCheckTask.getVerboseModeEnabled().get()).isEqualTo(extension.getVerboseModeEnabled().get()); + assertThat(frontendCheckTask.getSystemJvmArch().get()).isEqualTo(expectedSystemProperties.get(JVM_ARCH)); + assertThat(frontendCheckTask.getSystemOsName().get()).isEqualTo(expectedSystemProperties.get(OS_NAME)); assertThat(frontendCheckTask.getDependsOn()).containsExactlyInAnyOrder(installFrontendTask.getName()); assertThat(project.getTasks().named(LifecycleBasePlugin.CHECK_TASK_NAME).get().getDependsOn()).contains( frontendCheckTask.getName()); - final PublishTask frontendPublishTask = project - .getTasks() - .named(FrontendGradlePlugin.PUBLISH_TASK_NAME, PublishTask.class) - .get(); + final PublishTask frontendPublishTask = project.getTasks().named(PUBLISH_TASK_NAME, PublishTask.class).get(); + assertThat(frontendPublishTask.getBeanRegistryBuildService().get().getBeanRegistry()).isNotNull(); assertThat(frontendPublishTask.getPackageJsonDirectory().get()).isEqualTo( extension.getPackageJsonDirectory().getAsFile().get()); - assertThat(frontendPublishTask.getNodeInstallDirectory().isPresent()).isTrue(); + assertThat(frontendPublishTask.getNodeInstallDirectory().get()).isEqualTo( + extension.getNodeInstallDirectory().getAsFile().get()); + //frontendPublishTask.getPackageManagerExecutableFile() assertThat(frontendPublishTask.getPublishScript().getOrNull()).isEqualTo( extension.getPublishScript().getOrNull()); + assertThat(frontendPublishTask.getVerboseModeEnabled().get()).isEqualTo( + extension.getVerboseModeEnabled().get()); + assertThat(frontendPublishTask.getSystemJvmArch().get()).isEqualTo(expectedSystemProperties.get(JVM_ARCH)); + assertThat(frontendPublishTask.getSystemOsName().get()).isEqualTo(expectedSystemProperties.get(OS_NAME)); assertThat(frontendPublishTask.getDependsOn()).containsExactlyInAnyOrder(frontendAssembleTask.getName()); assertThat( project.getTasks().named(PublishingPlugin.PUBLISH_LIFECYCLE_TASK_NAME).get().getDependsOn()).contains( diff --git a/plugin/src/test/java/org/siouan/frontendgradleplugin/domain/SystemPropertiesFixture.java b/plugin/src/test/java/org/siouan/frontendgradleplugin/domain/SystemPropertiesFixture.java index 76c8abb1..518f5366 100644 --- a/plugin/src/test/java/org/siouan/frontendgradleplugin/domain/SystemPropertiesFixture.java +++ b/plugin/src/test/java/org/siouan/frontendgradleplugin/domain/SystemPropertiesFixture.java @@ -19,7 +19,7 @@ public final class SystemPropertiesFixture { * @return String describing the JVM architecture. */ public static String getSystemJvmArch() { - return getPropertyAndAssertNotNull(SystemProperties.JVM_ARCH_PROPERTY); + return getPropertyAndAssertNotNull(SystemProperties.JVM_ARCH); } /** @@ -28,7 +28,7 @@ public static String getSystemJvmArch() { * @return String describing the O/S. */ public static String getSystemOsName() { - return getPropertyAndAssertNotNull(SystemProperties.OS_NAME_PROPERTY); + return getPropertyAndAssertNotNull(SystemProperties.OS_NAME); } private static String getPropertyAndAssertNotNull(final String property) { diff --git a/plugin/src/test/java/org/siouan/frontendgradleplugin/domain/installer/ResolveProxySettingsByUrlTest.java b/plugin/src/test/java/org/siouan/frontendgradleplugin/domain/installer/ResolveProxySettingsByUrlTest.java index fce773cc..b621f4d1 100644 --- a/plugin/src/test/java/org/siouan/frontendgradleplugin/domain/installer/ResolveProxySettingsByUrlTest.java +++ b/plugin/src/test/java/org/siouan/frontendgradleplugin/domain/installer/ResolveProxySettingsByUrlTest.java @@ -16,7 +16,6 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.siouan.frontendgradleplugin.domain.SystemSettingsProvider; @ExtendWith(MockitoExtension.class) class ResolveProxySettingsByUrlTest { @@ -40,9 +39,6 @@ class ResolveProxySettingsByUrlTest { private static final int SYSTEM_PROXY_PORT = 233; - @Mock - private SystemSettingsProvider systemSettingsProvider; - @Mock private IsNonProxyHost isNonProxyHost; @@ -64,7 +60,7 @@ void should_fail_when_url_uses_unsupported_protocol() throws MalformedURLExcepti assertThatThrownBy(() -> usecase.execute(command)).isInstanceOf(IllegalArgumentException.class); - verifyNoMoreInteractions(systemSettingsProvider, isNonProxyHost, selectProxySettings); + verifyNoMoreInteractions(isNonProxyHost, selectProxySettings); } @Test @@ -78,13 +74,12 @@ void should_return_direct_connection_when_url_uses_file_protocol() throws Malfor .build()) .getProxyType()).isEqualTo(Proxy.Type.DIRECT); - verifyNoMoreInteractions(systemSettingsProvider, isNonProxyHost, selectProxySettings); + verifyNoMoreInteractions(isNonProxyHost, selectProxySettings); } @Test void should_return_direct_connection_when_url_uses_non_proxy_host() throws MalformedURLException { final Set nonProxyHosts = Set.of(PLUGIN_PROXY_HOST); - when(systemSettingsProvider.getNonProxyHosts()).thenReturn(nonProxyHosts); final URL resourceUrl = new URL(HTTP_RESOURCE_URL); when(isNonProxyHost.execute(IsNonProxyHostCommand .builder() @@ -98,16 +93,16 @@ void should_return_direct_connection_when_url_uses_non_proxy_host() throws Malfo .httpProxyPort(80) .httpsProxyPort(443) .resourceUrl(resourceUrl) + .systemNonProxyHosts(nonProxyHosts) .build()) .getProxyType()).isEqualTo(Proxy.Type.DIRECT); - verifyNoMoreInteractions(systemSettingsProvider, isNonProxyHost, selectProxySettings); + verifyNoMoreInteractions(isNonProxyHost, selectProxySettings); } @Test void should_return_http_proxy_settings_when_url_uses_non_secure_http_protocol() throws MalformedURLException { final Set nonProxyHosts = Set.of(); - when(systemSettingsProvider.getNonProxyHosts()).thenReturn(nonProxyHosts); final URL resourceUrl = new URL(HTTP_RESOURCE_URL); when(isNonProxyHost.execute(IsNonProxyHostCommand .builder() @@ -116,8 +111,6 @@ void should_return_http_proxy_settings_when_url_uses_non_secure_http_protocol() .build())).thenReturn(false); final String systemProxyHost = SYSTEM_PROXY_HOST; final int systemProxyPort = SYSTEM_PROXY_PORT; - when(systemSettingsProvider.getHttpProxyHost()).thenReturn(systemProxyHost); - when(systemSettingsProvider.getHttpProxyPort()).thenReturn(systemProxyPort); final String proxyHost = PLUGIN_PROXY_HOST; final int proxyPort = PLUGIN_PROXY_PORT; final ProxySettings proxySettings = someProxySettings(); @@ -137,15 +130,17 @@ void should_return_http_proxy_settings_when_url_uses_non_secure_http_protocol() .httpProxyHost(proxyHost) .httpProxyPort(proxyPort) .httpProxyCredentials(proxyCredentials) + .systemNonProxyHosts(nonProxyHosts) + .systemHttpProxyHost(systemProxyHost) + .systemHttpProxyPort(systemProxyPort) .build())).isEqualTo(proxySettings); - verifyNoMoreInteractions(systemSettingsProvider, isNonProxyHost, selectProxySettings); + verifyNoMoreInteractions(isNonProxyHost, selectProxySettings); } @Test void should_return_https_proxy_settings_when_url_uses_secure_http_protocol() throws MalformedURLException { final Set nonProxyHosts = Set.of(); - when(systemSettingsProvider.getNonProxyHosts()).thenReturn(nonProxyHosts); final URL resourceUrl = new URL(HTTPS_RESOURCE_URL); when(isNonProxyHost.execute(IsNonProxyHostCommand .builder() @@ -154,8 +149,6 @@ void should_return_https_proxy_settings_when_url_uses_secure_http_protocol() thr .build())).thenReturn(false); final String systemProxyHost = SYSTEM_PROXY_HOST; final int systemProxyPort = SYSTEM_PROXY_PORT; - when(systemSettingsProvider.getHttpsProxyHost()).thenReturn(systemProxyHost); - when(systemSettingsProvider.getHttpsProxyPort()).thenReturn(systemProxyPort); final String proxyHost = PLUGIN_PROXY_HOST; final int proxyPort = PLUGIN_PROXY_PORT; final ProxySettings proxySettings = someProxySettings(); @@ -175,8 +168,11 @@ void should_return_https_proxy_settings_when_url_uses_secure_http_protocol() thr .httpsProxyHost(proxyHost) .httpsProxyPort(proxyPort) .httpsProxyCredentials(proxyCredentials) + .systemNonProxyHosts(nonProxyHosts) + .systemHttpsProxyHost(systemProxyHost) + .systemHttpsProxyPort(systemProxyPort) .build())).isEqualTo(proxySettings); - verifyNoMoreInteractions(systemSettingsProvider, isNonProxyHost, selectProxySettings); + verifyNoMoreInteractions(isNonProxyHost, selectProxySettings); } } diff --git a/plugin/src/test/java/org/siouan/frontendgradleplugin/infrastructure/bean/BeanRegistryTest.java b/plugin/src/test/java/org/siouan/frontendgradleplugin/infrastructure/bean/BeanRegistryTest.java index 7458f3c0..88b5be9a 100644 --- a/plugin/src/test/java/org/siouan/frontendgradleplugin/infrastructure/bean/BeanRegistryTest.java +++ b/plugin/src/test/java/org/siouan/frontendgradleplugin/infrastructure/bean/BeanRegistryTest.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import lombok.Getter; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -23,7 +24,7 @@ void should_register_the_registry_itself() @Test void should_not_replace_registry_when_registering_other_registry_class() throws BeanInstanciationException, TooManyCandidateBeansException, ZeroOrMultiplePublicConstructorsException { - beanRegistry.registerBean(BeanRegistry.class); + beanRegistry.registerBeanClass(BeanRegistry.class); assertThat(beanRegistry.getBean(BeanRegistry.class)).isSameAs(beanRegistry); } @@ -134,11 +135,11 @@ void should_get_same_instance_when_registering_bean_class_multiple_times() void should_get_same_instance_when_registering_bean_instance_multiple_times() throws BeanInstanciationException, TooManyCandidateBeansException, ZeroOrMultiplePublicConstructorsException { // First call triggers internally bean registration and instanciation. - beanRegistry.registerBean(DefaultPublicConstructorBean.class); + beanRegistry.registerBeanClass(DefaultPublicConstructorBean.class); // Second call shall return exactly the same bean. final DefaultPublicConstructorBean bean1 = beanRegistry.getBean(DefaultPublicConstructorBean.class); // Third call shall return exactly the same bean, even if we tried to register another instance. - beanRegistry.registerBean(DefaultPublicConstructorBean.class); + beanRegistry.registerBeanClass(DefaultPublicConstructorBean.class); final DefaultPublicConstructorBean bean2 = beanRegistry.getBean(DefaultPublicConstructorBean.class); assertThat(bean1).isNotNull(); @@ -210,6 +211,7 @@ public BeanWithParameterConstructorWithException(final BeanWithConstructorExcept } } + @Getter private static class PublicConstructorWithValidParameterBean { private final DefaultPublicConstructorBean parameter; @@ -217,10 +219,6 @@ private static class PublicConstructorWithValidParameterBean { public PublicConstructorWithValidParameterBean(final DefaultPublicConstructorBean parameter) { this.parameter = parameter; } - - public DefaultPublicConstructorBean getParameter() { - return parameter; - } } private static class Parent {} diff --git a/plugin/src/test/java/org/siouan/frontendgradleplugin/infrastructure/gradle/SystemProvidersTest.java b/plugin/src/test/java/org/siouan/frontendgradleplugin/infrastructure/gradle/SystemProvidersTest.java new file mode 100644 index 00000000..aef6a5a7 --- /dev/null +++ b/plugin/src/test/java/org/siouan/frontendgradleplugin/infrastructure/gradle/SystemProvidersTest.java @@ -0,0 +1,70 @@ +package org.siouan.frontendgradleplugin.infrastructure.gradle; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import org.gradle.api.internal.provider.DefaultProviderFactory; +import org.gradle.api.provider.Provider; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.siouan.frontendgradleplugin.domain.SystemProperties; + +@ExtendWith(MockitoExtension.class) +class SystemProvidersTest { + + @Mock + private Provider httpProxyHostProvider; + + @Mock + private Provider httpProxyPortProvider; + + @Mock + private Provider httpsProxyHostProvider; + + @Mock + private Provider httpsProxyPortProvider; + + @Mock + private Provider nonProxyHosts; + + @Mock + private Provider jvmArchProvider; + + @Mock + private Provider osNameProvider; + + @Mock + private DefaultProviderFactory providerFactory; + + private SystemProviders systemProviders; + + @BeforeEach + void setUp() { + when(providerFactory.systemProperty(SystemProperties.HTTP_PROXY_HOST)).thenReturn(httpProxyHostProvider); + when(providerFactory.systemProperty(SystemProperties.HTTP_PROXY_PORT)).thenReturn(httpProxyPortProvider); + when(providerFactory.systemProperty(SystemProperties.HTTPS_PROXY_HOST)).thenReturn(httpsProxyHostProvider); + when(providerFactory.systemProperty(SystemProperties.HTTPS_PROXY_PORT)).thenReturn(httpsProxyPortProvider); + when(providerFactory.systemProperty(SystemProperties.NON_PROXY_HOSTS)).thenReturn(nonProxyHosts); + when(providerFactory.systemProperty(SystemProperties.JVM_ARCH)).thenReturn(jvmArchProvider); + when(providerFactory.systemProperty(SystemProperties.OS_NAME)).thenReturn(osNameProvider); + systemProviders = new SystemProviders(providerFactory); + } + + @Test + void should_return_system_properties() { + assertThat(systemProviders.getHttpProxyHost()).isEqualTo(httpProxyHostProvider); + assertThat(systemProviders.getHttpProxyPort()).isEqualTo(httpProxyPortProvider); + assertThat(systemProviders.getHttpsProxyHost()).isEqualTo(httpsProxyHostProvider); + assertThat(systemProviders.getHttpsProxyPort()).isEqualTo(httpsProxyPortProvider); + assertThat(systemProviders.getNonProxyHosts()).isEqualTo(nonProxyHosts); + assertThat(systemProviders.getJvmArch()).isEqualTo(jvmArchProvider); + assertThat(systemProviders.getOsName()).isEqualTo(osNameProvider); + + verifyNoMoreInteractions(httpProxyHostProvider, httpProxyPortProvider, httpsProxyHostProvider, + httpsProxyPortProvider, nonProxyHosts, jvmArchProvider, osNameProvider); + } +} diff --git a/plugin/src/test/java/org/siouan/frontendgradleplugin/infrastructure/gradle/SystemSettingsProviderImplTest.java b/plugin/src/test/java/org/siouan/frontendgradleplugin/infrastructure/gradle/SystemSettingsProviderImplTest.java deleted file mode 100644 index fd473acd..00000000 --- a/plugin/src/test/java/org/siouan/frontendgradleplugin/infrastructure/gradle/SystemSettingsProviderImplTest.java +++ /dev/null @@ -1,149 +0,0 @@ -package org.siouan.frontendgradleplugin.infrastructure.gradle; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; -import static org.siouan.frontendgradleplugin.domain.SystemPropertiesFixture.getSystemJvmArch; -import static org.siouan.frontendgradleplugin.domain.SystemPropertiesFixture.getSystemOsName; - -import org.gradle.api.provider.Provider; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junitpioneer.jupiter.ClearSystemProperty; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.siouan.frontendgradleplugin.domain.SystemProperties; - -@ExtendWith(MockitoExtension.class) -class SystemSettingsProviderImplTest { - - private static final int DEFAULT_HTTP_PROXY_PORT = 80; - - private static final int DEFAULT_HTTPS_PROXY_PORT = 443; - - @Mock - private Provider stringProvider; - - @Mock - private SystemExtension systemExtension; - - private SystemSettingsProviderImpl systemSettingsProvider; - - @BeforeEach - void setUp() { - when(systemExtension.getJvmArch()).thenReturn(stringProvider); - when(systemExtension.getOsName()).thenReturn(stringProvider); - when(systemExtension.getHttpProxyHost()).thenReturn(stringProvider); - when(systemExtension.getHttpProxyPort()).thenReturn(stringProvider); - when(systemExtension.getHttpsProxyHost()).thenReturn(stringProvider); - when(systemExtension.getHttpsProxyPort()).thenReturn(stringProvider); - when(systemExtension.getNonProxyHosts()).thenReturn(stringProvider); - systemSettingsProvider = new SystemSettingsProviderImpl(systemExtension, DEFAULT_HTTP_PROXY_PORT, - DEFAULT_HTTPS_PROXY_PORT); - } - - @Test - void should_return_jvm_arch_from_extension() { - final String jvmArch = getSystemJvmArch(); - when(stringProvider.get()).thenReturn(jvmArch); - - assertThat(systemSettingsProvider.getSystemJvmArch()).isEqualTo(jvmArch); - - verifyNoMoreInteractions(systemExtension, stringProvider); - } - - @Test - void should_return_os_name_from_extension() { - final String osName = getSystemOsName(); - when(stringProvider.get()).thenReturn(osName); - - assertThat(systemSettingsProvider.getSystemOsName()).isEqualTo(osName); - - verifyNoMoreInteractions(systemExtension, stringProvider); - } - - @Test - @ClearSystemProperty(key = SystemProperties.HTTP_PROXY_HOST) - void should_return_http_proxy_host_from_extension() { - final String httpProxyHost = "http-proxy"; - when(stringProvider.getOrNull()).thenReturn(httpProxyHost); - - assertThat(systemSettingsProvider.getHttpProxyHost()).isEqualTo(httpProxyHost); - - verifyNoMoreInteractions(systemExtension, stringProvider); - } - - @Test - @ClearSystemProperty(key = SystemProperties.HTTP_PROXY_HOST) - void should_return_no_http_proxy_port_from_extension() { - when(stringProvider.getOrNull()).thenReturn(null); - - assertThat(systemSettingsProvider.getHttpProxyPort()).isEqualTo(DEFAULT_HTTP_PROXY_PORT); - - verifyNoMoreInteractions(systemExtension, stringProvider); - } - - @Test - @ClearSystemProperty(key = SystemProperties.HTTP_PROXY_PORT) - void should_return_http_proxy_port_when_non_blank() { - final String httpProxyPort = "743"; - when(stringProvider.getOrNull()).thenReturn(httpProxyPort); - - assertThat(systemSettingsProvider.getHttpProxyPort()).hasToString(httpProxyPort); - - verifyNoMoreInteractions(systemExtension, stringProvider); - } - - @Test - @ClearSystemProperty(key = SystemProperties.HTTP_PROXY_PORT) - void should_return_no_http_non_proxy_hosts_when_system_property_is_null_or_blank() { - when(stringProvider.getOrNull()).thenReturn(null); - - assertThat(systemSettingsProvider.getNonProxyHosts()).isEmpty(); - - verifyNoMoreInteractions(systemExtension, stringProvider); - } - - @Test - @ClearSystemProperty(key = SystemProperties.NON_PROXY_HOSTS) - void should_return_http_non_proxy_hosts_when_system_property_is_defined() { - when(stringProvider.getOrNull()).thenReturn("localhost|127.*|[::1]"); - - assertThat(systemSettingsProvider.getNonProxyHosts()).containsExactlyInAnyOrder("localhost", "127.*", "[::1]"); - - verifyNoMoreInteractions(systemExtension, stringProvider); - } - - @Test - @ClearSystemProperty(key = SystemProperties.HTTPS_PROXY_HOST) - void should_return_https_proxy_host_from_extension() { - final String httpsProxyHost = "https-proxy"; - when(stringProvider.getOrNull()).thenReturn(httpsProxyHost); - - assertThat(systemSettingsProvider.getHttpsProxyHost()).isEqualTo(httpsProxyHost); - - verifyNoMoreInteractions(systemExtension, stringProvider); - } - - @Test - @ClearSystemProperty(key = SystemProperties.HTTPS_PROXY_PORT) - void should_return_no_https_proxy_port_from_extension() { - when(stringProvider.getOrNull()).thenReturn(null); - - assertThat(systemSettingsProvider.getHttpsProxyPort()).isEqualTo(DEFAULT_HTTPS_PROXY_PORT); - - verifyNoMoreInteractions(systemExtension, stringProvider); - } - - @Test - @ClearSystemProperty(key = SystemProperties.HTTPS_PROXY_PORT) - void should_return_https_proxy_port_when_non_blank() { - final String httpsProxyPort = "1988"; - when(stringProvider.getOrNull()).thenReturn(httpsProxyPort); - - assertThat(systemSettingsProvider.getHttpsProxyPort()).hasToString(httpsProxyPort); - - verifyNoMoreInteractions(systemExtension, stringProvider); - } -} diff --git a/plugin/src/test/java/org/siouan/frontendgradleplugin/infrastructure/gradle/TaskLoggerConfigurerTest.java b/plugin/src/test/java/org/siouan/frontendgradleplugin/infrastructure/gradle/TaskLoggerInitializerTest.java similarity index 54% rename from plugin/src/test/java/org/siouan/frontendgradleplugin/infrastructure/gradle/TaskLoggerConfigurerTest.java rename to plugin/src/test/java/org/siouan/frontendgradleplugin/infrastructure/gradle/TaskLoggerInitializerTest.java index 7746f920..2c6dafa0 100644 --- a/plugin/src/test/java/org/siouan/frontendgradleplugin/infrastructure/gradle/TaskLoggerConfigurerTest.java +++ b/plugin/src/test/java/org/siouan/frontendgradleplugin/infrastructure/gradle/TaskLoggerInitializerTest.java @@ -10,23 +10,18 @@ import org.gradle.api.logging.LogLevel; import org.gradle.api.logging.Logger; import org.gradle.api.logging.LoggingManager; -import org.gradle.api.provider.Property; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class TaskLoggerConfigurerTest { +class TaskLoggerInitializerTest { private static final String TASK_NAME = "task"; private static final LogLevel LOGGING_LEVEL = LogLevel.WARN; - @Mock - private Property verboseModeEnabled; - @Mock private Logger gradleLogger; @@ -37,30 +32,22 @@ class TaskLoggerConfigurerTest { private LoggingManager taskLoggingManager; @Mock - private GradleLoggerAdapter adapter; - - @Mock - private FrontendExtension extension; + private GradleLoggerAdapter gradleLoggerAdapter; @Mock private GradleSettings gradleSettings; - @InjectMocks - private TaskLoggerConfigurer taskLoggerConfigurer; - @Test void should_init_logger_before_task_execution_with_task_level() { when(task.getName()).thenReturn(TASK_NAME); when(task.getLogger()).thenReturn(gradleLogger); when(task.getLogging()).thenReturn(taskLoggingManager); when(taskLoggingManager.getLevel()).thenReturn(LOGGING_LEVEL); - when(extension.getVerboseModeEnabled()).thenReturn(verboseModeEnabled); - when(verboseModeEnabled.get()).thenReturn(true); - taskLoggerConfigurer.initLoggerAdapter(task); + TaskLoggerInitializer.initAdapter(task, true, gradleLoggerAdapter, gradleSettings); - verify(adapter).init(eq(gradleLogger), eq(LOGGING_LEVEL), eq(true), anyString()); - verifyNoMoreInteractions(task, taskLoggingManager, adapter, gradleSettings); + verify(gradleLoggerAdapter).init(eq(gradleLogger), eq(LOGGING_LEVEL), eq(true), anyString()); + verifyNoMoreInteractions(task, taskLoggingManager, gradleLoggerAdapter, gradleSettings); } @Test @@ -68,15 +55,13 @@ void should_init_logger_before_task_execution_with_project_level() { when(task.getName()).thenReturn(TASK_NAME); when(task.getLogger()).thenReturn(gradleLogger); when(task.getLogging()).thenReturn(taskLoggingManager); - when(gradleSettings.projectLogLevel()).thenReturn(LOGGING_LEVEL); - when(extension.getVerboseModeEnabled()).thenReturn(verboseModeEnabled); - when(verboseModeEnabled.get()).thenReturn(true); + when(gradleSettings.getProjectLogLevel()).thenReturn(LOGGING_LEVEL); - taskLoggerConfigurer.initLoggerAdapter(task); + TaskLoggerInitializer.initAdapter(task, true, gradleLoggerAdapter, gradleSettings); - verify(adapter).init(eq(gradleLogger), eq(LOGGING_LEVEL), eq(true), anyString()); + verify(gradleLoggerAdapter).init(eq(gradleLogger), eq(LOGGING_LEVEL), eq(true), anyString()); verify(taskLoggingManager).getLevel(); - verifyNoMoreInteractions(task, taskLoggingManager, adapter, gradleSettings); + verifyNoMoreInteractions(task, taskLoggingManager, gradleLoggerAdapter, gradleSettings); } @Test @@ -84,15 +69,13 @@ void should_init_logger_before_task_execution_with_gradle_start_level() { when(task.getName()).thenReturn(TASK_NAME); when(task.getLogger()).thenReturn(gradleLogger); when(task.getLogging()).thenReturn(taskLoggingManager); - when(gradleSettings.projectLogLevel()).thenReturn(null); - when(gradleSettings.commandLineLogLevel()).thenReturn(LOGGING_LEVEL); - when(extension.getVerboseModeEnabled()).thenReturn(verboseModeEnabled); - when(verboseModeEnabled.get()).thenReturn(true); + when(gradleSettings.getProjectLogLevel()).thenReturn(null); + when(gradleSettings.getCommandLineLogLevel()).thenReturn(LOGGING_LEVEL); - taskLoggerConfigurer.initLoggerAdapter(task); + TaskLoggerInitializer.initAdapter(task, true, gradleLoggerAdapter, gradleSettings); - verify(adapter).init(eq(gradleLogger), eq(LOGGING_LEVEL), eq(true), anyString()); + verify(gradleLoggerAdapter).init(eq(gradleLogger), eq(LOGGING_LEVEL), eq(true), anyString()); verify(taskLoggingManager).getLevel(); - verifyNoMoreInteractions(task, taskLoggingManager, adapter, gradleSettings); + verifyNoMoreInteractions(task, taskLoggingManager, gradleLoggerAdapter, gradleSettings); } } diff --git a/site/src/components/link/yarn-link.vue b/site/src/components/link/yarn-link.vue index b75345e3..63ffaece 100644 --- a/site/src/components/link/yarn-link.vue +++ b/site/src/components/link/yarn-link.vue @@ -1,10 +1,12 @@ diff --git a/site/src/components/property/node-install-directory-property.vue b/site/src/components/property/node-install-directory-property.vue index 8f2c7f41..4d05ad14 100644 --- a/site/src/components/property/node-install-directory-property.vue +++ b/site/src/components/property/node-install-directory-property.vue @@ -2,7 +2,6 @@ @@ -58,6 +67,7 @@ interface Input { readonly type: TaskPropertyTypeType; readonly binding: TaskPropertyBindingType; readonly property?: string; + readonly optionalHint?: string; } interface Output { @@ -66,6 +76,11 @@ interface Output { readonly binding: TaskPropertyBindingType; } +interface OutcomeHint { + readonly outcome: TaskOutcomeType; + readonly description: string; +} + interface Props { readonly name: string; readonly type?: boolean; @@ -73,6 +88,7 @@ interface Props { readonly inputs?: any[]; readonly outputs?: any[]; readonly cacheable?: boolean; + readonly outcomeHints?: OutcomeHint[]; } const slots = useSlots(); @@ -82,6 +98,7 @@ withDefaults(defineProps(), { inputs: () => [], outputs: () => [], cacheable: false, + outcomeHints: () => [] }); const skippable = computed(() => !!slots.skipConditions); diff --git a/site/src/components/v7/task/install-frontend-task-v7.vue b/site/src/components/v7/task/install-frontend-task-v7.vue index 9c43fb46..0db1b930 100644 --- a/site/src/components/v7/task/install-frontend-task-v7.vue +++ b/site/src/components/v7/task/install-frontend-task-v7.vue @@ -52,7 +52,7 @@ yarn.lock, while outputs may be the node_modules directory and the package-lock.json file (see - npm install). If + npm install). If the property is set with ci, files npm-shrinkwrap.json and package-lock.json may be the only possible input file, if one or the diff --git a/site/src/pages/7/index.vue b/site/src/pages/7/index.vue index 352ad693..2cb337a3 100644 --- a/site/src/pages/7/index.vue +++ b/site/src/pages/7/index.vue @@ -10,7 +10,7 @@ /> -

Build -based applications with

+

Build -based applications with and

This plugin allows to build a -based application relying on a package manager supported by diff --git a/site/src/pages/getting-started.vue b/site/src/pages/getting-started.vue index 662a8d07..8ad8e7a5 100644 --- a/site/src/pages/getting-started.vue +++ b/site/src/pages/getting-started.vue @@ -7,11 +7,11 @@

  • JDK 17 build: - 7.3+ + 8.5+
  • JDK 11 build: - 6.2+ + 8.5+

diff --git a/site/src/pages/index.vue b/site/src/pages/index.vue index a81dc83d..843e95aa 100644 --- a/site/src/pages/index.vue +++ b/site/src/pages/index.vue @@ -10,7 +10,7 @@ /> -

Build -based applications with

+

Build -based applications with and

This plugin allows to build a -based application relying on a package manager supported by diff --git a/site/src/utils/task-outcome.ts b/site/src/utils/task-outcome.ts new file mode 100644 index 00000000..e8b16352 --- /dev/null +++ b/site/src/utils/task-outcome.ts @@ -0,0 +1,8 @@ +export enum TaskOutcome { + SUCCESS = 'SUCCESS', + NO_SOURCE = 'NO_SOURCE', + SKIPPED = 'SKIPPED', + UP_TO_DATE = 'UP_TO_DATE' +} + +export type TaskOutcomeType = `${TaskOutcome}`;