From 4dc6d691f60ae6c281f0d47e7930bfc938d484e7 Mon Sep 17 00:00:00 2001
From: Johannes Feichtner <343448+Churro@users.noreply.github.com>
Date: Sun, 2 Jul 2023 23:09:06 +0200
Subject: [PATCH] refactor(manager/gradle): ignore stdout of artifact updates
 via stdio convenience option (#23088)

---
 lib/modules/manager/gradle-wrapper/utils.ts  | 14 --------
 lib/modules/manager/gradle/artifacts.spec.ts | 34 ++++++++++++++------
 lib/modules/manager/gradle/artifacts.ts      | 11 +------
 lib/modules/manager/gradle/readme.md         |  8 +++--
 lib/util/exec/index.spec.ts                  | 23 +++++++++++++
 lib/util/exec/index.ts                       |  5 +++
 lib/util/exec/types.ts                       |  1 +
 7 files changed, 59 insertions(+), 37 deletions(-)

diff --git a/lib/modules/manager/gradle-wrapper/utils.ts b/lib/modules/manager/gradle-wrapper/utils.ts
index b0edcccdc0..65e68bf9fa 100644
--- a/lib/modules/manager/gradle-wrapper/utils.ts
+++ b/lib/modules/manager/gradle-wrapper/utils.ts
@@ -21,20 +21,6 @@ export function gradleWrapperFileName(): string {
   return './gradlew';
 }
 
-export function nullRedirectionCommand(): string {
-  if (
-    os.platform() === 'win32' &&
-    GlobalConfig.get('binarySource') !== 'docker'
-  ) {
-    // TODO: Windows environment without docker needs to be implemented
-    logger.debug(
-      'Updating artifacts may fail due to excessive output from "gradle.bat :dependencies" command.'
-    );
-    return '';
-  }
-  return ' > /dev/null';
-}
-
 export async function prepareGradleCommand(
   gradlewFile: string
 ): Promise<string | null> {
diff --git a/lib/modules/manager/gradle/artifacts.spec.ts b/lib/modules/manager/gradle/artifacts.spec.ts
index 087952f444..f52fcf0d27 100644
--- a/lib/modules/manager/gradle/artifacts.spec.ts
+++ b/lib/modules/manager/gradle/artifacts.spec.ts
@@ -159,9 +159,10 @@ describe('modules/manager/gradle/artifacts', () => {
         },
       },
       {
-        cmd: './gradlew --console=plain -q :dependencies --update-locks org.junit.jupiter:junit-jupiter-api,org.junit.jupiter:junit-jupiter-engine > /dev/null',
+        cmd: './gradlew --console=plain -q :dependencies --update-locks org.junit.jupiter:junit-jupiter-api,org.junit.jupiter:junit-jupiter-engine',
         options: {
           cwd: '/tmp/github/some/repo',
+          stdio: ['pipe', 'ignore', 'pipe'],
         },
       },
     ]);
@@ -204,6 +205,7 @@ describe('modules/manager/gradle/artifacts', () => {
         cmd: 'gradlew.bat --console=plain -q :dependencies --update-locks org.junit.jupiter:junit-jupiter-api,org.junit.jupiter:junit-jupiter-engine',
         options: {
           cwd: '/tmp/github/some/repo',
+          stdio: ['pipe', 'ignore', 'pipe'],
         },
       },
     ]);
@@ -243,9 +245,10 @@ describe('modules/manager/gradle/artifacts', () => {
         },
       },
       {
-        cmd: './gradlew --console=plain -q :dependencies --update-locks org.springframework.boot:org.springframework.boot.gradle.plugin > /dev/null',
+        cmd: './gradlew --console=plain -q :dependencies --update-locks org.springframework.boot:org.springframework.boot.gradle.plugin',
         options: {
           cwd: '/tmp/github/some/repo',
+          stdio: ['pipe', 'ignore', 'pipe'],
         },
       },
     ]);
@@ -293,9 +296,10 @@ describe('modules/manager/gradle/artifacts', () => {
         },
       },
       {
-        cmd: './gradlew --console=plain -q :dependencies --write-locks > /dev/null',
+        cmd: './gradlew --console=plain -q :dependencies --write-locks',
         options: {
           cwd: '/tmp/github/some/repo',
+          stdio: ['pipe', 'ignore', 'pipe'],
         },
       },
     ]);
@@ -355,9 +359,12 @@ describe('modules/manager/gradle/artifacts', () => {
           ' bash -l -c "' +
           'install-tool java 16.0.1' +
           ' && ' +
-          './gradlew --console=plain -q :dependencies --write-locks > /dev/null' +
+          './gradlew --console=plain -q :dependencies --write-locks' +
           '"',
-        options: { cwd: '/tmp/github/some/repo' },
+        options: {
+          cwd: '/tmp/github/some/repo',
+          stdio: ['pipe', 'ignore', 'pipe'],
+        },
       },
     ]);
   });
@@ -390,8 +397,11 @@ describe('modules/manager/gradle/artifacts', () => {
       },
       { cmd: 'install-tool java 16.0.1' },
       {
-        cmd: './gradlew --console=plain -q :dependencies --write-locks > /dev/null',
-        options: { cwd: '/tmp/github/some/repo' },
+        cmd: './gradlew --console=plain -q :dependencies --write-locks',
+        options: {
+          cwd: '/tmp/github/some/repo',
+          stdio: ['pipe', 'ignore', 'pipe'],
+        },
       },
     ]);
   });
