build/toolbase/gotoolchain: init
Another piece of the toolbase puzzle, this one is a library which
provides information about the Go SDK picked by Bazel/rules_go, and
allows to build tools that call the `go` tool.
This is effectively the logic from //build/fietsje:def.bzl, but
rewritten to be reusable. In a later CL, we will make Fietsje use this
logic instead of its existing starlark/shell magic.
Change-Id: I2be723089410c81843b54df77bcd665a4e050cbb
Reviewed-on: https://review.monogon.dev/c/monogon/+/329
Reviewed-by: Mateusz Zalega <mateusz@monogon.tech>
diff --git a/build/toolbase/gotoolchain/BUILD.bazel b/build/toolbase/gotoolchain/BUILD.bazel
new file mode 100644
index 0000000..9579e2c
--- /dev/null
+++ b/build/toolbase/gotoolchain/BUILD.bazel
@@ -0,0 +1,25 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+load(":def.bzl", "toolchain_library")
+
+toolchain_library(
+ name = "toolchain_library",
+ importpath = "source.monogon.dev/build/toolbase/gotoolchain",
+ visibility = ["//visibility:public"],
+ deps = [
+ "@io_bazel_rules_go//go/tools/bazel:go_default_library",
+ ],
+)
+
+# keep
+go_library(
+ name = "go_default_library",
+ embed = [":toolchain_library"],
+ importpath = "source.monogon.dev/build/toolbase/gotoolchain",
+ visibility = ["//visibility:public"],
+)
+
+go_test(
+ name = "go_default_test",
+ srcs = ["toolchain_test.go"],
+ embed = [":go_default_library"], # keep
+)
diff --git a/build/toolbase/gotoolchain/def.bzl b/build/toolbase/gotoolchain/def.bzl
new file mode 100644
index 0000000..1722cc4
--- /dev/null
+++ b/build/toolbase/gotoolchain/def.bzl
@@ -0,0 +1,78 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_context", "GoSource")
+
+# This implements the toolchain_library rule, which is used to generate a
+# rules_go compatible go_library-style target which contains toolchain.go.in
+# augmented with information about the Go SDK's toolchain used by Bazel.
+#
+# The library can then be used to build tools that call the `go` tool, for
+# example to perform static analysis or dependency management.
+
+def _toolchain_library_impl(ctx):
+ go = go_context(ctx)
+
+ importpath = ctx.attr.importpath
+
+ out = go.declare_file(go, ext = ".go")
+ ctx.actions.expand_template(
+ template = ctx.file._template,
+ output = out,
+ substitutions = {
+ 'GOROOT': go.root,
+ 'GOTOOL': go.go.path,
+ },
+ )
+
+ library = go.new_library(go)
+ source = go.library_to_source(go, struct(
+ srcs = [struct(files = [out])],
+ deps = ctx.attr.deps,
+ ), library, ctx.coverage_instrumented())
+
+ # Hack: we want to inject runfiles into the generated GoSource, because
+ # there's no other way to make rules_go pick up runfiles otherwise.
+ runfiles = ctx.runfiles(files = [
+ go.go,
+ go.sdk_root,
+ ] + go.sdk_files)
+ source = {
+ key: getattr(source, key)
+ for key in dir(source)
+ if key not in ['to_json', 'to_proto']
+ }
+ source['runfiles'] = runfiles
+ source = GoSource(**source)
+ archive = go.archive(go, source)
+
+
+ return [
+ library,
+ source,
+ archive,
+ DefaultInfo(
+ files = depset([archive.data.file]),
+ runfiles = runfiles,
+ ),
+ OutputGroupInfo(
+ cgo_exports = archive.cgo_exports,
+ compilation_outputs = [archive.data.file],
+ ),
+ ]
+
+
+toolchain_library = rule(
+ implementation = _toolchain_library_impl,
+ attrs = {
+ "importpath": attr.string(
+ mandatory = True,
+ ),
+ "deps": attr.label_list(),
+ "_template": attr.label(
+ allow_single_file = True,
+ default = ":toolchain.go.in",
+ ),
+ "_go_context_data": attr.label(
+ default = "@io_bazel_rules_go//:go_context_data",
+ ),
+ },
+ toolchains = ["@io_bazel_rules_go//go:toolchain"],
+)
diff --git a/build/toolbase/gotoolchain/toolchain.go.in b/build/toolbase/gotoolchain/toolchain.go.in
new file mode 100644
index 0000000..ca3db65
--- /dev/null
+++ b/build/toolbase/gotoolchain/toolchain.go.in
@@ -0,0 +1,24 @@
+// gotoolchain provides information about the Go toolchain used on the host by
+// rules_go.
+package gotoolchain
+
+import (
+ "fmt"
+
+ "github.com/bazelbuild/rules_go/go/tools/bazel"
+)
+
+func mustRunfile(s string) string {
+ res, err := bazel.Runfile(s)
+ if err != nil {
+ panic(fmt.Sprintf("runfile %q not found: %v", s, err))
+ }
+ return res
+}
+
+var (
+ // Go is a path to the `go` executable.
+ Go = mustRunfile(`GOTOOL`)
+ // Root is the GOROOT path.
+ Root = mustRunfile(`GOROOT`)
+)
diff --git a/build/toolbase/gotoolchain/toolchain_test.go b/build/toolbase/gotoolchain/toolchain_test.go
new file mode 100644
index 0000000..7b93d6d
--- /dev/null
+++ b/build/toolbase/gotoolchain/toolchain_test.go
@@ -0,0 +1,22 @@
+package gotoolchain
+
+import (
+ "os"
+ "os/exec"
+ "path"
+ "testing"
+)
+
+func TestGoToolRuns(t *testing.T) {
+ cmd := exec.Command(Go, "version")
+ if out, err := cmd.CombinedOutput(); err != nil {
+ t.Fatalf("Failed to run `go version`: %q, %v", string(out), err)
+ }
+}
+
+func TestGorootContainsRoot(t *testing.T) {
+ rootfile := path.Join(Root, "ROOT")
+ if _, err := os.Stat(rootfile); err != nil {
+ t.Fatalf("ROOT not found in %s", Root)
+ }
+}