diff --git a/cmd/gazelle/fix-update.go b/cmd/gazelle/fix-update.go index 759723068..355145819 100644 --- a/cmd/gazelle/fix-update.go +++ b/cmd/gazelle/fix-update.go @@ -63,8 +63,10 @@ func getUpdateConfig(c *config.Config) *updateConfig { } type updateConfigurer struct { - mode string - recursive bool + mode string + recursive bool + knownImports []string + repoConfigPath string } func (ucr *updateConfigurer) RegisterFlags(fs *flag.FlagSet, cmd string, c *config.Config) { @@ -76,6 +78,8 @@ func (ucr *updateConfigurer) RegisterFlags(fs *flag.FlagSet, cmd string, c *conf fs.StringVar(&ucr.mode, "mode", "fix", "print: prints all of the updated BUILD files\n\tfix: rewrites all of the BUILD files in place\n\tdiff: computes the rewrite but then just does a diff") fs.BoolVar(&ucr.recursive, "r", true, "when true, gazelle will update subdirectories recursively") fs.StringVar(&uc.patchPath, "patch", "", "when set with -mode=diff, gazelle will write to a file instead of stdout") + fs.Var(&gzflag.MultiFlag{Values: &ucr.knownImports}, "known_import", "import path for which external resolution is skipped (can specify multiple times)") + fs.StringVar(&ucr.repoConfigPath, "repo_config", "", "file where Gazelle should load repository configuration. Defaults to WORKSPACE.") } func (ucr *updateConfigurer) CheckFlags(fs *flag.FlagSet, c *config.Config) error { @@ -118,6 +122,34 @@ func (ucr *updateConfigurer) CheckFlags(fs *flag.FlagSet, c *config.Config) erro uc.walkMode = walk.UpdateDirsMode } + if ucr.repoConfigPath == "" { + ucr.repoConfigPath = filepath.Join(c.RepoRoot, "WORKSPACE") + } + if repoConfigFile, err := rule.LoadWorkspaceFile(ucr.repoConfigPath, ""); err != nil { + if !os.IsNotExist(err) { + return err + } + } else { + uc.repos, _, err = repo.ListRepositories(repoConfigFile) + if err != nil { + return err + } + } + repoPrefixes := make(map[string]bool) + for _, r := range uc.repos { + repoPrefixes[r.GoPrefix] = true + } + for _, imp := range ucr.knownImports { + if repoPrefixes[imp] { + continue + } + repo := repo.Repo{ + Name: label.ImportPathToBazelRepoName(imp), + GoPrefix: imp, + } + uc.repos = append(uc.repos, repo) + } + return nil } @@ -331,9 +363,6 @@ func newFixUpdateConfiguration(cmd command, args []string, cexts []config.Config // -h or -help were passed explicitly. fs.Usage = func() {} - var knownImports []string - fs.Var(&gzflag.MultiFlag{Values: &knownImports}, "known_import", "import path for which external resolution is skipped (can specify multiple times)") - for _, cext := range cexts { cext.RegisterFlags(fs, cmd.String(), c) } @@ -353,7 +382,6 @@ func newFixUpdateConfiguration(cmd command, args []string, cexts []config.Config } } - uc := getUpdateConfig(c) workspacePath := filepath.Join(c.RepoRoot, "WORKSPACE") if workspace, err := rule.LoadWorkspaceFile(workspacePath, ""); err != nil { if !os.IsNotExist(err) { @@ -362,7 +390,7 @@ func newFixUpdateConfiguration(cmd command, args []string, cexts []config.Config } else { c.RepoName = findWorkspaceName(workspace) var reposFiles map[*rule.File][]string - uc.repos, reposFiles, err = repo.ListRepositories(workspace) + _, reposFiles, err = repo.ListRepositories(workspace) if err != nil { return nil, err } @@ -378,20 +406,6 @@ func newFixUpdateConfiguration(cmd command, args []string, cexts []config.Config return nil, err } } - repoPrefixes := make(map[string]bool) - for _, r := range uc.repos { - repoPrefixes[r.GoPrefix] = true - } - for _, imp := range knownImports { - if repoPrefixes[imp] { - continue - } - repo := repo.Repo{ - Name: label.ImportPathToBazelRepoName(imp), - GoPrefix: imp, - } - uc.repos = append(uc.repos, repo) - } return c, nil } diff --git a/cmd/gazelle/integration_test.go b/cmd/gazelle/integration_test.go index dd94a70f7..287235235 100644 --- a/cmd/gazelle/integration_test.go +++ b/cmd/gazelle/integration_test.go @@ -1064,7 +1064,7 @@ go_library( }) } -func TestCustomRepoNames(t *testing.T) { +func TestCustomRepoNamesMain(t *testing.T) { files := []testtools.FileSpec{ { Path: "WORKSPACE", @@ -1110,6 +1110,60 @@ go_library( }) } +func TestCustomRepoNamesExternal(t *testing.T) { + files := []testtools.FileSpec{ + { + Path: "main/WORKSPACE", + Content: `go_repository( + name = "custom_repo", + importpath = "example.com/bar", + commit = "123456", +) +`, + }, { + Path: "ext/WORKSPACE", + Content: "", + }, { + Path: "ext/foo.go", + Content: ` +package foo + +import _ "example.com/bar" +`, + }, + } + dir, cleanup := testtools.CreateFiles(t, files) + defer cleanup() + + extDir := filepath.Join(dir, "ext") + args := []string{ + "-go_prefix=example.com/foo", + "-mode=fix", + "-repo_root=" + extDir, + "-repo_config=" + filepath.Join(dir, "main", "WORKSPACE"), + } + if err := runGazelle(extDir, args); err != nil { + t.Fatal(err) + } + + testtools.CheckFiles(t, dir, []testtools.FileSpec{ + { + Path: "ext/BUILD.bazel", + Content: ` +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["foo.go"], + importpath = "example.com/foo", + visibility = ["//visibility:public"], + deps = ["@custom_repo//:go_default_library"], +) +`, + }, + }) +} + func TestImportReposFromDepToWorkspace(t *testing.T) { files := []testtools.FileSpec{ { diff --git a/deps.bzl b/deps.bzl index 72dc4875b..0c29ea0dd 100644 --- a/deps.bzl +++ b/deps.bzl @@ -15,8 +15,14 @@ load( "@bazel_gazelle//internal:go_repository.bzl", _go_repository = "go_repository", - _go_repository_cache = "go_repository_cache", - _go_repository_tools = "go_repository_tools", +) +load( + "@bazel_gazelle//internal:go_repository_cache.bzl", + "go_repository_cache", +) +load( + "@bazel_gazelle//internal:go_repository_tools.bzl", + "go_repository_tools", ) load( "@bazel_gazelle//internal:overlay_repository.bzl", @@ -35,7 +41,7 @@ go_repository = _go_repository def gazelle_dependencies(go_sdk = ""): if go_sdk: - _go_repository_cache( + go_repository_cache( name = "bazel_gazelle_go_repository_cache", go_sdk_name = go_sdk, ) @@ -52,12 +58,12 @@ def gazelle_dependencies(go_sdk = ""): else: platform = "host" go_sdk_info[name] = platform - _go_repository_cache( + go_repository_cache( name = "bazel_gazelle_go_repository_cache", go_sdk_info = go_sdk_info, ) - _go_repository_tools( + go_repository_tools( name = "bazel_gazelle_go_repository_tools", go_cache = "@bazel_gazelle_go_repository_cache//:go.env", ) @@ -74,7 +80,7 @@ def gazelle_dependencies(go_sdk = ""): name = "com_github_bazelbuild_buildtools", importpath = "github.com/bazelbuild/buildtools", sum = "h1:KLCFP96KTydy03EMRwdGnngaD76gt34BBWK4g7TChgk=", - version = "v0.0.0-20190329162354-3f7be923c4b0", + version = "v0.0.0-20190329162354-3f7be923c4b0", ) _maybe( @@ -82,7 +88,7 @@ def gazelle_dependencies(go_sdk = ""): name = "com_github_fsnotify_fsnotify", importpath = "github.com/fsnotify/fsnotify", sum = "h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=", - version = "v1.4.7", + version = "v1.4.7", ) _maybe( diff --git a/go.mod b/go.mod index bed1b5276..faf465dd9 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,7 @@ module github.com/bazelbuild/bazel-gazelle +go 1.12 + require ( github.com/BurntSushi/toml v0.3.1 // indirect github.com/bazelbuild/buildtools v0.0.0-20190329162354-3f7be923c4b0 diff --git a/internal/go_repository.bzl b/internal/go_repository.bzl index 8bc2c5809..38a224226 100644 --- a/internal/go_repository.bzl +++ b/internal/go_repository.bzl @@ -13,11 +13,23 @@ # limitations under the License. load("@io_bazel_rules_go//go/private:common.bzl", "env_execute", "executable_extension") +load("@bazel_gazelle//internal:go_repository_cache.bzl", "read_cache_env") # We can't disable timeouts on Bazel, but we can set them to large values. _GO_REPOSITORY_TIMEOUT = 86400 def _go_repository_impl(ctx): + # Locate and resolve configuration files. Gazelle reads directives and + # known repositories from these files. Resolving them here forces + # go_repository rules to be invalidated when they change. Gazelle's cache + # should NOT be invalidated, so we shouldn't need to download these again. + # TODO(#549): vcs repositories are not cached and still need to be fetched. + workspace_label = Label("@//:WORKSPACE") + workspace_path = ctx.path(workspace_label) + for label in _find_macro_file_labels(ctx, workspace_label): + ctx.path(label) + + # Download the repository or module. fetch_repo_args = None if ctx.attr.urls: @@ -72,7 +84,7 @@ def _go_repository_impl(ctx): fail("one of urls, commit, tag, or importpath must be specified") if fetch_repo_args or generate: - env = _read_cache_env(ctx, str(ctx.path(Label("@bazel_gazelle_go_repository_cache//:go.env")))) + env = read_cache_env(ctx, str(ctx.path(Label("@bazel_gazelle_go_repository_cache//:go.env")))) env_keys = [ "GOPROXY", "PATH", @@ -103,6 +115,7 @@ def _go_repository_impl(ctx): if result.stderr: print("fetch_repo: " + result.stderr) + # Generate build files if needed. generate = ctx.attr.build_file_generation == "on" if ctx.attr.build_file_generation == "auto": generate = True @@ -124,6 +137,8 @@ def _go_repository_impl(ctx): "fix", "-repo_root", ctx.path(""), + "-repo_config", + str(workspace_path), ] if ctx.attr.version: cmd.append("-go_experimental_module_mode") @@ -243,221 +258,51 @@ def patch(ctx): fail("Error applying patch command %s:\n%s%s" % (cmd, st.stdout, st.stderr)) -def _go_repository_cache_impl(ctx): - if ctx.attr.go_sdk_name: - go_sdk_name = ctx.attr.go_sdk_name - else: - host_platform = _detect_host_platform(ctx) - matches = [ - name - for name, platform in ctx.attr.go_sdk_info.items() - if host_platform == platform or platform == "host" - ] - if len(matches) > 1: - fail('gazelle found more than one suitable Go SDK ({}). Specify which one to use with gazelle_dependencies(go_sdk = "go_sdk").'.format(", ".join(matches))) - if len(matches) == 0: - fail('gazelle could not find a Go SDK. Specify which one to use with gazelle_dependencies(go_sdk = "go_sdk").') - if len(matches) == 1: - go_sdk_name = matches[0] - - go_sdk_label = Label("@" + go_sdk_name + "//:ROOT") - - go_root = str(ctx.path(go_sdk_label).dirname) - go_path = str(ctx.path(".")) - go_cache = str(ctx.path("gocache")) - if ctx.os.environ.get("GO_REPOSITORY_USE_HOST_CACHE", "") == "1": - extension = executable_extension(ctx) - go_tool = go_root + "/bin/go" + extension - res = ctx.execute([go_tool, "env", "GOPATH"]) - if res.return_code: - fail("failed to read go environment: " + res.stderr) - if not res.stdout: - fail("GOPATH must be set when GO_REPOSITORY_USE_HOST_CACHE is enabled.") - go_path = res.stdout.strip() - res = ctx.execute([go_tool, "env", "GOCACHE"]) - if res.return_code: - fail("failed to read go environment: " + res.stderr) - if not res.stdout: - fail("GOCACHE must be set when GO_REPOSITORY_USE_HOST_CACHE is enabled.") - go_cache = res.stdout.strip() - - env_tpl = """ -GOROOT='{goroot}' -GOPATH='{gopath}' -GOCACHE='{gocache}' -""" - env_content = env_tpl.format( - goroot = go_root, - gopath = go_path, - gocache = go_cache, - ) - ctx.file("go.env", env_content) - ctx.file("BUILD.bazel", 'exports_files(["go.env"])') +def _find_macro_file_labels(ctx, label): + """Returns a list of labels for configuration files that Gazelle may read. -go_repository_cache = repository_rule( - _go_repository_cache_impl, - attrs = { - "go_sdk_name": attr.string(), - "go_sdk_info": attr.string_dict(), - }, -) + The list is gathered by reading '# gazelle:repository_macro' directives + from the file named by label (which is not included in the returned list). + """ + seen = {} + files = [] -def _read_cache_env(ctx, path): - result = ctx.execute(["cat", path]) - if result.return_code: - fail("failed to read cache environment: " + result.stderr) - env = {} - lines = result.stdout.split("\n") + if "read" in dir(ctx): + # TODO(jayconrod): not supported in Bazel 0.23.0. Use directly when + # minimum version of Bazel supports this. + content = ctx.read(label) + else: + result = ctx.execute(["cat", str(ctx.path(label))]) + if result.return_code == 0: + content = result.stdout + else: + # TODO(jayconrod): "type" might work on Windows, but I think + # it's a shell builtin, and I'm not sure if ctx.execute will work. + content = "" + + lines = content.split("\n") for line in lines: - line = line.strip() - if line == "" or line.startswith("#"): + i = line.find("#") + if i < 0: continue - k, sep, v = line.partition("=") - if sep == "": - fail("failed to parse cache environment") - env[k] = v.strip("'") - return env - -_GO_REPOSITORY_TOOLS_BUILD_FILE = """ -package(default_visibility = ["//visibility:public"]) - -filegroup( - name = "fetch_repo", - srcs = ["bin/fetch_repo{extension}"], -) - -filegroup( - name = "gazelle", - srcs = ["bin/gazelle{extension}"], -) - -exports_files(["ROOT"]) -""" - -def _go_repository_tools_impl(ctx): - # Create a link to the gazelle repo. This will be our GOPATH. - env = _read_cache_env(ctx, str(ctx.path(ctx.attr.go_cache))) - extension = executable_extension(ctx) - go_tool = env["GOROOT"] + "/bin/go" + extension - - ctx.symlink( - ctx.path(Label("@bazel_gazelle//:WORKSPACE")).dirname, - "src/github.com/bazelbuild/bazel-gazelle", - ) - - # Resolve a label for each source file so this rule will be re-executed - # when they change. - list_script = str(ctx.path(Label("@bazel_gazelle//internal:list_repository_tools_srcs.go"))) - result = ctx.execute([go_tool, "run", list_script]) - if result.return_code: - print("could not resolve gazelle sources: " + result.stderr) - else: - for line in result.stdout.split("\n"): - line = line.strip() - if line == "": - continue - ctx.path(Label(line)) - - # Build the tools. - env.update({ - "GOPATH": str(ctx.path(".")), - "GO111MODULE": "off", - # workaround: to find gcc for go link tool on Arm platform - "PATH": ctx.os.environ["PATH"], - # workaround: avoid the Go SDK paths from leaking into the binary - "GOROOT_FINAL": "GOROOT", - # workaround: avoid cgo paths in /tmp leaking into binary - "CGO_ENABLED": "0", - }) - if "GOPROXY" in ctx.os.environ: - env["GOPROXY"] = ctx.os.environ["GOPROXY"] - - args = [ - go_tool, - "install", - "-ldflags", - "-w -s", - "-gcflags", - "all=-trimpath=" + env["GOPATH"], - "-asmflags", - "all=-trimpath=" + env["GOPATH"], - "github.com/bazelbuild/bazel-gazelle/cmd/gazelle", - "github.com/bazelbuild/bazel-gazelle/cmd/fetch_repo", - ] - result = env_execute(ctx, args, environment = env) - if result.return_code: - fail("failed to build tools: " + result.stderr) - - # add a build file to export the tools - ctx.file( - "BUILD.bazel", - _GO_REPOSITORY_TOOLS_BUILD_FILE.format(extension = executable_extension(ctx)), - False, - ) - ctx.file( - "ROOT", - "", - False, - ) - -go_repository_tools = repository_rule( - _go_repository_tools_impl, - attrs = { - "go_cache": attr.label( - mandatory = True, - allow_single_file = True, - ), - }, - environ = [ - "GOCACHE", - "GOPATH", - "GO_REPOSITORY_USE_HOST_CACHE", - "TMP", - ], -) -"""go_repository_tools is a synthetic repository used by go_repository. - -go_repository depends on two Go binaries: fetch_repo and gazelle. We can't -build these with Bazel inside a repository rule, and we don't want to manage -prebuilt binaries, so we build them in here with go build, using whichever -SDK rules_go is using. -""" - -# copied from rules_go. Keep in sync. -def _detect_host_platform(ctx): - if ctx.os.name == "linux": - host = "linux_amd64" - res = ctx.execute(["uname", "-p"]) - if res.return_code == 0: - uname = res.stdout.strip() - if uname == "s390x": - host = "linux_s390x" - elif uname == "i686": - host = "linux_386" - - # uname -p is not working on Aarch64 boards - # or for ppc64le on some distros - res = ctx.execute(["uname", "-m"]) - if res.return_code == 0: - uname = res.stdout.strip() - if uname == "aarch64": - host = "linux_arm64" - elif uname == "armv6l": - host = "linux_arm" - elif uname == "armv7l": - host = "linux_arm" - elif uname == "ppc64le": - host = "linux_ppc64le" - - # Default to amd64 when uname doesn't return a known value. - - elif ctx.os.name == "mac os x": - host = "darwin_amd64" - elif ctx.os.name.startswith("windows"): - host = "windows_amd64" - elif ctx.os.name == "freebsd": - host = "freebsd_amd64" - else: - fail("Unsupported operating system: " + ctx.os.name) + line = line[i + len("#"):] + i = line.find("gazelle:") + if i < 0 or not line[:i].isspace(): + continue + line = line[i + len("gazelle:"):] + i = line.find("repository_macro") + if i < 0 or (i > 0 and not line[:i].isspace()): + continue + line = line[i + len("repository_macro"):] + if len(line) == 0 or not line[0].isspace(): + continue + i = line.rfind("%") + if i < 0: + continue + line = line[:i].lstrip() + macro_label = Label("@//:" + line) + if macro_label not in seen: + seen[macro_label] = None + files.append(macro_label) - return host + return files diff --git a/internal/go_repository_cache.bzl b/internal/go_repository_cache.bzl new file mode 100644 index 000000000..3c3e08f41 --- /dev/null +++ b/internal/go_repository_cache.bzl @@ -0,0 +1,131 @@ +# Copyright 2019 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@io_bazel_rules_go//go/private:common.bzl", "executable_extension") + +def _go_repository_cache_impl(ctx): + if ctx.attr.go_sdk_name: + go_sdk_name = ctx.attr.go_sdk_name + else: + host_platform = _detect_host_platform(ctx) + matches = [ + name + for name, platform in ctx.attr.go_sdk_info.items() + if host_platform == platform or platform == "host" + ] + if len(matches) > 1: + fail('gazelle found more than one suitable Go SDK ({}). Specify which one to use with gazelle_dependencies(go_sdk = "go_sdk").'.format(", ".join(matches))) + if len(matches) == 0: + fail('gazelle could not find a Go SDK. Specify which one to use with gazelle_dependencies(go_sdk = "go_sdk").') + if len(matches) == 1: + go_sdk_name = matches[0] + + go_sdk_label = Label("@" + go_sdk_name + "//:ROOT") + + go_root = str(ctx.path(go_sdk_label).dirname) + go_path = str(ctx.path(".")) + go_cache = str(ctx.path("gocache")) + if ctx.os.environ.get("GO_REPOSITORY_USE_HOST_CACHE", "") == "1": + extension = executable_extension(ctx) + go_tool = go_root + "/bin/go" + extension + res = ctx.execute([go_tool, "env", "GOPATH"]) + if res.return_code: + fail("failed to read go environment: " + res.stderr) + if not res.stdout: + fail("GOPATH must be set when GO_REPOSITORY_USE_HOST_CACHE is enabled.") + go_path = res.stdout.strip() + res = ctx.execute([go_tool, "env", "GOCACHE"]) + if res.return_code: + fail("failed to read go environment: " + res.stderr) + if not res.stdout: + fail("GOCACHE must be set when GO_REPOSITORY_USE_HOST_CACHE is enabled.") + go_cache = res.stdout.strip() + + env_tpl = """ +GOROOT='{goroot}' +GOPATH='{gopath}' +GOCACHE='{gocache}' +""" + env_content = env_tpl.format( + goroot = go_root, + gopath = go_path, + gocache = go_cache, + ) + ctx.file("go.env", env_content) + ctx.file("BUILD.bazel", 'exports_files(["go.env"])') + +go_repository_cache = repository_rule( + _go_repository_cache_impl, + attrs = { + "go_sdk_name": attr.string(), + "go_sdk_info": attr.string_dict(), + }, + # Don't put anything in environ. If we switch between the host cache + # and Bazel's cache, it shouldn't actually invalidate Bazel's cache. +) + +def read_cache_env(ctx, path): + result = ctx.execute(["cat", path]) + if result.return_code: + fail("failed to read cache environment: " + result.stderr) + env = {} + lines = result.stdout.split("\n") + for line in lines: + line = line.strip() + if line == "" or line.startswith("#"): + continue + k, sep, v = line.partition("=") + if sep == "": + fail("failed to parse cache environment") + env[k] = v.strip("'") + return env + +# copied from rules_go. Keep in sync. +def _detect_host_platform(ctx): + if ctx.os.name == "linux": + host = "linux_amd64" + res = ctx.execute(["uname", "-p"]) + if res.return_code == 0: + uname = res.stdout.strip() + if uname == "s390x": + host = "linux_s390x" + elif uname == "i686": + host = "linux_386" + + # uname -p is not working on Aarch64 boards + # or for ppc64le on some distros + res = ctx.execute(["uname", "-m"]) + if res.return_code == 0: + uname = res.stdout.strip() + if uname == "aarch64": + host = "linux_arm64" + elif uname == "armv6l": + host = "linux_arm" + elif uname == "armv7l": + host = "linux_arm" + elif uname == "ppc64le": + host = "linux_ppc64le" + + # Default to amd64 when uname doesn't return a known value. + + elif ctx.os.name == "mac os x": + host = "darwin_amd64" + elif ctx.os.name.startswith("windows"): + host = "windows_amd64" + elif ctx.os.name == "freebsd": + host = "freebsd_amd64" + else: + fail("Unsupported operating system: " + ctx.os.name) + + return host diff --git a/internal/go_repository_tools.bzl b/internal/go_repository_tools.bzl new file mode 100644 index 000000000..1c1344d78 --- /dev/null +++ b/internal/go_repository_tools.bzl @@ -0,0 +1,121 @@ +# Copyright 2019 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@io_bazel_rules_go//go/private:common.bzl", "env_execute", "executable_extension") +load("@bazel_gazelle//internal:go_repository_cache.bzl", "read_cache_env") + +_GO_REPOSITORY_TOOLS_BUILD_FILE = """ +package(default_visibility = ["//visibility:public"]) + +filegroup( + name = "fetch_repo", + srcs = ["bin/fetch_repo{extension}"], +) + +filegroup( + name = "gazelle", + srcs = ["bin/gazelle{extension}"], +) + +exports_files(["ROOT"]) +""" + +def _go_repository_tools_impl(ctx): + # Create a link to the gazelle repo. This will be our GOPATH. + env = read_cache_env(ctx, str(ctx.path(ctx.attr.go_cache))) + extension = executable_extension(ctx) + go_tool = env["GOROOT"] + "/bin/go" + extension + + ctx.symlink( + ctx.path(Label("@bazel_gazelle//:WORKSPACE")).dirname, + "src/github.com/bazelbuild/bazel-gazelle", + ) + + # Resolve a label for each source file so this rule will be re-executed + # when they change. + list_script = str(ctx.path(Label("@bazel_gazelle//internal:list_repository_tools_srcs.go"))) + result = ctx.execute([go_tool, "run", list_script]) + if result.return_code: + print("could not resolve gazelle sources: " + result.stderr) + else: + for line in result.stdout.split("\n"): + line = line.strip() + if line == "": + continue + ctx.path(Label(line)) + + # Build the tools. + env.update({ + "GOPATH": str(ctx.path(".")), + "GO111MODULE": "off", + # workaround: to find gcc for go link tool on Arm platform + "PATH": ctx.os.environ["PATH"], + # workaround: avoid the Go SDK paths from leaking into the binary + "GOROOT_FINAL": "GOROOT", + # workaround: avoid cgo paths in /tmp leaking into binary + "CGO_ENABLED": "0", + }) + if "GOPROXY" in ctx.os.environ: + env["GOPROXY"] = ctx.os.environ["GOPROXY"] + + args = [ + go_tool, + "install", + "-ldflags", + "-w -s", + "-gcflags", + "all=-trimpath=" + env["GOPATH"], + "-asmflags", + "all=-trimpath=" + env["GOPATH"], + "github.com/bazelbuild/bazel-gazelle/cmd/gazelle", + "github.com/bazelbuild/bazel-gazelle/cmd/fetch_repo", + ] + result = env_execute(ctx, args, environment = env) + if result.return_code: + fail("failed to build tools: " + result.stderr) + + # add a build file to export the tools + ctx.file( + "BUILD.bazel", + _GO_REPOSITORY_TOOLS_BUILD_FILE.format(extension = executable_extension(ctx)), + False, + ) + ctx.file( + "ROOT", + "", + False, + ) + +go_repository_tools = repository_rule( + _go_repository_tools_impl, + attrs = { + "go_cache": attr.label( + mandatory = True, + allow_single_file = True, + ), + }, + environ = [ + "GOCACHE", + "GOPATH", + "GO_REPOSITORY_USE_HOST_CACHE", + ], +) +"""go_repository_tools is a synthetic repository used by go_repository. + + +go_repository depends on two Go binaries: fetch_repo and gazelle. We can't +build these with Bazel inside a repository rule, and we don't want to manage +prebuilt binaries, so we build them in here with go build, using whichever +SDK rules_go is using. +"""