aboutsummaryrefslogtreecommitdiff
path: root/go/private/rules/test.bzl
diff options
context:
space:
mode:
Diffstat (limited to 'go/private/rules/test.bzl')
-rw-r--r--go/private/rules/test.bzl707
1 files changed, 707 insertions, 0 deletions
diff --git a/go/private/rules/test.bzl b/go/private/rules/test.bzl
new file mode 100644
index 00000000..413a19da
--- /dev/null
+++ b/go/private/rules/test.bzl
@@ -0,0 +1,707 @@
+# 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.
+
+load(
+ "//go/private:context.bzl",
+ "go_context",
+)
+load(
+ "//go/private:common.bzl",
+ "as_list",
+ "asm_exts",
+ "cgo_exts",
+ "go_exts",
+ "split_srcs",
+)
+load(
+ "//go/private:go_toolchain.bzl",
+ "GO_TOOLCHAIN",
+)
+load(
+ "//go/private/rules:binary.bzl",
+ "gc_linkopts",
+)
+load(
+ "//go/private:providers.bzl",
+ "GoArchive",
+ "GoLibrary",
+ "GoSource",
+ "INFERRED_PATH",
+ "get_archive",
+)
+load(
+ "//go/private/rules:transition.bzl",
+ "go_transition",
+)
+load(
+ "//go/private:mode.bzl",
+ "LINKMODE_NORMAL",
+)
+load(
+ "@bazel_skylib//lib:structs.bzl",
+ "structs",
+)
+
+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."""
+
+ go = go_context(ctx)
+
+ # Compile the library to test with internal white box tests
+ internal_library = go.new_library(go, testfilter = "exclude")
+ internal_source = go.library_to_source(go, ctx.attr, internal_library, ctx.coverage_instrumented())
+ internal_archive = go.archive(go, internal_source)
+ go_srcs = split_srcs(internal_source.srcs).go
+
+ # Compile the library with the external black box tests
+ external_library = go.new_library(
+ go,
+ name = internal_library.name + "_test",
+ importpath = internal_library.importpath + "_test",
+ testfilter = "only",
+ )
+ external_source = go.library_to_source(go, struct(
+ srcs = [struct(files = go_srcs)],
+ embedsrcs = [struct(files = internal_source.embedsrcs)],
+ deps = internal_archive.direct + [internal_archive],
+ x_defs = ctx.attr.x_defs,
+ ), external_library, ctx.coverage_instrumented())
+ external_source, internal_archive = _recompile_external_deps(go, external_source, internal_archive, [t.label for t in ctx.attr.embed])
+ external_archive = go.archive(go, external_source)
+
+ # now generate the main function
+ repo_relative_rundir = ctx.attr.rundir or ctx.label.package or "."
+ if ctx.label.workspace_name:
+ # The test is contained in an external repository (Label.workspace_name is always the empty
+ # string for the main repository, which is the canonical repository name of this repo).
+ # The test runner cd's into the directory corresponding to the main repository, so walk up
+ # and then down.
+ run_dir = "../" + ctx.label.workspace_name + "/" + repo_relative_rundir
+ else:
+ run_dir = repo_relative_rundir
+
+ main_go = go.declare_file(go, path = "testmain.go")
+ arguments = go.builder_args(go, "gentestmain")
+ arguments.add("-output", main_go)
+ if go.coverage_enabled:
+ if go.mode.race:
+ arguments.add("-cover_mode", "atomic")
+ else:
+ arguments.add("-cover_mode", "set")
+ arguments.add("-cover_format", go.cover_format)
+ arguments.add(
+ # the l is the alias for the package under test, the l_test must be the
+ # same with the test suffix
+ "-import",
+ "l=" + internal_source.library.importpath,
+ )
+ arguments.add(
+ "-import",
+ "l_test=" + external_source.library.importpath,
+ )
+ arguments.add("-pkgname", internal_source.library.importpath)
+ arguments.add_all(go_srcs, before_each = "-src", format_each = "l=%s")
+ ctx.actions.run(
+ inputs = go_srcs,
+ outputs = [main_go],
+ mnemonic = "GoTestGenTest",
+ executable = go.toolchain._builder,
+ arguments = [arguments],
+ )
+
+ test_gc_linkopts = gc_linkopts(ctx)
+ if not go.mode.debug:
+ # Disable symbol table and DWARF generation for test binaries.
+ test_gc_linkopts.extend(["-s", "-w"])
+
+ # Link in the run_dir global for bzltestutil
+ test_gc_linkopts.extend(["-X", "github.com/bazelbuild/rules_go/go/tools/bzltestutil.RunDir=" + run_dir])
+
+ # Now compile the test binary itself
+ test_library = GoLibrary(
+ name = go.label.name + "~testmain",
+ label = go.label,
+ importpath = "testmain",
+ importmap = "testmain",
+ importpath_aliases = (),
+ pathtype = INFERRED_PATH,
+ is_main = True,
+ resolve = None,
+ )
+ test_deps = external_archive.direct + [external_archive] + ctx.attr._testmain_additional_deps
+ if ctx.configuration.coverage_enabled:
+ test_deps.append(go.coverdata)
+ test_source = go.library_to_source(go, struct(
+ srcs = [struct(files = [main_go])],
+ deps = test_deps,
+ ), test_library, False)
+ test_archive, executable, runfiles = go.binary(
+ go,
+ name = ctx.label.name,
+ source = test_source,
+ test_archives = [internal_archive.data],
+ gc_linkopts = test_gc_linkopts,
+ version_file = ctx.version_file,
+ info_file = ctx.info_file,
+ )
+
+ env = {}
+ for k, v in ctx.attr.env.items():
+ env[k] = ctx.expand_location(v, ctx.attr.data)
+
+ run_environment_info = RunEnvironmentInfo(env, ctx.attr.env_inherit)
+
+ # Bazel only looks for coverage data if the test target has an
+ # InstrumentedFilesProvider. If the provider is found and at least one
+ # source file is present, Bazel will set the COVERAGE_OUTPUT_FILE
+ # environment variable during tests and will save that file to the build
+ # events + test outputs.
+ return [
+ test_archive,
+ DefaultInfo(
+ files = depset([executable]),
+ runfiles = runfiles,
+ executable = executable,
+ ),
+ OutputGroupInfo(
+ compilation_outputs = [internal_archive.data.file],
+ ),
+ coverage_common.instrumented_files_info(
+ ctx,
+ source_attributes = ["srcs"],
+ dependency_attributes = ["data", "deps", "embed", "embedsrcs"],
+ extensions = ["go"],
+ ),
+ run_environment_info,
+ ]
+
+_go_test_kwargs = {
+ "implementation": _go_test_impl,
+ "attrs": {
+ "data": attr.label_list(
+ allow_files = True,
+ doc = """List of files needed by this rule at run-time. This may include data files
+ needed or other programs that may be executed. The [bazel] package may be
+ used to locate run files; they may appear in different places depending on the
+ operating system and environment. See [data dependencies] for more
+ information on data files.
+ """,
+ ),
+ "srcs": attr.label_list(
+ allow_files = go_exts + asm_exts + cgo_exts,
+ doc = """The list of Go source files that are compiled to create the package.
+ Only `.go` and `.s` files are permitted, unless the `cgo`
+ attribute is set, in which case,
+ `.c .cc .cpp .cxx .h .hh .hpp .hxx .inc .m .mm`
+ files are also permitted. Files may be filtered at build time
+ using Go [build constraints].
+ """,
+ ),
+ "deps": attr.label_list(
+ providers = [GoLibrary],
+ doc = """List of Go libraries this test imports directly.
+ These may be go_library rules or compatible rules with the [GoLibrary] provider.
+ """,
+ cfg = go_transition,
+ ),
+ "embed": attr.label_list(
+ providers = [GoLibrary],
+ doc = """List of Go libraries whose sources should be compiled together with this
+ package's sources. Labels listed here must name `go_library`,
+ `go_proto_library`, or other compatible targets with the [GoLibrary] and
+ [GoSource] providers. Embedded libraries must have the same `importpath` as
+ the embedding library. At most one embedded library may have `cgo = True`,
+ and the embedding library may not also have `cgo = True`. See [Embedding]
+ for more information.
+ """,
+ cfg = go_transition,
+ ),
+ "embedsrcs": attr.label_list(
+ allow_files = True,
+ doc = """The list of files that may be embedded into the compiled package using
+ `//go:embed` directives. All files must be in the same logical directory
+ or a subdirectory as source files. All source files containing `//go:embed`
+ directives must be in the same logical directory. It's okay to mix static and
+ generated source files and static and generated embeddable files.
+ """,
+ ),
+ "env": attr.string_dict(
+ doc = """Environment variables to set for the test execution.
+ The values (but not keys) are subject to
+ [location expansion](https://docs.bazel.build/versions/main/skylark/macros.html) but not full
+ [make variable expansion](https://docs.bazel.build/versions/main/be/make-variables.html).
+ """,
+ ),
+ "env_inherit": attr.string_list(
+ doc = """Environment variables to inherit from the external environment.
+ """,
+ ),
+ "importpath": attr.string(
+ doc = """The import path of this test. Tests can't actually be imported, but this
+ may be used by [go_path] and other tools to report the location of source
+ files. This may be inferred from embedded libraries.
+ """,
+ ),
+ "gc_goopts": attr.string_list(
+ doc = """List of flags to add to the Go compilation command when using the gc compiler.
+ Subject to ["Make variable"] substitution and [Bourne shell tokenization].
+ """,
+ ),
+ "gc_linkopts": attr.string_list(
+ doc = """List of flags to add to the Go link command when using the gc compiler.
+ Subject to ["Make variable"] substitution and [Bourne shell tokenization].
+ """,
+ ),
+ "rundir": attr.string(
+ doc = """ A directory to cd to before the test is run.
+ This should be a path relative to the root directory of the
+ repository in which the test is defined, which can be the main or an
+ external repository.
+
+ The default behaviour is to change to the relative path
+ corresponding to the test's package, which replicates the normal
+ behaviour of `go test` so it is easy to write compatible tests.
+
+ Setting it to `.` makes the test behave the normal way for a bazel
+ test, except that the working directory is always that of the test's
+ repository, which is not necessarily the main repository.
+
+ Note: If runfile symlinks are disabled (such as on Windows by
+ default), the test will run in the working directory set by Bazel,
+ which is the subdirectory of the runfiles directory corresponding to
+ the main repository.
+ """,
+ ),
+ "x_defs": attr.string_dict(
+ doc = """Map of defines to add to the go link command.
+ See [Defines and stamping] for examples of how to use these.
+ """,
+ ),
+ "linkmode": attr.string(
+ default = LINKMODE_NORMAL,
+ doc = """Determines how the binary should be built and linked. This accepts some of
+ the same values as `go build -buildmode` and works the same way.
+ <br><br>
+ <ul>
+ <li>`normal`: Builds a normal executable with position-dependent code.</li>
+ <li>`pie`: Builds a position-independent executable.</li>
+ <li>`plugin`: Builds a shared library that can be loaded as a Go plugin. Only supported on platforms that support plugins.</li>
+ <li>`c-shared`: Builds a shared library that can be linked into a C program.</li>
+ <li>`c-archive`: Builds an archive that can be linked into a C program.</li>
+ </ul>
+ """,
+ ),
+ "cgo": attr.bool(
+ doc = """
+ If `True`, the package may contain [cgo] code, and `srcs` may contain
+ C, C++, Objective-C, and Objective-C++ files and non-Go assembly files.
+ When cgo is enabled, these files will be compiled with the C/C++ toolchain
+ and included in the package. Note that this attribute does not force cgo
+ to be enabled. Cgo is enabled for non-cross-compiling builds when a C/C++
+ toolchain is configured.
+ """,
+ ),
+ "cdeps": attr.label_list(
+ doc = """The list of other libraries that the c code depends on.
+ This can be anything that would be allowed in [cc_library deps]
+ Only valid if `cgo` = `True`.
+ """,
+ ),
+ "cppopts": attr.string_list(
+ doc = """List of flags to add to the C/C++ preprocessor command.
+ Subject to ["Make variable"] substitution and [Bourne shell tokenization].
+ Only valid if `cgo` = `True`.
+ """,
+ ),
+ "copts": attr.string_list(
+ doc = """List of flags to add to the C compilation command.
+ Subject to ["Make variable"] substitution and [Bourne shell tokenization].
+ Only valid if `cgo` = `True`.
+ """,
+ ),
+ "cxxopts": attr.string_list(
+ doc = """List of flags to add to the C++ compilation command.
+ Subject to ["Make variable"] substitution and [Bourne shell tokenization].
+ Only valid if `cgo` = `True`.
+ """,
+ ),
+ "clinkopts": attr.string_list(
+ doc = """List of flags to add to the C link command.
+ Subject to ["Make variable"] substitution and [Bourne shell tokenization].
+ Only valid if `cgo` = `True`.
+ """,
+ ),
+ "pure": attr.string(
+ default = "auto",
+ doc = """Controls whether cgo source code and dependencies are compiled and linked,
+ similar to setting `CGO_ENABLED`. May be one of `on`, `off`,
+ or `auto`. If `auto`, pure mode is enabled when no C/C++
+ toolchain is configured or when cross-compiling. It's usually better to
+ control this on the command line with
+ `--@io_bazel_rules_go//go/config:pure`. See [mode attributes], specifically
+ [pure].
+ """,
+ ),
+ "static": attr.string(
+ default = "auto",
+ doc = """Controls whether a binary is statically linked. May be one of `on`,
+ `off`, or `auto`. Not available on all platforms or in all
+ modes. It's usually better to control this on the command line with
+ `--@io_bazel_rules_go//go/config:static`. See [mode attributes],
+ specifically [static].
+ """,
+ ),
+ "race": attr.string(
+ default = "auto",
+ doc = """Controls whether code is instrumented for race detection. May be one of
+ `on`, `off`, or `auto`. Not available when cgo is
+ disabled. In most cases, it's better to control this on the command line with
+ `--@io_bazel_rules_go//go/config:race`. See [mode attributes], specifically
+ [race].
+ """,
+ ),
+ "msan": attr.string(
+ default = "auto",
+ doc = """Controls whether code is instrumented for memory sanitization. May be one of
+ `on`, `off`, or `auto`. Not available when cgo is
+ disabled. In most cases, it's better to control this on the command line with
+ `--@io_bazel_rules_go//go/config:msan`. See [mode attributes], specifically
+ [msan].
+ """,
+ ),
+ "gotags": attr.string_list(
+ doc = """Enables a list of build tags when evaluating [build constraints]. Useful for
+ conditional compilation.
+ """,
+ ),
+ "goos": attr.string(
+ default = "auto",
+ doc = """Forces a binary to be cross-compiled for a specific operating system. It's
+ usually better to control this on the command line with `--platforms`.
+
+ This disables cgo by default, since a cross-compiling C/C++ toolchain is
+ rarely available. To force cgo, set `pure` = `off`.
+
+ See [Cross compilation] for more information.
+ """,
+ ),
+ "goarch": attr.string(
+ default = "auto",
+ doc = """Forces a binary to be cross-compiled for a specific architecture. It's usually
+ better to control this on the command line with `--platforms`.
+
+ This disables cgo by default, since a cross-compiling C/C++ toolchain is
+ rarely available. To force cgo, set `pure` = `off`.
+
+ See [Cross compilation] for more information.
+ """,
+ ),
+ "_go_context_data": attr.label(default = "//:go_context_data", cfg = go_transition),
+ "_testmain_additional_deps": attr.label_list(
+ providers = [GoLibrary],
+ default = ["//go/tools/bzltestutil"],
+ cfg = go_transition,
+ ),
+ # Required for Bazel to collect coverage of instrumented C/C++ binaries
+ # executed by go_test.
+ # This is just a shell script and thus cheap enough to depend on
+ # unconditionally.
+ "_collect_cc_coverage": attr.label(
+ default = "@bazel_tools//tools/test:collect_cc_coverage",
+ cfg = "exec",
+ ),
+ # Required for Bazel to merge coverage reports for Go and other
+ # languages into a single report per test.
+ # Using configuration_field ensures that the tool is only built when
+ # run with bazel coverage, not with bazel test.
+ "_lcov_merger": attr.label(
+ default = configuration_field(fragment = "coverage", name = "output_generator"),
+ cfg = "exec",
+ ),
+ "_allowlist_function_transition": attr.label(
+ default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
+ ),
+ },
+ "executable": True,
+ "test": True,
+ "toolchains": [GO_TOOLCHAIN],
+ "doc": """This builds a set of tests that can be run with `bazel test`.<br><br>
+ To run all tests in the workspace, and print output on failure (the
+ equivalent of `go test ./...`), run<br>
+ ```
+ bazel test --test_output=errors //...
+ ```<br><br>
+ To run a Go benchmark test, run<br>
+ ```
+ bazel run //path/to:test -- -test.bench=.
+ ```<br><br>
+ You can run specific tests by passing the `--test_filter=pattern
+ <test_filter_>` argument to Bazel. You can pass arguments to tests by passing
+ `--test_arg=arg <test_arg_>` arguments to Bazel, and you can set environment
+ variables in the test environment by passing
+ `--test_env=VAR=value <test_env_>`. You can terminate test execution after the first
+ failure by passing the `--test_runner_fast_fast <test_runner_fail_fast_>` argument
+ to Bazel. This is equivalent to passing `--test_arg=-failfast <test_arg_>`.<br><br>
+ To write structured testlog information to Bazel's `XML_OUTPUT_FILE`, tests
+ ran with `bazel test` execute using a wrapper. This functionality can be
+ disabled by setting `GO_TEST_WRAP=0` in the test environment. Additionally,
+ the testbinary can be invoked with `-test.v` by setting
+ `GO_TEST_WRAP_TESTV=1` in the test environment; this will result in the
+ `XML_OUTPUT_FILE` containing more granular data.<br><br>
+ ***Note:*** To interoperate cleanly with old targets generated by [Gazelle], `name`
+ should be `go_default_test` for internal tests and
+ `go_default_xtest` for external tests. Gazelle now generates
+ the name based on the last component of the path. For example, a test
+ in `//foo/bar` is named `bar_test`, and uses internal and external
+ sources.
+ """,
+}
+
+go_test = rule(**_go_test_kwargs)
+
+def _recompile_external_deps(go, external_source, internal_archive, library_labels):
+ """Recompiles some archives in order to split internal and external tests.
+
+ go_test, like 'go test', splits tests into two separate archives: an
+ internal archive ('package foo') and an external archive
+ ('package foo_test'). The library under test is embedded into the internal
+ archive. The external archive may import it and may depend on symbols
+ defined in the internal test files.
+
+ To avoid conflicts, the library under test must not be linked into the test
+ binary, since the internal test archive embeds the same sources.
+ Libraries imported by the external test that transitively import the
+ library under test must be recompiled too, or the linker will complain that
+ export data they were compiled with doesn't match the export data they
+ are linked with.
+
+ This function identifies which archives may need to be recompiled, then
+ declares new output files and actions to recompile them. This is an
+ unfortunately an expensive process requiring O(V+E) time and space in the
+ size of the test's dependency graph for each test.
+
+ Args:
+ go: go object returned by go_context.
+ external_source: GoSource for the external archive.
+ internal_archive: GoArchive for the internal archive.
+ library_labels: labels for embedded libraries under test.
+
+ Returns:
+ external_soruce: recompiled GoSource for the external archive. If no
+ recompilation is needed, the original GoSource is returned.
+ internal_archive: recompiled GoArchive for the internal archive. If no
+ recompilation is needed, the original GoSource is returned.
+ """
+
+ # If no libraries are embedded in the internal archive, then nothing needs
+ # to be recompiled.
+ if not library_labels:
+ return external_source, internal_archive
+
+ # Build a map from labels to GoArchiveData.
+ # If none of the librares embedded in the internal archive are in the
+ # dependency graph, then nothing needs to be recompiled.
+ arc_data_list = depset(transitive = [get_archive(dep).transitive for dep in external_source.deps]).to_list()
+ label_to_arc_data = {a.label: a for a in arc_data_list}
+ if all([l not in label_to_arc_data for l in library_labels]):
+ return external_source, internal_archive
+
+ # Build a depth-first post-order list of dependencies starting with the
+ # external archive. Each archive appears after its dependencies and before
+ # its dependents.
+ #
+ # This is tricky because Starlark doesn't support recursion or while loops.
+ # We simulate a while loop by iterating over a list of 2N elements where
+ # N is the number of archives. Each archive is pushed onto the stack
+ # twice: once before its dependencies are pushed, and once after.
+
+ # dep_list is the post-order list of dependencies we're building.
+ dep_list = []
+
+ # stack is a stack of targets to process. We're done when it's empty.
+ stack = [get_archive(dep).data.label for dep in external_source.deps]
+
+ # deps_pushed tracks the status of each target.
+ # DEPS_UNPROCESSED means the target is on the stack, but its dependencies
+ # are not.
+ # Non-negative integers are the number of dependencies on the stack that
+ # still need to be processed.
+ # A target is on the stack if its status is DEPS_UNPROCESSED or 0.
+ DEPS_UNPROCESSED = -1
+ deps_pushed = {l: DEPS_UNPROCESSED for l in stack}
+
+ # dependents maps labels to lists of known dependents. When a target is
+ # processed, its dependents' deps_pushed count is deprecated.
+ dependents = {l: [] for l in stack}
+
+ # step is a list to iterate over to simulate a while loop. i tracks
+ # iterations.
+ step = [None] * (2 * len(arc_data_list))
+ i = 0
+ for _ in step:
+ if len(stack) == 0:
+ break
+ i += 1
+
+ label = stack.pop()
+ if deps_pushed[label] == 0:
+ # All deps have been added to dep_list. Append this target to the
+ # list. If a dependent is not waiting for anything else, push
+ # it back onto the stack.
+ dep_list.append(label)
+ for p in dependents.get(label, []):
+ deps_pushed[p] -= 1
+ if deps_pushed[p] == 0:
+ stack.append(p)
+ continue
+
+ # deps_pushed[label] == None, indicating we don't know whether this
+ # targets dependencies have been processed. Other targets processed
+ # earlier may depend on them.
+ deps_pushed[label] = 0
+ arc_data = label_to_arc_data[label]
+ for c in arc_data._dep_labels:
+ if c not in deps_pushed:
+ # Dependency not seen yet; push it.
+ stack.append(c)
+ deps_pushed[c] = None
+ deps_pushed[label] += 1
+ dependents[c] = [label]
+ elif deps_pushed[c] != 0:
+ # Dependency pushed, not processed; wait for it.
+ deps_pushed[label] += 1
+ dependents[c].append(label)
+ if deps_pushed[label] == 0:
+ # No dependencies to wait for; push self.
+ stack.append(label)
+ if i != len(step):
+ fail("assertion failed: iterated %d times instead of %d" % (i, len(step)))
+
+ # Determine which dependencies need to be recompiled because they depend
+ # on embedded libraries.
+ need_recompile = {}
+ for label in dep_list:
+ arc_data = label_to_arc_data[label]
+ need_recompile[label] = any([
+ dep in library_labels or need_recompile[dep]
+ for dep in arc_data._dep_labels
+ ])
+
+ # Recompile the internal archive without dependencies that need
+ # recompilation. This breaks a cycle which occurs because the deps list
+ # is shared between the internal and external archive. The internal archive
+ # can't import anything that imports itself.
+ internal_source = internal_archive.source
+
+ internal_deps = []
+
+ # Pass internal dependencies that need to be recompiled down to the builder to check if the internal archive
+ # tries to import any of the dependencies. If there is, that means that there is a dependency cycle.
+ need_recompile_deps = []
+ for dep in internal_source.deps:
+ dep_data = get_archive(dep).data
+ if not need_recompile[dep_data.label]:
+ internal_deps.append(dep)
+ else:
+ need_recompile_deps.append(dep_data.importpath)
+
+ x_defs = dict(internal_source.x_defs)
+ x_defs.update(internal_archive.x_defs)
+ attrs = structs.to_dict(internal_source)
+ attrs["deps"] = internal_deps
+ attrs["x_defs"] = x_defs
+ internal_source = GoSource(**attrs)
+ internal_archive = go.archive(go, internal_source, _recompile_suffix = ".recompileinternal", recompile_internal_deps = need_recompile_deps)
+
+ # Build a map from labels to possibly recompiled GoArchives.
+ label_to_archive = {}
+ i = 0
+ for label in dep_list:
+ i += 1
+ recompile_suffix = ".recompile%d" % i
+
+ # If this library is the internal archive, use the recompiled version.
+ if label == internal_archive.data.label:
+ label_to_archive[label] = internal_archive
+ continue
+
+ # If this is a library embedded into the internal test archive,
+ # use the internal test archive instead.
+ if label in library_labels:
+ label_to_archive[label] = internal_archive
+ continue
+
+ # Create a stub GoLibrary and GoSource from the archive data.
+ arc_data = label_to_arc_data[label]
+ library = GoLibrary(
+ name = arc_data.name,
+ label = arc_data.label,
+ importpath = arc_data.importpath,
+ importmap = arc_data.importmap,
+ importpath_aliases = arc_data.importpath_aliases,
+ pathtype = arc_data.pathtype,
+ resolve = None,
+ testfilter = None,
+ is_main = False,
+ )
+ deps = [label_to_archive[d] for d in arc_data._dep_labels]
+ source = GoSource(
+ library = library,
+ mode = go.mode,
+ srcs = as_list(arc_data.srcs),
+ orig_srcs = as_list(arc_data.orig_srcs),
+ orig_src_map = dict(zip(arc_data.srcs, arc_data._orig_src_map)),
+ cover = arc_data._cover,
+ embedsrcs = as_list(arc_data._embedsrcs),
+ x_defs = dict(arc_data._x_defs),
+ deps = deps,
+ gc_goopts = as_list(arc_data._gc_goopts),
+ runfiles = go._ctx.runfiles(files = arc_data.data_files),
+ cgo = arc_data._cgo,
+ cdeps = as_list(arc_data._cdeps),
+ cppopts = as_list(arc_data._cppopts),
+ copts = as_list(arc_data._copts),
+ cxxopts = as_list(arc_data._cxxopts),
+ clinkopts = as_list(arc_data._clinkopts),
+ cgo_exports = as_list(arc_data._cgo_exports),
+ )
+
+ # If this archive needs to be recompiled, use go.archive.
+ # Otherwise, create a stub GoArchive, using the original file.
+ if need_recompile[label]:
+ recompile_suffix = ".recompile%d" % i
+ archive = go.archive(go, source, _recompile_suffix = recompile_suffix)
+ else:
+ archive = GoArchive(
+ source = source,
+ data = arc_data,
+ direct = deps,
+ libs = depset(direct = [arc_data.file], transitive = [a.libs for a in deps]),
+ transitive = depset(direct = [arc_data], transitive = [a.transitive for a in deps]),
+ x_defs = source.x_defs,
+ cgo_deps = depset(direct = arc_data._cgo_deps, transitive = [a.cgo_deps for a in deps]),
+ cgo_exports = depset(direct = list(source.cgo_exports), transitive = [a.cgo_exports for a in deps]),
+ runfiles = source.runfiles,
+ mode = go.mode,
+ )
+ label_to_archive[label] = archive
+
+ # Finally, we need to replace external_source.deps with the recompiled
+ # archives.
+ attrs = structs.to_dict(external_source)
+ attrs["deps"] = [label_to_archive[get_archive(dep).data.label] for dep in external_source.deps]
+ return GoSource(**attrs), internal_archive