From 8f7c963716406acc79f60f195b057abc7882152e Mon Sep 17 00:00:00 2001 From: Stefan Penner Date: Mon, 26 Feb 2024 10:16:23 -0700 Subject: [PATCH] POC - go.mod replace with ModulePath --- internal/bzlmod/go_deps.bzl | 16 +++++++-- internal/bzlmod/go_mod.bzl | 9 ++--- internal/go_repository.bzl | 69 ++++++++++++++++++++++++++++++++++--- repository.md | 5 +-- 4 files changed, 86 insertions(+), 13 deletions(-) diff --git a/internal/bzlmod/go_deps.bzl b/internal/bzlmod/go_deps.bzl index 24d5ddc2f..278ab4a61 100644 --- a/internal/bzlmod/go_deps.bzl +++ b/internal/bzlmod/go_deps.bzl @@ -550,6 +550,7 @@ def _go_deps_impl(module_ctx): version = new_version, raw_version = replace.version, ) + if path in root_versions: if replace != replace.to_path: # If the root module replaces a Go module with a completely different one, do @@ -599,7 +600,6 @@ def _go_deps_impl(module_ctx): # Do not create a go_repository for a dep shared with the non-isolated instance of # go_deps. continue - go_repository_args = { "name": module.repo_name, "importpath": path, @@ -626,6 +626,13 @@ def _go_deps_impl(module_ctx): "version": "v" + module.raw_version, }) + # TODO: remove the need for a sentinel value, make it explicit instead + if module.raw_version == "999.999.999": + go_repository_args.update({ + "version": None, + "path": module.replace, + }) + go_repository(**go_repository_args) # Create a synthetic WORKSPACE file that lists all Go repositories created @@ -670,8 +677,11 @@ def _get_sum_from_module(path, module, sums): entry = (module.replace, module.raw_version) if entry not in sums: - # TODO: if no sum exist, this is probably because a go mod tidy was missed - fail("No sum for {}@{} found".format(path, module.raw_version)) + if module.raw_version == "999.999.999": + return "" + else: + # TODO: if no sum exist, this is probably because a go mod tidy was missed + fail("No sum for {}@{} found".format(path, module.raw_version)) return sums[entry] diff --git a/internal/bzlmod/go_mod.bzl b/internal/bzlmod/go_mod.bzl index 024bf170b..deb1f99cc 100644 --- a/internal/bzlmod/go_mod.bzl +++ b/internal/bzlmod/go_mod.bzl @@ -252,10 +252,11 @@ def _parse_replace_directive(state, tokens, path, line_no): version = _canonicalize_raw_version(tokens[4]), ) else: - fail( - "{}:{}: replace directive must follow pattern: ".format(path, line_no) + - "'replace from_path from_version => to_path to_version' or " + - "'replace from_path => to_path to_version'", + state["replace"][from_path] = struct( + from_version = None, + to_path = tokens[2], + # TODO: should version be None here? or some sentinel Null value version? + version = _canonicalize_raw_version("999.999.999"), ) def _tokenize_line(line, path, line_no): diff --git a/internal/go_repository.bzl b/internal/go_repository.bzl index 8dac6eb9b..4ce8430e4 100644 --- a/internal/go_repository.bzl +++ b/internal/go_repository.bzl @@ -181,6 +181,8 @@ def _go_repository_impl(ctx): "-version=" + ctx.attr.version, "-sum=" + ctx.attr.sum, ] + elif ctx.attr.path: + pass else: fail("one of urls, commit, tag, or importpath must be specified") @@ -237,6 +239,58 @@ def _go_repository_impl(ctx): env.update({k: ctx.os.environ[k] for k in env_keys if k in ctx.os.environ}) + if ctx.attr.path: + local_path_env = dict(env) + local_path_env["GOSUMDB"] = "off" + + # Override external GO111MODULE, because it is needed by module mode, no-op in repository mode + local_path_env["GO111MODULE"] = "on" + + if hasattr(ctx, "watch_tree"): + print("hi") + # https://github.com/bazelbuild/bazel/commit/fffa0affebbacf1961a97ef7cd248be64487d480 + ctx.watch_tree(ctx.attr.path) + else: + print(""" + WARNING: go.mod replace directives to module paths is only supported in bazel 7.1.0-rc1 or later, + Because of this changes to %s will not be detected by bazel in previous versions.""" % ctx.attr.path) + + command = ["cp", "-r", "%s/" % ctx.attr.path, ctx.path("")] + result = env_execute( + ctx, + command, + environment = local_path_env, + timeout = _GO_REPOSITORY_TIMEOUT, + ) + + # use "go mod download" to satisfy the dependencies of this module + if ctx.attr.debug_mode and result.stderr: + print("copy mod: %s", result.stderr) + + if result.return_code: + fail(command) + + # TODO: this needs to have a go mod with the same go version as the + # project, or it may fail as not all go versions are available + # locally + command = ["go", "mod", "download", "-json", "-modcacherw"] + result = env_execute( + ctx, + command, + environment = local_path_env, + timeout = _GO_REPOSITORY_TIMEOUT, + working_directory = "%s" % ctx.path(""), + ) + + # use "go mod download" to satisfy the dependencies of this module + if result.stderr: + print("mod download: %s", result.stderr) + + if result.return_code: + fail(command) + + # use "go mod download" to satisfy the dependencies of this module + if fetch_repo_args: # Disable sumdb in fetch_repo. In module mode, the sum is a mandatory # attribute of go_repository, so we don't need to look it up. @@ -252,10 +306,12 @@ def _go_repository_impl(ctx): environment = fetch_repo_env, timeout = _GO_REPOSITORY_TIMEOUT, ) + + if result.stderr: + print("copy_repo: " + result.stderr) + if result.return_code: - fail("failed to fetch %s: %s" % (ctx.name, result.stderr)) - if ctx.attr.debug_mode and result.stderr: - print("fetch_repo: " + result.stderr) + fail("%s: %s" % (ctx.name, result.stderr)) # Repositories are fetched. Determine if build file generation is needed. build_file_names = ctx.attr.build_file_name.split(",") @@ -307,7 +363,7 @@ def _go_repository_impl(ctx): "-repo_config", repo_config, ] - if ctx.attr.version: + if ctx.attr.version or ctx.attr.path: cmd.append("-go_repository_module_mode") if ctx.attr.build_file_name: cmd.extend(["-build_file_name", ctx.attr.build_file_name]) @@ -421,6 +477,11 @@ go_repository = repository_rule( doc = _AUTH_PATTERN_DOC, ), + # Attributes for a module that should be loaded from the local file system. + "path": attr.string( + doc = """ If specified, `go_repository` will load the module from this local directory""", + ), + # Attributes for a module that should be downloaded with the Go toolchain. "version": attr.string( doc = """If specified, `go_repository` will download the module at this version diff --git a/repository.md b/repository.md index 406c69d4f..f0e8b5a46 100644 --- a/repository.md +++ b/repository.md @@ -103,8 +103,8 @@ git_repository( go_repository(name, auth_patterns, build_config, build_directives, build_external, build_extra_args, build_file_generation, build_file_name, build_file_proto_mode, build_naming_convention, build_tags, canonical_id, commit, debug_mode, importpath, patch_args, patch_cmds, - patch_tool, patches, remote, replace, repo_mapping, sha256, strip_prefix, sum, tag, - type, urls, vcs, version) + patch_tool, patches, path, remote, replace, repo_mapping, sha256, strip_prefix, sum, + tag, type, urls, vcs, version) `go_repository` downloads a Go project and generates build files with Gazelle @@ -189,6 +189,7 @@ go_repository( | patch_cmds | Commands to run in the repository after patches are applied. | List of strings | optional | `[]` | | patch_tool | The patch tool used to apply `patches`. If this is specified, Bazel will use the specifed patch tool instead of the Bazel-native patch implementation. | String | optional | `""` | | patches | A list of patches to apply to the repository after gazelle runs. | List of labels | optional | `[]` | +| path | If specified, `go_repository` will load the module from this local directory | String | optional | `""` | | remote | The VCS location where the repository should be downloaded from. This is usually inferred from `importpath`, but you can set `remote` to download from a private repository or a fork. | String | optional | `""` | | replace | A replacement for the module named by `importpath`. The module named by `replace` will be downloaded at `version` and verified with `sum`.

NOTE: There is no `go_repository` equivalent to file path `replace` directives. Use `local_repository` instead. | String | optional | `""` | | repo_mapping | A dictionary from local repository name to global repository name. This allows controls over workspace dependency resolution for dependencies of this repository.

For example, an entry `"@foo": "@bar"` declares that, for any time this repository depends on `@foo` (such as a dependency on `@foo//some:target`, it should actually resolve that dependency within globally-declared `@bar` (`@bar//some:target`). | Dictionary: String -> String | required | |