From a530fe054944ca28bb49985c4334de20bca9c24c Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Sun, 21 Nov 2021 15:41:28 +0100 Subject: build: move to yarn workspaces The root project (worktree) must have a valid package.json, .yarnrc.yaml, and have the gradle precompiled script plugin refinery-frontend-worktree applied to it. All frontend modules (workspaces) must have a valid package.json, be referenced in the worktree project.json, and be a valid gradle subproject with the gradle precompiled script plugin refinery-frontend-workspace applied to it. To reduce unnecessary downloads, we do the following workarounds: * The node version is pinned in gradle.properties and the installed node version is stored in .node/frontend.properties. A new node version is only downloaded if it differs from the installed one (or none is installed). This improves on the default gradle behavior, which would download a new node version if the classpath of the installNode task was changed. * The yarn classic version is pinned in gradle.properties and the installed yarn classic version is stored in .node/frontend.properties. This improves on the default gradle behavior, which would reinstall the yarn npm package at every build. * We disable the enableYarnBerry task, since a yarn berry version is already committed into the repository at .yarn/versions as it is recommended in http://yarnpkg.com/getting-started/qa/ * We use yarn set version --only-if-needed in the installYarn task. * The installFrontend task takes package.json and yarn.lock as inputs and produces .pnp.cjs and .pnp.loader.mjs as outputs. Whether this task is up-to-date can be checked by gradle quickly, since it doesn't have to traverse the node_module folder due to pnp resoltion: https://yarnpkg.com/features/pnp/ All workspaces have the installNode, installYarnGlobally, installYarn and installFrontend tasks disabled. However, these tasks depend on the task with the same name in the worktree project, so installation is still triggered globally. Moreover, the installFrontend task in the worktree has the package.json of the workspaces as inputs, so reinstallation is triggered when the workspace package.json changes. --- .../groovy/refinery-frontend-conventions.gradle | 14 ++++ .../main/groovy/refinery-frontend-workspace.gradle | 28 +++++++ .../main/groovy/refinery-frontend-worktree.gradle | 91 ++++++++++++++++++++++ 3 files changed, 133 insertions(+) create mode 100644 buildSrc/src/main/groovy/refinery-frontend-conventions.gradle create mode 100644 buildSrc/src/main/groovy/refinery-frontend-workspace.gradle create mode 100644 buildSrc/src/main/groovy/refinery-frontend-worktree.gradle (limited to 'buildSrc/src') diff --git a/buildSrc/src/main/groovy/refinery-frontend-conventions.gradle b/buildSrc/src/main/groovy/refinery-frontend-conventions.gradle new file mode 100644 index 00000000..da9370fe --- /dev/null +++ b/buildSrc/src/main/groovy/refinery-frontend-conventions.gradle @@ -0,0 +1,14 @@ +plugins { + id 'org.siouan.frontend-jdk11' +} + +frontend { + nodeVersion = project.ext['frontend.nodeVersion'] + nodeInstallDirectory = file("${rootDir}/.node") + yarnEnabled = true + yarnVersion = project.ext['frontend.yarnVersion'] +} + +tasks.named('enableYarnBerry') { + enabled = false +} diff --git a/buildSrc/src/main/groovy/refinery-frontend-workspace.gradle b/buildSrc/src/main/groovy/refinery-frontend-workspace.gradle new file mode 100644 index 00000000..35410db4 --- /dev/null +++ b/buildSrc/src/main/groovy/refinery-frontend-workspace.gradle @@ -0,0 +1,28 @@ +plugins { + id 'refinery-frontend-conventions' +} + +tasks.named('installNode') { + dependsOn rootProject.tasks.named('installNode') + enabled = false +} + +tasks.named('installYarnGlobally') { + dependsOn rootProject.tasks.named('installYarnGlobally') + enabled = false +} + +tasks.named('installYarn') { + dependsOn rootProject.tasks.named('installYarn') + enabled = false +} + +def rootInstallFrontend = rootProject.tasks.named('installFrontend') +rootInstallFrontend.configure { + inputs.file "${projectDir}/package.json" +} + +tasks.named('installFrontend') { + dependsOn rootInstallFrontend + enabled = false +} diff --git a/buildSrc/src/main/groovy/refinery-frontend-worktree.gradle b/buildSrc/src/main/groovy/refinery-frontend-worktree.gradle new file mode 100644 index 00000000..fbbf13d2 --- /dev/null +++ b/buildSrc/src/main/groovy/refinery-frontend-worktree.gradle @@ -0,0 +1,91 @@ +plugins { + id 'refinery-frontend-conventions' +} + +frontend { + yarnGlobalInstallScript = "install -g yarn@${project.ext['frontend.yarn1Version']}" + yarnInstallScript = "set version ${frontend.yarnVersion.get()} --only-if-needed" +} + +ext.frontedPropertiesFile = "${frontend.nodeInstallDirectory.get()}/frontend.properties" + +def String getFrontendProperty(String propertyName) { + FileInputStream inputStream = null + Properties props = new Properties() + try { + inputStream = new FileInputStream(frontedPropertiesFile) + props.load(inputStream) + } catch (FileNotFoundException | IOException e) { + return null + } finally { + if (inputStream != null) { + inputStream.close() + } + } + return props.get(propertyName) +} + +def String putFrontedProperty(String propertyName, String propertyValue) { + FileInputStream inputStream = null + Properties props = new Properties() + try { + inputStream = new FileInputStream(frontedPropertiesFile) + props.load(inputStream) + } catch (FileNotFoundException e) { + // Use an empty Properties object instead + } finally { + if (inputStream != null) { + inputStream.close() + } + } + props.put(propertyName, propertyValue) + FileOutputStream outputStream = null + try { + outputStream = new FileOutputStream(frontedPropertiesFile) + props.store(outputStream, null) + } catch (IOException e) { + return true; + } finally { + if (outputStream != null) { + outputStream.close() + } + } +} + +tasks.named('installNode') { + onlyIf { + getFrontendProperty('installedNodeVersion') != frontend.nodeVersion.get() + } + doLast { + putFrontedProperty('installedNodeVersion', frontend.nodeVersion.get()) + } +} + +tasks.named('installYarnGlobally') { + onlyIf { + getFrontendProperty('installedYarn1Version') != project.ext['frontend.yarn1Version'] + } + doLast { + putFrontedProperty('installedYarn1Version', project.ext['frontend.yarn1Version']) + } + outputs.dir "${frontend.nodeInstallDirectory.get()}/lib/node_modules/yarn" +} + +tasks.named('installYarn') { + outputs.file ".yarn/releases/yarn-${frontend.yarnVersion.get()}.cjs" +} + +tasks.named('installFrontend') { + inputs.files('package.json', 'yarn.lock') + outputs.files('.pnp.cjs', '.pnp.loader.mjs') +} + +tasks.register('clobberFrontend', Delete) { + delete frontend.nodeInstallDirectory.get() + delete '.yarn/cache' + delete '.yarn/install-state.gz' + delete '.yarn/sdks' + delete '.yarn/unplugged' + delete '.pnp.cjs' + delete '.pnp.loader.mjs' +} -- cgit v1.2.3-54-g00ecf