From 8901e724f30532ebe7f5da992eb70cc66e8f2df4 Mon Sep 17 00:00:00 2001
From: Jason Bedard <jason+github@jbedard.ca>
Date: Tue, 21 Jun 2022 23:16:43 -0700
Subject: [PATCH] feat(bazel): add "maybe" macro support (#16003)

---
 .../manager/bazel/__fixtures__/WORKSPACE1     | 23 +++++
 .../manager/bazel/__fixtures__/WORKSPACE2     | 10 ++-
 .../bazel/__fixtures__/repositories.bzl       | 11 +++
 .../bazel/__snapshots__/extract.spec.ts.snap  | 88 +++++++++++++++++++
 .../bazel/__snapshots__/update.spec.ts.snap   | 23 +++++
 lib/modules/manager/bazel/extract.spec.ts     |  7 +-
 lib/modules/manager/bazel/extract.ts          | 26 ++++--
 lib/modules/manager/bazel/update.spec.ts      | 87 +++++++++++++++++-
 8 files changed, 263 insertions(+), 12 deletions(-)

diff --git a/lib/modules/manager/bazel/__fixtures__/WORKSPACE1 b/lib/modules/manager/bazel/__fixtures__/WORKSPACE1
index 11078c7cc6..cc9884dd2a 100644
--- a/lib/modules/manager/bazel/__fixtures__/WORKSPACE1
+++ b/lib/modules/manager/bazel/__fixtures__/WORKSPACE1
@@ -105,6 +105,29 @@ http_archive(
     urls=["https://github.com/GoogleContainerTools/distroless/archive/446923c3756ceeaa75888f52fcbdd48bb314fbf8.tar.gz"]
 )
 
+load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
+maybe(
+  http_archive,
+  name = "io_bazel_rules_go",
+  sha256 = "2b1641428dff9018f9e85c0384f03ec6c10660d935b750e3fa1492a281a53b0f",
+  url = "https://github.com/bazelbuild/rules_go/releases/download/v0.29.0/rules_go-v0.29.0.zip",
+)
+maybe(
+    http_archive,
+    name = "bazel_gazelle",
+    sha256 = "de69a09dc70417580aabf20a28619bb3ef60d038470c7cf8442fafcf627c21cb",
+    urls = [
+        "https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.24.0/bazel-gazelle-v0.24.0.tar.gz",
+        "https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.24.0/bazel-gazelle-v0.24.0.tar.gz",
+    ],
+)
+maybe(
+    go_repository,
+    name = "com_github_pkg_errors",
+    commit = "816c9085562cd7ee03e7f8188a1cfd942858cded",
+    importpath = "github.com/pkg/errors",
+)
+
 load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains")
 
 go_rules_dependencies()
diff --git a/lib/modules/manager/bazel/__fixtures__/WORKSPACE2 b/lib/modules/manager/bazel/__fixtures__/WORKSPACE2
index 712be4f480..0d82bf1378 100644
--- a/lib/modules/manager/bazel/__fixtures__/WORKSPACE2
+++ b/lib/modules/manager/bazel/__fixtures__/WORKSPACE2
@@ -1,4 +1,5 @@
 load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
 
 http_archive(
     name = "GBDeviceInfo",
@@ -28,4 +29,11 @@ http_archive(
     url = "https://github.com/nelhage/rules_boost/archive/135d46b4c9423ee7d494c78a21ff621bc73c12f3.zip",
     sha256 = "de8aac034cabe4a9ba5f7a33b9523862bf76c245a6c554c0e737f591bb7c7aeb",
     strip_prefix = "rules_boost-135d46b4c9423ee7d494c78a21ff621bc73c12f3",
-)
\ No newline at end of file
+)
+
+maybe(
+  http_archive,
+  name = "io_bazel_rules_go",
+  sha256 = "2b1641428dff9018f9e85c0384f03ec6c10660d935b750e3fa1492a281a53b0f",
+  url = "https://github.com/bazelbuild/rules_go/releases/download/v0.29.0/rules_go-v0.29.0.zip",
+)
diff --git a/lib/modules/manager/bazel/__fixtures__/repositories.bzl b/lib/modules/manager/bazel/__fixtures__/repositories.bzl
index 22e77421df..7e2819bbd9 100644
--- a/lib/modules/manager/bazel/__fixtures__/repositories.bzl
+++ b/lib/modules/manager/bazel/__fixtures__/repositories.bzl
@@ -18,6 +18,7 @@ load(
     "http_archive",
     "http_file",
 )
+load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
 load(
     "@io_bazel_rules_docker//toolchains/docker:toolchain.bzl",
     _docker_toolchain_configure = "toolchain_configure",
@@ -210,6 +211,16 @@ py_library(
             urls = ["https://github.com/bazelbuild/bazel-skylib/archive/0.6.0.tar.gz"],
         )
 
+    maybe(
+        http_archive,
+        name = "io_bazel_stardoc",
+        sha256 = "c9794dcc8026a30ff67cf7cf91ebe245ca294b20b071845d12c192afe243ad72",
+        urls = [
+            "https://mirror.bazel.build/github.com/bazelbuild/stardoc/releases/download/0.5.0/stardoc-0.5.0.tar.gz",
+            "https://github.com/bazelbuild/stardoc/releases/download/0.5.0/stardoc-0.5.0.tar.gz",
+        ],
+    )
+
     if "gzip" not in excludes:
         local_tool(
             name = "gzip",
diff --git a/lib/modules/manager/bazel/__snapshots__/extract.spec.ts.snap b/lib/modules/manager/bazel/__snapshots__/extract.spec.ts.snap
index 6c1d2bc9a1..73caee37c6 100644
--- a/lib/modules/manager/bazel/__snapshots__/extract.spec.ts.snap
+++ b/lib/modules/manager/bazel/__snapshots__/extract.spec.ts.snap
@@ -62,6 +62,25 @@ Array [
     "packageName": "bazelbuild/bazel-skylib",
     "repo": "bazelbuild/bazel-skylib",
   },
+  Object {
+    "currentValue": "0.5.0",
+    "datasource": "github-releases",
+    "depName": "io_bazel_stardoc",
+    "depType": "http_archive",
+    "managerData": Object {
+      "def": "maybe(
+        http_archive,
+        name = \\"io_bazel_stardoc\\",
+        sha256 = \\"c9794dcc8026a30ff67cf7cf91ebe245ca294b20b071845d12c192afe243ad72\\",
+        urls = [
+            \\"https://mirror.bazel.build/github.com/bazelbuild/stardoc/releases/download/0.5.0/stardoc-0.5.0.tar.gz\\",
+            \\"https://github.com/bazelbuild/stardoc/releases/download/0.5.0/stardoc-0.5.0.tar.gz\\",
+        ],
+    )",
+    },
+    "packageName": "bazelbuild/stardoc",
+    "repo": "bazelbuild/stardoc",
+  },
 ]
 `;
 
@@ -131,6 +150,22 @@ Array [
     "packageName": "nelhage/rules_boost",
     "repo": "nelhage/rules_boost",
   },
+  Object {
+    "currentValue": "v0.29.0",
+    "datasource": "github-releases",
+    "depName": "io_bazel_rules_go",
+    "depType": "http_archive",
+    "managerData": Object {
+      "def": "maybe(
+  http_archive,
+  name = \\"io_bazel_rules_go\\",
+  sha256 = \\"2b1641428dff9018f9e85c0384f03ec6c10660d935b750e3fa1492a281a53b0f\\",
+  url = \\"https://github.com/bazelbuild/rules_go/releases/download/v0.29.0/rules_go-v0.29.0.zip\\",
+)",
+    },
+    "packageName": "bazelbuild/rules_go",
+    "repo": "bazelbuild/rules_go",
+  },
 ]
 `;
 
@@ -323,6 +358,59 @@ Array [
     "packageName": "GoogleContainerTools/distroless",
     "repo": "GoogleContainerTools/distroless",
   },
+  Object {
+    "currentValue": "v0.29.0",
+    "datasource": "github-releases",
+    "depName": "io_bazel_rules_go",
+    "depType": "http_archive",
+    "managerData": Object {
+      "def": "maybe(
+  http_archive,
+  name = \\"io_bazel_rules_go\\",
+  sha256 = \\"2b1641428dff9018f9e85c0384f03ec6c10660d935b750e3fa1492a281a53b0f\\",
+  url = \\"https://github.com/bazelbuild/rules_go/releases/download/v0.29.0/rules_go-v0.29.0.zip\\",
+)",
+    },
+    "packageName": "bazelbuild/rules_go",
+    "repo": "bazelbuild/rules_go",
+  },
+  Object {
+    "currentValue": "v0.24.0",
+    "datasource": "github-releases",
+    "depName": "bazel_gazelle",
+    "depType": "http_archive",
+    "managerData": Object {
+      "def": "maybe(
+    http_archive,
+    name = \\"bazel_gazelle\\",
+    sha256 = \\"de69a09dc70417580aabf20a28619bb3ef60d038470c7cf8442fafcf627c21cb\\",
+    urls = [
+        \\"https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.24.0/bazel-gazelle-v0.24.0.tar.gz\\",
+        \\"https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.24.0/bazel-gazelle-v0.24.0.tar.gz\\",
+    ],
+)",
+    },
+    "packageName": "bazelbuild/bazel-gazelle",
+    "repo": "bazelbuild/bazel-gazelle",
+  },
+  Object {
+    "currentDigest": "816c9085562cd7ee03e7f8188a1cfd942858cded",
+    "currentDigestShort": "816c908",
+    "currentValue": "v0.0.0",
+    "datasource": "go",
+    "depName": "com_github_pkg_errors",
+    "depType": "go_repository",
+    "digestOneAndOnly": true,
+    "managerData": Object {
+      "def": "maybe(
+    go_repository,
+    name = \\"com_github_pkg_errors\\",
+    commit = \\"816c9085562cd7ee03e7f8188a1cfd942858cded\\",
+    importpath = \\"github.com/pkg/errors\\",
+)",
+    },
+    "packageName": "github.com/pkg/errors",
+  },
   Object {
     "currentDigest": "sha256:d5a717649fd93ea5b9c430d7f84e4c37ba219eb53bd73ed1d4a5a98e9edd84a7",
     "currentValue": "latest",
diff --git a/lib/modules/manager/bazel/__snapshots__/update.spec.ts.snap b/lib/modules/manager/bazel/__snapshots__/update.spec.ts.snap
index bd6d620020..86e80709fb 100644
--- a/lib/modules/manager/bazel/__snapshots__/update.spec.ts.snap
+++ b/lib/modules/manager/bazel/__snapshots__/update.spec.ts.snap
@@ -108,6 +108,29 @@ http_archive(
     urls=[\\"https://github.com/GoogleContainerTools/distroless/archive/446923c3756ceeaa75888f52fcbdd48bb314fbf8.tar.gz\\"]
 )
 
+load(\\"@bazel_tools//tools/build_defs/repo:utils.bzl\\", \\"maybe\\")
+maybe(
+  http_archive,
+  name = \\"io_bazel_rules_go\\",
+  sha256 = \\"2b1641428dff9018f9e85c0384f03ec6c10660d935b750e3fa1492a281a53b0f\\",
+  url = \\"https://github.com/bazelbuild/rules_go/releases/download/v0.29.0/rules_go-v0.29.0.zip\\",
+)
+maybe(
+    http_archive,
+    name = \\"bazel_gazelle\\",
+    sha256 = \\"de69a09dc70417580aabf20a28619bb3ef60d038470c7cf8442fafcf627c21cb\\",
+    urls = [
+        \\"https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.24.0/bazel-gazelle-v0.24.0.tar.gz\\",
+        \\"https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.24.0/bazel-gazelle-v0.24.0.tar.gz\\",
+    ],
+)
+maybe(
+    go_repository,
+    name = \\"com_github_pkg_errors\\",
+    commit = \\"816c9085562cd7ee03e7f8188a1cfd942858cded\\",
+    importpath = \\"github.com/pkg/errors\\",
+)
+
 load(\\"@io_bazel_rules_go//go:def.bzl\\", \\"go_rules_dependencies\\", \\"go_register_toolchains\\")
 
 go_rules_dependencies()
diff --git a/lib/modules/manager/bazel/extract.spec.ts b/lib/modules/manager/bazel/extract.spec.ts
index dc3605204d..c83e26c77d 100644
--- a/lib/modules/manager/bazel/extract.spec.ts
+++ b/lib/modules/manager/bazel/extract.spec.ts
@@ -15,7 +15,7 @@ describe('modules/manager/bazel/extract', () => {
 
     it('extracts multiple types of dependencies', () => {
       const res = extractPackageFile(Fixtures.get('WORKSPACE1'));
-      expect(res?.deps).toHaveLength(14);
+      expect(res?.deps).toHaveLength(17);
       expect(res?.deps).toMatchSnapshot();
     });
 
@@ -26,6 +26,7 @@ describe('modules/manager/bazel/extract', () => {
         { packageName: 'nelhage/rules_boost' },
         { packageName: 'lmirosevic/GBDeviceInfo' },
         { packageName: 'nelhage/rules_boost' },
+        { packageName: 'bazelbuild/rules_go' },
       ]);
     });
 
@@ -47,6 +48,10 @@ describe('modules/manager/bazel/extract', () => {
           currentValue: '0.6.0',
           packageName: 'bazelbuild/bazel-skylib',
         },
+        {
+          currentValue: '0.5.0',
+          packageName: 'bazelbuild/stardoc',
+        },
       ]);
     });
 
