diff options
-rw-r--r-- | base_workspace/examples/go/README.md | 22 | ||||
-rw-r--r-- | base_workspace/examples/go/lib1/BUILD | 2 | ||||
-rw-r--r-- | tools/build_rules/go_rules.bzl | 239 |
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", - }) +}) |