Write rules in terms of a toolchain object (#472)

* Write rules in terms of a toolchain object

This modifies all the rules to access the tools and files of a go toolchain
through a single struct, in preparation for that struct being an automatically
selected toolchain.
Accessing this struct is hidden behind a get_toolchain function for now, to
allow it's behaviour to be easily modified.

* get_go_toolchain -> _get_go_toolchain
diff --git a/go/def.bzl b/go/def.bzl
index 5c32844..2d8213d 100644
--- a/go/def.bzl
+++ b/go/def.bzl
@@ -94,7 +94,8 @@
   }
   env = {}
   if hasattr(ctx.file, "go_tool"):
-    env["GOROOT"] = ctx.file.go_tool.dirname + "/.."
+    go_toolchain = _get_go_toolchain(ctx)
+    env["GOROOT"] = go_toolchain.root
   env.update(bazel_to_go_toolchain.get(ctx.fragments.cpp.cpu, default_toolchain))
   return env
 
@@ -127,19 +128,20 @@
     hdrs: list of .h files that may be included
     out_obj: the artifact (configured target?) that should be produced
   """
+  go_toolchain = _get_go_toolchain(ctx)
   params = {
-      "go_tool": ctx.file.go_tool.path,
-      "includes": [f.dirname for f in hdrs] + [ctx.file.go_include.path],
+      "go_tool": go_toolchain.go.path,
+      "includes": [f.dirname for f in hdrs] + [go_toolchain.include.path],
       "source": source.path,
       "out": out_obj.path,
   }
 
-  inputs = hdrs + ctx.files.toolchain + [source]
+  inputs = hdrs + go_toolchain.all_files + [source]
   ctx.action(
       inputs = inputs,
       outputs = [out_obj],
       mnemonic = "GoAsmCompile",
-      executable = ctx.executable._asm,
+      executable = go_toolchain.asm,
       arguments = [json_marshal(params)],
   )
 
@@ -180,19 +182,20 @@
     out_object: the object file that should be produced
     gc_goopts: additional flags to pass to the compiler.
   """
+  go_toolchain = _get_go_toolchain(ctx)
   if ctx.coverage_instrumented():
     sources = _emit_go_cover_action(ctx, sources)
 
   # Compile filtered files.
   args = [
       "-cgo",
-      ctx.file.go_tool.path,
+      go_toolchain.go.path,
       "tool", "compile",
       "-o", out_object.path,
       "-trimpath", "-abs-.",
       "-I", "-abs-.",
   ]
-  inputs = depset(sources + ctx.files.toolchain)
+  inputs = depset(sources + go_toolchain.all_files)
   for dep in deps:
     inputs += dep.transitive_go_libraries
   for path in libpaths:
@@ -202,7 +205,7 @@
       inputs = list(inputs),
       outputs = [out_object],
       mnemonic = "GoCompile",
-      executable = ctx.executable._filter_exec,
+      executable = go_toolchain.filter_exec,
       arguments = args,
       env = go_environment_vars(ctx),
   )
@@ -217,11 +220,12 @@
     out_lib: the archive that should be produced
     objects: an iterable of object files to be added to the output archive file.
   """
+  go_toolchain = _get_go_toolchain(ctx)
   ctx.action(
-      inputs = objects + ctx.files.toolchain,
+      inputs = objects + go_toolchain.all_files,
       outputs = [out_lib],
       mnemonic = "GoPack",
-      executable = ctx.file.go_tool,
+      executable = go_toolchain.go,
       arguments = ["tool", "pack", "c", out_lib.path] + [a.path for a in objects],
       env = go_environment_vars(ctx),
   )
@@ -236,6 +240,7 @@
   Returns:
     A list of Go source code files which might be coverage instrumented.
   """
+  go_toolchain = _get_go_toolchain(ctx)
   outputs = []
   # TODO(linuxerwang): make the mode configurable.
   count = 0
