Adding an environment variable to a Gradle build

Simple failure

On recent builds at work, there were some intermittent failures when building our frontend, due to:

<--- Last few GCs --->

[33385:0x7fbef2900000]   242941 ms: Mark-sweep (reduce) 1997.3 (2085.2) -> 1997.2 (2054.2) MB, 2916.4 / 0.3 ms  (average mu = 0.091, current mu = 0.000) last resort GC in old space requested
[33385:0x7fbef2900000]   246364 ms: Mark-sweep (reduce) 1997.2 (2054.2) -> 1997.2 (2054.2) MB, 3423.2 / 0.1 ms  (average mu = 0.049, current mu = 0.000) last resort GC in old space requested


<--- JS stacktrace --->

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory

As is written in the message, this is likely due to Node not having enough heap memory. A quick google search indicates that increasing max-old-space-size via the NODE_OPTIONS environment variable is enough.

Up to this point, the fix works by using a value of 8192. However, Gradle is used as the build system at work, and Node tasks are also called via Gradle.

The problem seldom happens, but if we want the change to be available for the CI (Jenkins) as well as for all developers, it should be integrated into Gradle. We do not want to setup environment variables in multiple places.

Trying to solve it the Gradle way

Gradle is a build system that executes tasks that depend on each other, just like a Makefile. Each task has a type, options in a task are limited by the interface provided.

Gradle allows setting environment variables only for:

  • tests tasks
  • Exec or JavaExec tasks

Otherwise environment variables have to be set up externally before running Gradle, as usual. For example NODE_OPTIONS="--max-old-space-size=8192" ./gradlew ...

Node tasks are called via the Gradle Node Plugin, and the max-old-space-size can also be given to the node command directly with the options parameter, which would be equivalent to node --max-old-space-size=8192.
The feature was added in this PR, which also references an issue with the exact same problem…

While this works file for tasks of type NodeTask, our build system also use some NpxTask and NpmTask. In our case, it was a NpxTask that failed, and the options provided by the plugin does not apply for this task.

Setting an environment variable in a dependent (or previous) task does not work, because in Gradle, they are local to a task: the process being executed.

Modifying the wrapper

Gradle uses a wrapper called gradlew to download the good version of Gradle for each build. This allows all developers and CI to have a reproducible environment without having to install system packages, except the JVM.
That script is committed in the repository and is used to run the build.

Instead of forcing my way with the Gradle build system, and add the required environment variable only to tasks that needed it, I decided to just add it in the wrapper. The problem is that the variable is available for all tasks, but in this case it’s good enough.