@@ -426,9 +436,10 @@ describe('modules/manager/gradle/artifacts', () => {
         },
       },
       {
-        cmd: './gradlew --console=plain -q :dependencies :sub1:dependencies :sub2:dependencies --write-locks > /dev/null',
+        cmd: './gradlew --console=plain -q :dependencies :sub1:dependencies :sub2:dependencies --write-locks',
         options: {
           cwd: '/tmp/github/some/repo',
+          stdio: ['pipe', 'ignore', 'pipe'],
         },
       },
     ]);
@@ -527,8 +538,11 @@ describe('modules/manager/gradle/artifacts', () => {
       },
       { cmd: 'install-tool java 11.0.1' },
       {
-        cmd: './gradlew --console=plain -q :dependencies --write-locks > /dev/null',
-        options: { cwd: '/tmp/github/some/repo' },
+        cmd: './gradlew --console=plain -q :dependencies --write-locks',
+        options: {
+          cwd: '/tmp/github/some/repo',
+          stdio: ['pipe', 'ignore', 'pipe'],
+        },
       },
     ]);
   });
diff --git a/lib/modules/manager/gradle/artifacts.ts b/lib/modules/manager/gradle/artifacts.ts
index a0c52b9439..e3db9b645b 100644
--- a/lib/modules/manager/gradle/artifacts.ts
+++ b/lib/modules/manager/gradle/artifacts.ts
@@ -14,7 +14,6 @@ import {
   extractGradleVersion,
   getJavaConstraint,
   gradleWrapperFileName,
-  nullRedirectionCommand,
   prepareGradleCommand,
 } from '../gradle-wrapper/utils';
 import type { UpdateArtifact, UpdateArtifactsResult } from '../types';
@@ -163,16 +162,8 @@ export async function updateArtifacts({
       cmd += ` --update-locks ${updatedDepNames.map(quote).join(',')}`;
     }
 
-    // `./gradlew :dependencies` command can output huge text due to `:dependencies`
-    // that renders dependency graphs. Given the output can exceed `ExecOptions.maxBuffer` size,
-    // drop stdout from the command.
-    //
-    // Note: Windows without docker doesn't supported this yet
-    const nullRedirection = nullRedirectionCommand();
-    cmd += nullRedirection;
-
     await writeLocalFile(packageFileName, newPackageFileContent);
-    await exec(cmd, execOptions);
+    await exec(cmd, { ...execOptions, ignoreStdout: true });
 
     const res = await getUpdatedLockfiles(oldLockFileContentMap);
     logger.debug('Returning updated Gradle dependency lockfiles');
diff --git a/lib/modules/manager/gradle/readme.md b/lib/modules/manager/gradle/readme.md
index 31fcbc5995..5a5a813f34 100644
--- a/lib/modules/manager/gradle/readme.md
+++ b/lib/modules/manager/gradle/readme.md
@@ -3,6 +3,8 @@ It does not call `gradle` directly in order to extract a list of dependencies.
 
 ### Updating lockfiles
 
-Updating lockfiles is done with `./gradlew :dependencies --wirte/update-locks` command.
-This command can output excessive text to the console.
-While running the command, the output to stdout is dropped when you run Renovate on most platforms other than Windows.
+The gradle manager supports gradle lock files in `.lockfile` artifacts, as well as lock files used by the [gradle-consistent-versions](https://github.com/palantir/gradle-consistent-versions) plugin.
+During [lock file maintenance](https://docs.renovatebot.com/configuration-options/#lockfilemaintenance), renovate calls `./gradlew :dependencies --write-locks` on the root project and subprojects.
+For regular dependency updates, renovate automatically updates lock state entries via the `--update-locks` command line flag.
+
+As the output of these commands can be very large, any text other than errors (in `stderr`) is discarded.
diff --git a/lib/util/exec/index.spec.ts b/lib/util/exec/index.spec.ts
index a36cd1c717..a72aa0ee55 100644
--- a/lib/util/exec/index.spec.ts
+++ b/lib/util/exec/index.spec.ts
@@ -710,6 +710,29 @@ describe('util/exec/index', () => {
       },
     ],
 
+    [
+      'Discarded stdout if ignoreStdout=true',
+      {
+        processEnv,
+        inCmd,
+        inOpts: {
+          ignoreStdout: true,
+          cwdFile: '/somefile',
+        },
+        outCmd,
+        outOpts: [
+          {
+            cwd,
+            encoding,
+            env: envMock.basic,
+            timeout: 900000,
+            maxBuffer: 10485760,
+            stdio: ['pipe', 'ignore', 'pipe'],
+          },
+        ],
+      },
+    ],
+
     [
       'Hermit',
       {
diff --git a/lib/util/exec/index.ts b/lib/util/exec/index.ts
index 7abe4bc40d..547ad0f2be 100644
--- a/lib/util/exec/index.ts
+++ b/lib/util/exec/index.ts
@@ -87,6 +87,11 @@ function getRawExecOptions(opts: ExecOptions): RawExecOptions {
 
   // Set default max buffer size to 10MB
   rawExecOptions.maxBuffer = rawExecOptions.maxBuffer ?? 10 * 1024 * 1024;
+
+  if (opts.ignoreStdout) {
+    rawExecOptions.stdio = ['pipe', 'ignore', 'pipe'];
+  }
+
   return rawExecOptions;
 }
 
diff --git a/lib/util/exec/types.ts b/lib/util/exec/types.ts
index 430df31324..8f2a704e66 100644
--- a/lib/util/exec/types.ts
+++ b/lib/util/exec/types.ts
@@ -49,6 +49,7 @@ export interface ExecOptions {
   docker?: Opt<DockerOptions>;
   toolConstraints?: Opt<ToolConstraint[]>;
   preCommands?: Opt<string[]>;
+  ignoreStdout?: boolean;
   // Following are pass-through to child process
   maxBuffer?: number | undefined;
   timeout?: number | undefined;
-- 
GitLab