@@ -249,7 +254,7 @@
     out = ctx.new_file(src, src.basename[:-3] + '_' + cover_var + '.cover.go')
     outputs += [out]
     ctx.action(
-        inputs = [src] + ctx.files.toolchain,
+        inputs = [src] + go_toolchain.all_files,
         outputs = [out],
         mnemonic = "GoCover",
         executable = ctx.file.go_tool,
@@ -262,7 +267,7 @@
 
 def go_library_impl(ctx):
   """Implements the go_library() rule."""
-
+  go_toolchain = _get_go_toolchain(ctx)
   sources = set(ctx.files.srcs)
   go_srcs = set([s for s in sources if s.basename.endswith('.go')])
   asm_srcs = [s for s in sources if s.basename.endswith('.s') or s.basename.endswith('.S')]
@@ -409,6 +414,7 @@
 def _emit_go_link_action(ctx, transitive_go_library_paths, transitive_go_libraries, cgo_deps, libs,
                          executable, gc_linkopts):
   """Sets up a symlink tree to libraries to link together."""
+  go_toolchain = _get_go_toolchain(ctx)
   config_strip = len(ctx.configuration.bin_dir.path) + 1
   pkg_depth = executable.dirname[config_strip:].count('/') + 1
 
@@ -423,7 +429,7 @@
   gc_linkopts, extldflags = _extract_extldflags(gc_linkopts, extldflags)
 
   link_cmd = [
-      ctx.file.go_tool.path,
+      go_toolchain.go.path,
       "tool", "link",
       "-L", "."
   ]
@@ -472,7 +478,7 @@
 
   ctx.action(
       inputs = [f] + (list(transitive_go_libraries) + [lib] + list(cgo_deps) +
-                ctx.files.toolchain + ctx.files._crosstool) + stamp_inputs,
+                go_toolchain.all_files + ctx.files._crosstool) + stamp_inputs,
       outputs = [executable],
       command = f.path,
       mnemonic = "GoLink",
@@ -503,6 +509,7 @@
   It emits an action to run the test generator, and then compiles the
   test into a binary."""
 
+  go_toolchain = _get_go_toolchain(ctx)
   lib_result = go_library_impl(ctx)
   main_go = ctx.new_file(ctx.label.name + "_main_test.go")
   main_object = ctx.new_file(ctx.label.name + "_main_test.o")
@@ -518,9 +525,9 @@
       '    FILTERED_TEST_FILES+=("$line")',
       '  fi',
       'done < <(\'%s\' -cgo "${UNFILTERED_TEST_FILES[@]}")' %
-          ctx.executable._filter_tags.path,
+          go_toolchain.filter_tags.path,
       ' '.join([
-          "'%s'" % ctx.executable.test_generator.path,
+          "'%s'" % go_toolchain.test_generator.path,
           '--package',
           go_import,
           '--output',
@@ -530,8 +537,8 @@
   ]
   f = _emit_generate_params_action(
       cmds, ctx, ctx.label.name + ".GoTestGenTest.params")
-  inputs = (list(lib_result.go_sources) + list(ctx.files.toolchain) +
-            [f, ctx.executable._filter_tags, ctx.executable.test_generator])
+  inputs = (list(lib_result.go_sources) + list(go_toolchain.all_files) +
+            [f, go_toolchain.filter_tags, go_toolchain.test_generator])
   ctx.action(
       inputs = inputs,
       outputs = [main_go],
@@ -577,6 +584,7 @@
         default = Label("//go/toolchain:go_tool"),
         single_file = True,
         allow_files = True,
+        executable = True,
         cfg = "host",
     ),
     "go_prefix": attr.label(
@@ -600,10 +608,7 @@
         cfg = "host",
     ),
     "go_root": attr.label(
-        providers = ["go_root"],
-        default = Label(
-            "//go/toolchain:go_root",
-        ),
+        default = Label("//go/toolchain:go_root"),
         allow_files = False,
         cfg = "host",
     ),
@@ -625,8 +630,33 @@
         executable = True,
         single_file = True,
     ),
+    "_test_generator": attr.label(
+        executable = True,
+        default = Label("//go/tools:generate_test_main"),
+        cfg = "host",
+    ),
+    "_extract_package": attr.label(
+        default = Label("//go/tools/extract_package"),
+        executable = True,
+        cfg = "host",
+    ),
 }
 
+
+def _get_go_toolchain(ctx):
+    return struct(
+        go = ctx.executable.go_tool,
+        all_files = ctx.files.toolchain,
+        src = ctx.files.go_src,
+        include = ctx.file.go_include,
+        root = ctx.attr.go_root.path,
+        filter_tags = ctx.executable._filter_tags,
+        filter_exec = ctx.executable._filter_exec,
+        asm = ctx.executable._asm,
+        test_generator = ctx.executable._test_generator,
+        extract_package = ctx.executable._extract_package,
+    )
+
 go_library_attrs = go_env_attrs + {
     "data": attr.label_list(
         allow_files = True,
@@ -687,15 +717,7 @@
 
 go_test = rule(
     go_test_impl,
-    attrs = go_library_attrs + _crosstool_attrs + go_link_attrs + {
-        "test_generator": attr.label(
-            executable = True,
-            default = Label(
-                "//go/tools:generate_test_main",
-            ),
-            cfg = "host",
-        ),
-    },
+    attrs = go_library_attrs + _crosstool_attrs + go_link_attrs,
     executable = True,
     fragments = ["cpp"],
     test = True,
@@ -716,6 +738,7 @@
   return '${execroot}/' + path
 
 def _cgo_filter_srcs_impl(ctx):
+  go_toolchain = _get_go_toolchain(ctx)
   srcs = ctx.files.srcs
   dsts = []
   cmds = []
@@ -725,7 +748,7 @@
     dst = ctx.new_file(src, dst_basename)
     cmds += [
         "if '%s' -cgo -quiet '%s'; then" %
-            (ctx.executable._filter_tags.path, src.path),
+            (go_toolchain.filter_tags.path, src.path),
         "  cp '%s' '%s'" % (src.path, dst.path),
         "else",
         "  echo -n >'%s'" % dst.path,
@@ -739,7 +762,7 @@
     script_name = ctx.label.package + "/" + ctx.label.name + ".CGoFilterSrcs.params"
   f = _emit_generate_params_action(cmds, ctx, script_name)
   ctx.action(
-      inputs = [f, ctx.executable._filter_tags] + srcs,
+      inputs = [f, go_toolchain.filter_tags] + srcs,
       outputs = dsts,
       command = f.path,
       mnemonic = "CgoFilterSrcs",
@@ -750,21 +773,16 @@
 
 _cgo_filter_srcs = rule(
     implementation = _cgo_filter_srcs_impl,
-    attrs = {
+    attrs = go_env_attrs + {
         "srcs": attr.label_list(
             allow_files = cgo_filetype,
         ),
-        "_filter_tags": attr.label(
-            default = Label("//go/tools/filter_tags"),
-            cfg = "host",
-            executable = True,
-            single_file = True,
-        ),
     },
     fragments = ["cpp"],
 )
 
 def _cgo_codegen_impl(ctx):
+  go_toolchain = _get_go_toolchain(ctx)
   go_srcs = ctx.files.srcs
   srcs = go_srcs + ctx.files.c_hdrs
   linkopts = ctx.attr.linkopts
@@ -807,7 +825,7 @@
       'filtered_go_files=()',
       'for file in "${unfiltered_go_files[@]}"; do',
       '  stem=$(basename "$file" .go)',
-      '  if %s -cgo -quiet "$file"; then' % ctx.executable._filter_tags.path,
+      '  if %s -cgo -quiet "$file"; then' % go_toolchain.filter_tags.path,
       '    filtered_go_files+=("$file")',
       '  else',
       '    grep --max-count 1 "^package " "$file" >"$objdir/$stem.go"',
@@ -832,8 +850,8 @@
 
   f = _emit_generate_params_action(cmds, ctx, out_dir + ".CGoCodeGenFile.params")
 
-  inputs = (srcs + ctx.files.toolchain + ctx.files._crosstool +
-            [f, ctx.executable._filter_tags])
+  inputs = (srcs + go_toolchain.all_files + ctx.files._crosstool +
+            [f, go_toolchain.filter_tags])
   ctx.action(
       inputs = inputs,
       outputs = ctx.outputs.outs,
@@ -941,17 +959,18 @@
   return outs
 
 def _cgo_import_impl(ctx):
+  go_toolchain = _get_go_toolchain(ctx)
   cmds = [
-      (ctx.file.go_tool.path + " tool cgo" +
+      (go_toolchain.go.path + " tool cgo" +
        " -dynout " + ctx.outputs.out.path +
        " -dynimport " + ctx.file.cgo_o.path +
-       " -dynpackage $(%s %s)"  % (ctx.executable._extract_package.path,
+       " -dynpackage $(%s %s)"  % (go_toolchain.extract_package.path,
                                    ctx.file.sample_go_src.path)),
   ]
   f = _emit_generate_params_action(cmds, ctx, ctx.outputs.out.path + ".CGoImportGenFile.params")
   ctx.action(
-      inputs = (ctx.files.toolchain +
-                [f, ctx.file.go_tool, ctx.executable._extract_package,
+      inputs = (go_toolchain.all_files +
+                [f, go_toolchain.go, go_toolchain.extract_package,
                  ctx.file.cgo_o, ctx.file.sample_go_src]),
       outputs = [ctx.outputs.out],
       command = f.path,
@@ -976,11 +995,6 @@
         "out": attr.output(
             mandatory = True,
         ),
-        "_extract_package": attr.label(
-            default = Label("//go/tools/extract_package"),
-            executable = True,
-            cfg = "host",
-        ),
     },
     fragments = ["cpp"],
 )
diff --git a/go/private/go_root.bzl b/go/private/go_root.bzl
index 943f80f..434623c 100644
--- a/go/private/go_root.bzl
+++ b/go/private/go_root.bzl
@@ -14,12 +14,12 @@
 
 def _go_root_impl(ctx):
   """go_root_impl propogates a GOROOT path string."""
-  return struct(go_root = ctx.attr.path)
+  return struct(path = ctx.attr.path)
 
 go_root = rule(
   _go_root_impl,
   attrs = {
-    "path": attr.string(),
+    "path": attr.string(mandatory = True),
   },
 )
 """Captures the goroot value for use as a label dependency.
diff --git a/go/tools/BUILD b/go/tools/BUILD
index 3a351d5..5099d5e 100644
--- a/go/tools/BUILD
+++ b/go/tools/BUILD
@@ -1,9 +1,9 @@
 package(default_visibility = ["//visibility:public"])
 
-load("//go:def.bzl", "go_binary")
+load("//go/private:go_tool_binary.bzl", "go_tool_binary")
 
 # This binary is used implicitly by go_test().
-go_binary(
+go_tool_binary(
     name = "generate_test_main",
     srcs = ["generate_test_main.go"],
 )