aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Han-Wen Nienhuys <hanwen@google.com>2015-03-10 17:28:44 +0000
committerGravatar Han-Wen Nienhuys <hanwen@google.com>2015-03-11 18:17:31 +0000
commita07baee45d794bf71d7e3c1ce26d031e1d9df963 (patch)
treead40f46dc6236f431dd9a3ba4bfe722ad0b31509
parent7ecb764117e5eb24c74354cad778409f2b3a8a0a (diff)
Productionize Bazel's skylark go rules.
-- MOS_MIGRATED_REVID=88232883
-rw-r--r--base_workspace/examples/go/README.md22
-rw-r--r--base_workspace/examples/go/lib1/BUILD2
-rw-r--r--tools/build_rules/go_rules.bzl239
3 files changed, 192 insertions, 71 deletions
diff --git a/base_workspace/examples/go/README.md b/base_workspace/examples/go/README.md
index 73d73de70b..a0374f7250 100644
--- a/base_workspace/examples/go/README.md
+++ b/base_workspace/examples/go/README.md
@@ -74,6 +74,28 @@ so you use it as follows
}
+Writing tests
+-------------
+
+For tests, you should create a separate target,
+
+ go_test(
+ name = "q_test",
+ srcs = [ "f_test.go" ],
+ deps = [ ":q" ])
+
+if the test code is in the same package as the library under test
+(e.g., because you are testing private parts of the library), you should
+use the `library` attribute,
+
+ go_test(
+ name = "q_test",
+ srcs = [ "f_test.go" ],
+ library = ":q" )
+
+This causes the test and the library under test to be compiled
+together.
+
FAQ
---
diff --git a/base_workspace/examples/go/lib1/BUILD b/base_workspace/examples/go/lib1/BUILD
index 79d360e4fb..fb400772ef 100644
--- a/base_workspace/examples/go/lib1/BUILD
+++ b/base_workspace/examples/go/lib1/BUILD
@@ -13,9 +13,9 @@ go_library(
go_test(
name = "lib1_test",
srcs = [
- "lib1.go",
"lib1_test.go",
],
+ library = ":lib1",
)
go_test(
diff --git a/tools/build_rules/go_rules.bzl b/tools/build_rules/go_rules.bzl
index 8e6d561d56..3a50faacc8 100644
--- a/tools/build_rules/go_rules.bzl
+++ b/tools/build_rules/go_rules.bzl
@@ -16,144 +16,237 @@
Several issues:
-- Dependencies are not enforced; a symlink tree might help here too.
-
-- Hardcoded to 6g from the GC suite. We should be able to support GCC
- and derive 6g from the CPU (from which configuration?)
+- Currently hardcoded to 6g from the GC suite. We should be able to
+ extract the CPU type from ctx.configuration instead.
- It would be nice to be able to create a full-fledged Go
configuration in Skylark.
-- It would be nice to support zero-configuration rules, similar to
- vanilla "go", with one package per directory. This would requiere
- macro support though, to use glob()
-
- * For "a/b/c.go", the go tool creates library "a/b.a" with import path
- "a/b". We can probably simulate this with symlink trees.
-
-- does not support checked in compilers.
+- Almost supports checked in compilers (should use a filegroup under
+ tools/go instead of symlink).
- No C++ interop.
- deps must be populated by hand.
-- go_test must have both test and non-test files in srcs.
+- no test sharding or test XML.
"""
go_filetype = FileType([".go"])
-go_lib_filetype = FileType([".a"])
-
-def go_compile_command(ctx, sources, out_lib):
+# 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 item in artifact_dict.items():
+ old_path = item[0]
+ new_path = item[1]
+ 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
+ prefix = ""
+ if ctx.workspace_name:
+ ctx.workspace_name + "/"
+
+ tree_layout = {}
+ inputs = []
+ 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]
+
+ inputs += list(sources)
+ for s in sources:
+ tree_layout[s.path] = s.path
+
+ cmds = symlink_tree_commands(out_dir, tree_layout)
args = [
- ctx.files.go_root[0].path + "/bin/go",
-
+ "cd ", out_dir, "&&",
+ ('../' * out_depth) + ctx.files.go_root[0].path + "/bin/go",
"tool", "6g",
- "-o", out_lib.path, "-pack",
+ "-o", ('../' * out_depth) + out_lib.path, "-pack",
# Import path.
- "-I", ctx.configuration.bin_dir.path]
+ "-I", "."]
# Set -p to the import path of the library, ie.
# (ctx.label.package + "/" ctx.label.name) for now.
- return ' '.join(args + cmd_helper.template(sources, "%{path}"))
+ cmds += [ "export GOROOT=$(pwd)/" + ctx.files.go_root[0].path,
+ ' '.join(args + cmd_helper.template(sources, "%{path}"))]
+
+ ctx.action(
+ inputs = inputs,
+ outputs = [out_lib],
+ mnemonic = "GoCompile",
+ command = " && ".join(cmds))
+
def go_library_impl(ctx):
- sources = ctx.files.srcs
+ """Implements the go_library() rule."""
+
+ sources = set(ctx.files.srcs)
+ deps = ctx.targets.deps
+ if ctx.targets.library:
+ sources += ctx.target.library.go_sources
+ deps += ctx.target.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)
- ctx.action(
- inputs = sources + ctx.files.deps,
- outputs = [out_lib],
- mnemonic = "GoCompile",
- env = {
- "GOROOT": ctx.files.go_root[0].path,
- },
- command = go_compile_command(ctx, set(sources), out_lib))
+ transitive_libs = set([out_lib])
+ for dep in ctx.targets.deps:
+ transitive_libs += dep.transitive_go_library_object
- out_nset = set([out_lib])
return struct(
- files = out_nset,
- go_library_object = out_nset)
-
-
-def go_link_action(ctx, lib, executable):
- cmd = ' '.join([
- ctx.files.go_root[0].path + "/bin/go",
- "tool", "6l",
- # Link search path.
- "-L", ctx.configuration.bin_dir.path,
- "-o", executable.path,
- lib.path])
+ 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 = ""
+ if ctx.workspace_name:
+ ctx.workspace_name + "/"
+
+ 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.files.go_root[0].path,
+ "cd " + out_dir,
+ ' '.join([
+ ('../' * out_depth) + ctx.files.go_root[0].path + "/bin/go",
+ "tool", "6l", "-L", ".",
+ "-o", prefix + executable.path[config_strip:],
+ prefix + lib.path[config_strip:]])]
+
ctx.action(
- inputs = [lib],
+ inputs = list(transitive_libs) + [lib],
outputs = [executable],
- command = cmd,
- env = {
- "GOROOT": ctx.files.go_root[0].path,
- },
+ command = ' && '.join(cmds),
mnemonic = "GoLink")
def go_binary_impl(ctx):
+ """go_binary_impl emits the link action for a go executable."""
lib_result = go_library_impl(ctx)
executable = ctx.outputs.executable
lib_out = ctx.outputs.lib
- go_link_action(ctx, lib_out, executable)
+ 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 compile the
+ test."""
+
lib_result = go_library_impl(ctx)
main_go = ctx.outputs.main_go
+ prefix = ""
+ if ctx.workspace_name:
+ prefix = ctx.workspace_name + "/"
- go_import = ctx.label.package + "/" + ctx.label.name
+ go_import = prefix + ctx.label.package + "/" + ctx.label.name
- # Would be nice to use transitive info provider to get at sources of
- # a dependent library.
- sources = ctx.files.srcs
args = (["--package", go_import, "--output", ctx.outputs.main_go.path] +
- cmd_helper.template(set(sources), "%{path}"))
-
+ cmd_helper.template(lib_result.go_sources, "%{path}"))
ctx.action(
- inputs = sources,
+ inputs = list(lib_result.go_sources),
executable = ctx.executable.test_generator,
outputs = [main_go],
mnemonic = "GoTestGenTest",
arguments = args)
- ctx.action(
- inputs = [main_go, ctx.outputs.lib],
- outputs = [ctx.outputs.main_lib],
- command = go_compile_command(ctx, set([main_go]), ctx.outputs.main_lib),
- env = {
- "GOROOT": ctx.files.go_root[0].path,
- },
- mnemonic = "GoCompileTest")
+ emit_go_compile_action(
+ ctx, set([main_go]), ctx.targets.deps + [lib_result], ctx.outputs.main_lib)
- go_link_action(ctx, ctx.outputs.main_lib, ctx.outputs.executable)
+ 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=["go_library_object"]),
+ providers=[
+ "direct_deps",
+ "go_library_object",
+ "transitive_go_library_object",
+ ]),
"go_root": attr.label(
default=Label("//tools/go:go_root"),
allow_files=True,
cfg=HOST_CFG),
+ "library": attr.label(
+ providers=["go_sources"]),
}
go_library_outputs = {
- "lib": "%{name}.a",
+ "lib": "%{name}.a",
}
go_library = rule(
@@ -164,7 +257,9 @@ go_library = rule(
go_binary = rule(
go_binary_impl,
executable = True,
- attrs = go_library_attrs,
+ attrs = go_library_attrs + {
+ "stamp": attr.bool(default=False),
+ },
outputs = go_library_outputs)
go_test = rule(
@@ -174,10 +269,14 @@ go_test = rule(
attrs = go_library_attrs + {
"test_generator": attr.label(
default=Label("//tools/go:generate_test_main"),
- cfg=HOST_CFG, flags=["EXECUTABLE"])
+ cfg=HOST_CFG, flags=["EXECUTABLE"]),
+ # TODO(bazel-team): implement support for args and defines_main.
+ "args": attr.string_list(),
+ "defines_main": attr.bool(default=False),
+ "stamp": attr.bool(default=False),
},
outputs = {
"lib" : "%{name}.a",
"main_lib": "%{name}_main_test.a",
"main_go": "%{name}_main_test.go",
- })
+})