diff --git a/lib/modules/manager/bazel/extract.ts b/lib/modules/manager/bazel/extract.ts
index 8dd033bb0d..0833ecd381 100644
--- a/lib/modules/manager/bazel/extract.ts
+++ b/lib/modules/manager/bazel/extract.ts
@@ -73,13 +73,16 @@ const lexer = moo.states({
     },
     def: {
       match: new RegExp(
-        [
-          'container_pull',
-          'http_archive',
-          'http_file',
-          'go_repository',
-          'git_repository',
-        ].join('|')
+        '(?:' +
+          [
+            'container_pull',
+            'http_archive',
+            'http_file',
+            'go_repository',
+            'git_repository',
+            'maybe',
+          ].join('|') +
+          ')\\s*\\('
       ),
     },
     unknown: moo.fallback,
@@ -168,8 +171,7 @@ export function extractPackageFile(
   const deps: PackageDependency[] = [];
   definitions.forEach((def) => {
     logger.debug({ def }, 'Checking bazel definition');
-    const [depType] = def.split('(', 1);
-    const dep: PackageDependency = { depType, managerData: { def } };
+    let [depType] = def.split('(', 1);
     let depName: string | undefined;
     let importpath: string | undefined;
     let remote: string | undefined;
@@ -226,6 +228,12 @@ export function extractPackageFile(
       [, importpath] = match;
     }
     logger.debug({ dependency: depName, remote, currentValue });
+
+    if (depType === 'maybe') {
+      depType = def.split('(', 2).pop()!.split(',', 1).pop()!.trim();
+    }
+
+    const dep: PackageDependency = { depType, managerData: { def } };
     if (
       depType === 'git_repository' &&
       depName &&
diff --git a/lib/modules/manager/bazel/update.spec.ts b/lib/modules/manager/bazel/update.spec.ts
index 2df19ebdbf..97156d79f9 100644
--- a/lib/modules/manager/bazel/update.spec.ts
+++ b/lib/modules/manager/bazel/update.spec.ts
@@ -22,7 +22,7 @@ describe('modules/manager/bazel/update', () => {
       jest.resetAllMocks();
     });
 
-    it('updates tag', async () => {
+    it('updates git_repository tag', async () => {
       const upgrade = {
         depName: 'build_bazel_rules_nodejs',
         depType: 'git_repository',
@@ -39,6 +39,24 @@ describe('modules/manager/bazel/update', () => {
       expect(res).not.toEqual(content);
     });
 
+    it('updates maybe(git_repository) tag', async () => {
+      const upgrade = {
+        depName: 'build_bazel_rules_nodejs',
+        depType: 'git_repository',
+        managerData: {
+          def: `maybe(\n    git_repository,\n    name = "build_bazel_rules_nodejs",\n    remote = "https://github.com/bazelbuild/rules_nodejs.git",\n    tag = "0.1.8",\n)`,
+        },
+        currentValue: '0.1.8',
+        newValue: '0.2.0',
+      };
+      const res = await updateDependency({
+        fileContent: content,
+        upgrade,
+      });
+      expect(res).not.toEqual(content);
+      expect(res).toMatch(/maybe\([\n\s]*git_repository,/);
+    });
+
     it('updates container_pull deptype and preserves comment', async () => {
       const upgrade = {
         depName: 'hasura',
@@ -232,6 +250,32 @@ http_archive(
       expect(res).toBeNull();
     });
 
+    it('errors for maybe(http_archive) without urls', async () => {
+      const upgrade = {
+        depName: 'bazel_skylib',
+        depType: 'http_archive',
+        repo: 'bazelbuild/bazel-skylib',
+        managerData: {
+          def:
+            `
+maybe(
+  http_archive,
+  name = "bazel_skylib",
+  sha256 = "b5f6abe419da897b7901f90cbab08af958b97a8f3575b0d3dd062ac7ce78541f",
+  strip_prefix = "bazel-skylib-0.5.0",
+)
+        `.trim() + '\n',
+        },
+        currentValue: '0.5.0',
+        newValue: '0.6.2',
+      };
+      const res = await updateDependency({
+        fileContent: content,
+        upgrade,
+      });
+      expect(res).toBeNull();
+    });
+
     it('updates http_archive with urls array', async () => {
       const upgrade = {
         depName: 'bazel_skylib',
@@ -270,5 +314,46 @@ http_archive(
       expect(res?.indexOf('0.5.0')).toBe(-1);
       expect(res?.indexOf('0.6.2')).not.toBe(-1);
     });
+
+    it('updates maybe(http_archive) with urls array', async () => {
+      const upgrade = {
+        depName: 'bazel_skylib',
+        depType: 'http_archive',
+        repo: 'bazelbuild/bazel-skylib',
+        managerData: {
+          def:
+            `
+maybe(
+  http_archive,
+  name = "bazel_skylib",
+  sha256 = "b5f6abe419da897b7901f90cbab08af958b97a8f3575b0d3dd062ac7ce78541f",
+  strip_prefix = "bazel-skylib-0.5.0",
+  urls = [
+      "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/archive/0.5.0.tar.gz",
+      "https://github.com/bazelbuild/bazel-skylib/archive/0.5.0.tar.gz",
+  ],
+)
+        `.trim() + '\n',
+        },
+        currentValue: '0.5.0',
+        newValue: '0.6.2',
+      };
+      httpMock
+        .scope('https://github.com')
+        .get('/bazelbuild/bazel-skylib/archive/0.6.2.tar.gz')
+        .reply(200, Readable.from(['foo']));
+      httpMock
+        .scope('https://mirror.bazel.build')
+        .get('/github.com/bazelbuild/bazel-skylib/archive/0.6.2.tar.gz')
+        .reply(200, Readable.from(['foo']));
+      const res = await updateDependency({
+        fileContent: content,
+        upgrade,
+      });
+      expect(res).not.toEqual(content);
+      expect(res?.indexOf('0.5.0')).toBe(-1);
+      expect(res?.indexOf('0.6.2')).not.toBe(-1);
+      expect(res).toMatch(/maybe\([\n\s]*http_archive,/);
+    });
   });
 });
-- 
GitLab