aboutsummaryrefslogtreecommitdiffhomepage
path: root/tools
diff options
context:
space:
mode:
authorGravatar Han-Wen Nienhuys <hanwen@google.com>2015-10-09 16:30:46 +0000
committerGravatar Kristina Chodorow <kchodorow@google.com>2015-10-09 18:05:03 +0000
commit3cadf0a12cc3eb4fccb41666da34dd93f69ed64b (patch)
tree863fd79bb61e4c539fe5430a0949fe573c5d2a0a /tools
parent82d5b6241c853f9fdca000ee391b852315c3720a (diff)
RELNOTES: Go rules for Bazel.
-- MOS_MIGRATED_REVID=105062625
Diffstat (limited to 'tools')
-rw-r--r--tools/build_rules/go/BUILD3
-rw-r--r--tools/build_rules/go/README.md78
-rw-r--r--tools/build_rules/go/def.bzl331
-rw-r--r--tools/build_rules/go/toolchain/BUILD13
-rw-r--r--tools/build_rules/go/toolchain/BUILD.go-toolchain12
-rw-r--r--tools/build_rules/go/toolchain/WORKSPACE.go-toolchain12
-rw-r--r--tools/build_rules/go/tools/BUILD9
-rw-r--r--tools/build_rules/go/tools/generate_test_main.go91
8 files changed, 549 insertions, 0 deletions
diff --git a/tools/build_rules/go/BUILD b/tools/build_rules/go/BUILD
new file mode 100644
index 0000000000..917ca91a75
--- /dev/null
+++ b/tools/build_rules/go/BUILD
@@ -0,0 +1,3 @@
+package(
+ visibility = ["//visibility:public"],
+)
diff --git a/tools/build_rules/go/README.md b/tools/build_rules/go/README.md
new file mode 100644
index 0000000000..af5d416267
--- /dev/null
+++ b/tools/build_rules/go/README.md
@@ -0,0 +1,78 @@
+Go rules
+--------
+
+The rules should be considered experimental. They support:
+
+* libraries
+* binaries
+* tests
+* vendoring
+
+They currently do not support (in order of importance):
+
+* Darwin
+* //+build tags
+* auto generated BUILD files.
+* C/C++ interoperation (cgo, swig etc.)
+* race detector
+* coverage
+* test sharding
+
+Setup
+-----
+
+* Decide on the name of your package, eg. "github.com/joe/project"
+
+* Copy tools/build_rules/go/toolchain/WORKSPACE.go-toolchain to WORKSPACE
+
+* Add a BUILD file to the top of your workspace, containing
+
+ load("/tools/build_rules/go/def", "go_prefix")
+ go_prefix("github.com/joe/project")
+
+* For a library "github.com/joe/project/lib", create lib/BUILD, containing
+
+ load("/tools/build_rules/go/def", "go_library")
+ go_library(
+ name = "go_default_library",
+ srcs = ["file.go"])
+
+* Inside your project, you can use this library by declaring a dependency
+
+ go_binary( ...
+ deps = ["//lib:go_default_library"])
+
+* In this case, import the library as "github.com/joe/project/lib".
+
+* For vendored libraries, you may depend on
+ "//lib/vendor/github_com/user/project:go_default_library". Vendored
+ libraries should have BUILD files like normal libraries.
+
+* To declare a test,
+
+ go_test(
+ name = "mytest",
+ srcs = ["file_test.go"],
+ library = ":go_default_library")
+
+FAQ
+---
+
+# Can I still use the =go= tool?
+
+Yes, this setup was deliberately chosen to be compatible with the =go=
+tool. Make sure your workspace appears under
+
+ $GOROOT/src/github.com/joe/project/
+
+eg.
+
+ mkdir -p $GOROOT/src/github.com/joe/
+ ln -s my/bazel/workspace $GOROOT/src/github.com/joe/project
+
+and it should work.
+
+Disclaimer
+----------
+
+These rules are not supported by Google's Go team.
diff --git a/tools/build_rules/go/def.bzl b/tools/build_rules/go/def.bzl
new file mode 100644
index 0000000000..a6c22c6045
--- /dev/null
+++ b/tools/build_rules/go/def.bzl
@@ -0,0 +1,331 @@
+# Copyright 2014 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.
+
+"""These are bare-bones Go rules.
+
+In order of priority:
+
+- No support for build tags
+
+- BUILD file must be written by hand.
+
+- No C++ interop (SWIG, cgo).
+
+- No test sharding or test XML.
+
+"""
+
+_DEFAULT_LIB = "go_default_library"
+_VENDOR_PREFIX = "/vendor/"
+
+go_filetype = FileType([".go"])
+
+################
+
+# In Go, imports are always fully qualified with a URL,
+# eg. github.com/user/project. Hence, a label //foo:bar from within a
+# Bazel workspace must be referred to as
+# "github.com/user/project/foo/bar". To make this work, each rule must
+# know the repository's URL. This is achieved, by having all go rules
+# depend on a globally unique target that has a "go_prefix" transitive
+# info provider.
+
+def _go_prefix_impl(ctx):
+ """go_prefix_impl provides the go prefix to use as a transitive info provider."""
+ return struct(go_prefix = ctx.attr.prefix)
+
+def _go_prefix(ctx):
+ """slash terminated go-prefix"""
+ prefix = ctx.attr.go_prefix.go_prefix
+ if not prefix.endswith("/"):
+ prefix = prefix + "/"
+ return prefix
+
+_go_prefix_rule = rule(
+ _go_prefix_impl,
+ attrs = {
+ "prefix": attr.string(mandatory=True),
+ },
+ )
+
+def go_prefix(prefix):
+ """go_prefix sets the Go import name to be used for this workspace."""
+ _go_prefix_rule(name = "go_prefix",
+ prefix = prefix,
+ visibility = ["//visibility:public" ]
+ )
+
+################
+
+# TODO(bazel-team): it would be nice if Bazel had this built-in.
+def symlink_tree_commands(dest_dir, artifact_dict):
+ """Symlink_tree_commands returns a list of commands to create the
+ dest_dir, and populate it according to the given dict.
+
+ Args:
+ dest_dir: The destination directory, a string.
+ artifact_dict: The mapping of exec-path => path in the dest_dir.
+
+ Returns:
+ A list of commands that will setup the symlink tree.
+ """
+ cmds = [
+ "rm -rf " + dest_dir,
+ "mkdir -p " + dest_dir,
+ ]
+
+ for old_path, new_path in artifact_dict.items():
+ new_dir = new_path[:new_path.rfind('/')]
+ up = (new_dir.count('/') + 1 +
+ dest_dir.count('/') + 1)
+ cmds += [
+ "mkdir -p %s/%s" % (dest_dir, new_dir),
+ "ln -s %s%s %s/%s" % ('../' * up, old_path, dest_dir, new_path),
+ ]
+ return cmds
+
+
+
+def emit_go_compile_action(ctx, sources, deps, out_lib):
+ """Construct the command line for compiling Go code.
+ Constructs a symlink tree to accomodate for workspace name.
+
+ Args:
+ ctx: The skylark Context.
+ sources: an iterable of source code artifacts (or CTs? or labels?)
+ deps: an iterable of dependencies. Each dependency d should have an
+ artifact in d.go_library_object representing an imported library.
+ out_lib: the artifact (configured target?) that should be produced
+ """
+ config_strip = len(ctx.configuration.bin_dir.path) + 1
+
+ out_dir = out_lib.path + ".dir"
+ out_depth = out_dir.count('/') + 1
+ tree_layout = {}
+ inputs = []
+ prefix = _go_prefix(ctx)
+ import_map = {}
+ for d in deps:
+ library_artifact_path = d.go_library_object.path[config_strip:]
+ tree_layout[d.go_library_object.path] = prefix + library_artifact_path
+ inputs += [d.go_library_object]
+
+ source_import = prefix + d.label.package + "/" + d.label.name
+ actual_import = prefix + d.label.package + "/" + d.label.name
+ if d.label.name == _DEFAULT_LIB:
+ source_import = prefix + d.label.package
+
+ if source_import.rfind(_VENDOR_PREFIX) != -1:
+ source_import = source_import[len(_VENDOR_PREFIX) + source_import.rfind(_VENDOR_PREFIX):]
+
+ if source_import != actual_import:
+ if source_import in import_map:
+ fail("duplicate import %s: adding %s and have %s"
+ % (source_import, actual_import, import_map[source_import]))
+ import_map[source_import] = actual_import
+
+ inputs += list(sources)
+ for s in sources:
+ tree_layout[s.path] = prefix + s.path
+
+ cmds = symlink_tree_commands(out_dir, tree_layout)
+ args = [
+ "cd ", out_dir, "&&",
+ ('../' * out_depth) + ctx.file.go_tool.path,
+ "tool", "compile",
+ "-o", ('../' * out_depth) + out_lib.path, "-pack",
+
+ # Import path.
+ "-I", "."] + [
+ "-importmap=%s=%s" % (k,v) for k, v in import_map.items()
+ ]
+
+ # Set -p to the import path of the library, ie.
+ # (ctx.label.package + "/" ctx.label.name) for now.
+ cmds += [ "export GOROOT=$(pwd)/" + ctx.file.go_tool.dirname + "/..",
+ ' '.join(args + cmd_helper.template(sources, prefix + "%{path}"))]
+
+ ctx.action(
+ inputs = inputs + ctx.files.toolchain,
+ outputs = [out_lib],
+ mnemonic = "GoCompile",
+ command = " && ".join(cmds))
+
+
+def go_library_impl(ctx):
+ """Implements the go_library() rule."""
+
+ sources = set(ctx.files.srcs)
+ deps = ctx.attr.deps
+ if ctx.attr.library:
+ sources += ctx.attr.library.go_sources
+ deps += ctx.attr.library.direct_deps
+
+ if not sources:
+ fail("may not be empty", "srcs")
+
+ out_lib = ctx.outputs.lib
+ emit_go_compile_action(ctx, set(sources), deps, out_lib)
+
+ transitive_libs = set([out_lib])
+ for dep in ctx.attr.deps:
+ transitive_libs += dep.transitive_go_library_object
+
+ return struct(
+ label = ctx.label,
+ files = set([out_lib]),
+ direct_deps = deps,
+ go_sources = sources,
+ go_library_object = out_lib,
+ transitive_go_library_object = transitive_libs)
+
+
+def emit_go_link_action(ctx, transitive_libs, lib, executable):
+ """Sets up a symlink tree to libraries to link together."""
+ out_dir = executable.path + ".dir"
+ out_depth = out_dir.count('/') + 1
+ tree_layout = {}
+
+ config_strip = len(ctx.configuration.bin_dir.path) + 1
+ prefix = _go_prefix(ctx)
+
+ for l in transitive_libs:
+ library_artifact_path = l.path[config_strip:]
+ tree_layout[l.path] = prefix + library_artifact_path
+
+ tree_layout[lib.path] = prefix + lib.path[config_strip:]
+ tree_layout[executable.path] = prefix + executable.path[config_strip:]
+
+ cmds = symlink_tree_commands(out_dir, tree_layout)
+ cmds += [
+ "export GOROOT=$(pwd)/" + ctx.file.go_tool.dirname + "/..",
+ "cd " + out_dir,
+ ' '.join([
+ ('../' * out_depth) + ctx.file.go_tool.path,
+ "tool", "link", "-L", ".",
+ "-o", prefix + executable.path[config_strip:],
+ prefix + lib.path[config_strip:]])]
+
+ ctx.action(
+ inputs = list(transitive_libs) + [lib] + ctx.files.toolchain,
+ outputs = [executable],
+ command = ' && '.join(cmds),
+ mnemonic = "GoLink")
+
+
+def go_binary_impl(ctx):
+ """go_binary_impl emits actions for compiling and linking a go executable."""
+ lib_result = go_library_impl(ctx)
+ executable = ctx.outputs.executable
+ lib_out = ctx.outputs.lib
+
+ emit_go_link_action(
+ ctx, lib_result.transitive_go_library_object, lib_out, executable)
+ return struct(files = set([executable]) + lib_result.files)
+
+
+def go_test_impl(ctx):
+ """go_test_impl implements go testing.
+
+ It emits an action to run the test generator, and then compiles the
+ test into a binary."""
+
+ lib_result = go_library_impl(ctx)
+ main_go = ctx.outputs.main_go
+ prefix = _go_prefix(ctx)
+
+ go_import = prefix + ctx.label.package + "/" + ctx.label.name
+
+ args = (["--package", go_import, "--output", ctx.outputs.main_go.path] +
+ cmd_helper.template(lib_result.go_sources, "%{path}"))
+
+ inputs = list(lib_result.go_sources) + list(ctx.files.toolchain)
+ ctx.action(
+ inputs = inputs,
+ executable = ctx.executable.test_generator,
+ outputs = [main_go],
+ mnemonic = "GoTestGenTest",
+ arguments = args)
+
+ emit_go_compile_action(
+ ctx, set([main_go]), ctx.attr.deps + [lib_result], ctx.outputs.main_lib)
+
+ emit_go_link_action(
+ ctx, lib_result.transitive_go_library_object,
+ ctx.outputs.main_lib, ctx.outputs.executable)
+
+ # TODO(bazel-team): the Go tests should do a chdir to the directory
+ # holding the data files, so open-source go tests continue to work
+ # without code changes.
+ runfiles = ctx.runfiles(collect_data = True, files = [ctx.outputs.executable])
+ return struct(runfiles=runfiles)
+
+go_library_attrs = {
+ "data": attr.label_list(allow_files=True, cfg=DATA_CFG),
+ "srcs": attr.label_list(allow_files=go_filetype),
+ "deps": attr.label_list(
+ providers=[
+ "direct_deps",
+ "go_library_object",
+ "transitive_go_library_object",
+ ]),
+ "toolchain": attr.label(
+ default=Label("//tools/build_rules/go/toolchain:toolchain"),
+ allow_files=True,
+ cfg=HOST_CFG),
+ "go_tool": attr.label(
+ default=Label("//tools/build_rules/go/toolchain:go_tool"),
+ single_file = True,
+ allow_files=True,
+ cfg=HOST_CFG),
+ "library": attr.label(
+ providers=["go_sources"]),
+ "go_prefix": attr.label(
+ providers = [ "go_prefix" ],
+ default=Label("//external:go_prefix"),
+ allow_files=False, cfg=HOST_CFG),
+ }
+
+go_library_outputs = {
+ "lib": "%{name}.a",
+}
+
+go_library = rule(
+ go_library_impl,
+ attrs = go_library_attrs,
+ outputs = go_library_outputs)
+
+go_binary = rule(
+ go_binary_impl,
+ executable = True,
+ attrs = go_library_attrs + {
+ "stamp": attr.bool(default=False),
+ },
+ outputs = go_library_outputs)
+
+go_test = rule(
+ go_test_impl,
+ executable = True,
+ test = True,
+ attrs = go_library_attrs + {
+ "test_generator": attr.label(
+ executable=True,
+ default=Label("//tools/build_rules/go/tools:generate_test_main"),
+ cfg=HOST_CFG),
+ },
+ outputs = {
+ "lib" : "%{name}.a",
+ "main_lib": "%{name}_main_test.a",
+ "main_go": "%{name}_main_test.go",
+})
diff --git a/tools/build_rules/go/toolchain/BUILD b/tools/build_rules/go/toolchain/BUILD
new file mode 100644
index 0000000000..71b2bfec8c
--- /dev/null
+++ b/tools/build_rules/go/toolchain/BUILD
@@ -0,0 +1,13 @@
+# TODO(bazel-team): support darwin.
+
+filegroup(
+ name = "toolchain",
+ srcs = ["@golang-linux-amd64//:toolchain"],
+ visibility = ["//visibility:public"],
+)
+
+filegroup(
+ name = "go_tool",
+ srcs = ["@golang-linux-amd64//:go_tool"],
+ visibility = ["//visibility:public"],
+)
diff --git a/tools/build_rules/go/toolchain/BUILD.go-toolchain b/tools/build_rules/go/toolchain/BUILD.go-toolchain
new file mode 100644
index 0000000000..a32c134590
--- /dev/null
+++ b/tools/build_rules/go/toolchain/BUILD.go-toolchain
@@ -0,0 +1,12 @@
+package(
+ default_visibility = [ "//visibility:public" ])
+
+filegroup(
+ name = "toolchain",
+ srcs = glob(["go/bin/*", "go/pkg/linux_amd64/**", "go/pkg/tool/**", "go/pkg/include/**"]),
+)
+
+filegroup(
+ name = "go_tool",
+ srcs = [ "go/bin/go" ],
+)
diff --git a/tools/build_rules/go/toolchain/WORKSPACE.go-toolchain b/tools/build_rules/go/toolchain/WORKSPACE.go-toolchain
new file mode 100644
index 0000000000..5eed0f046c
--- /dev/null
+++ b/tools/build_rules/go/toolchain/WORKSPACE.go-toolchain
@@ -0,0 +1,12 @@
+# TODO(bazel-team): support darwin
+
+bind(name = "go_prefix",
+ actual = "//:go_prefix",
+)
+
+new_http_archive(
+ name= "golang-linux-amd64",
+ url = "https://storage.googleapis.com/golang/go1.5.1.linux-amd64.tar.gz",
+ build_file = "tools/build_rules/go/toolchain/BUILD.go-toolchain",
+ sha256 = "2593132ca490b9ee17509d65ee2cd078441ff544899f6afb97a03d08c25524e7"
+)
diff --git a/tools/build_rules/go/tools/BUILD b/tools/build_rules/go/tools/BUILD
new file mode 100644
index 0000000000..1bf9bfe159
--- /dev/null
+++ b/tools/build_rules/go/tools/BUILD
@@ -0,0 +1,9 @@
+package(default_visibility = ["//visibility:public"])
+
+load("/tools/build_rules/go/def", "go_binary")
+
+# This binary is used implicitly by go_test().
+go_binary(
+ name = "generate_test_main",
+ srcs = ["generate_test_main.go"],
+)
diff --git a/tools/build_rules/go/tools/generate_test_main.go b/tools/build_rules/go/tools/generate_test_main.go
new file mode 100644
index 0000000000..6316802dde
--- /dev/null
+++ b/tools/build_rules/go/tools/generate_test_main.go
@@ -0,0 +1,91 @@
+// Bare bones Go testing support for Bazel.
+
+package main
+
+import (
+ "flag"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "log"
+ "os"
+ "strings"
+ "text/template"
+)
+
+// Cases holds template data.
+type Cases struct {
+ Package string
+ Names []string
+}
+
+func main() {
+ pkg := flag.String("package", "", "package from which to import test methods.")
+ out := flag.String("output", "", "output file to write. Defaults to stdout.")
+ flag.Parse()
+
+ if *pkg == "" {
+ log.Fatal("must set --package.")
+ }
+
+ outFile := os.Stdout
+ if *out != "" {
+ var err error
+ outFile, err = os.Create(*out)
+ if err != nil {
+ log.Fatalf("os.Create(%q): %v", *out, err)
+ }
+ defer outFile.Close()
+ }
+
+ cases := Cases{
+ Package: *pkg,
+ }
+ testFileSet := token.NewFileSet()
+ for _, f := range flag.Args() {
+ parse, err := parser.ParseFile(testFileSet, f, nil, parser.ParseComments)
+ if err != nil {
+ log.Fatalf("ParseFile(%q): %v", f, err)
+ }
+
+ for _, d := range parse.Decls {
+ fn, ok := d.(*ast.FuncDecl)
+ if !ok {
+ continue
+ }
+ if fn.Recv != nil {
+ continue
+ }
+ if !strings.HasPrefix(fn.Name.Name, "Test") {
+ continue
+ }
+ cases.Names = append(cases.Names, fn.Name.Name)
+ }
+ }
+
+ tpl := template.Must(template.New("source").Parse(`
+package main
+import (
+ "testing"
+
+ undertest "{{.Package}}"
+)
+
+func everything(pat, str string) (bool, error) {
+ return true, nil
+}
+
+var tests = []testing.InternalTest{
+{{range .Names}}
+ {"{{.}}", undertest.{{.}} },
+{{end}}
+}
+
+func main() {
+ testing.Main(everything, tests, nil, nil)
+}
+`))
+ if err := tpl.Execute(outFile, &cases); err != nil {
+ log.Fatalf("template.Execute(%v): %v", cases, err)
+ }
+}