github.com/bazelbuild/rules_go@v0.47.2-0.20240515105122-e7ddb9ea474e/go/private/rules/test.bzl (about)

     1  # Copyright 2014 The Bazel Authors. All rights reserved.
     2  #
     3  # Licensed under the Apache License, Version 2.0 (the "License");
     4  # you may not use this file except in compliance with the License.
     5  # You may obtain a copy of the License at
     6  #
     7  #    http://www.apache.org/licenses/LICENSE-2.0
     8  #
     9  # Unless required by applicable law or agreed to in writing, software
    10  # distributed under the License is distributed on an "AS IS" BASIS,
    11  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  # See the License for the specific language governing permissions and
    13  # limitations under the License.
    14  
    15  load(
    16      "@bazel_skylib//lib:structs.bzl",
    17      "structs",
    18  )
    19  load(
    20      "//go/private:common.bzl",
    21      "GO_TOOLCHAIN",
    22      "GO_TOOLCHAIN_LABEL",
    23      "as_list",
    24      "asm_exts",
    25      "cgo_exts",
    26      "go_exts",
    27      "split_srcs",
    28  )
    29  load(
    30      "//go/private:context.bzl",
    31      "go_context",
    32  )
    33  load(
    34      "//go/private:mode.bzl",
    35      "LINKMODES",
    36  )
    37  load(
    38      "//go/private:providers.bzl",
    39      "GoArchive",
    40      "GoLibrary",
    41      "GoSource",
    42      "INFERRED_PATH",
    43      "get_archive",
    44  )
    45  load(
    46      "//go/private/rules:binary.bzl",
    47      "gc_linkopts",
    48  )
    49  load(
    50      "//go/private/rules:transition.bzl",
    51      "go_transition",
    52  )
    53  
    54  def _go_test_impl(ctx):
    55      """go_test_impl implements go testing.
    56  
    57      It emits an action to run the test generator, and then compiles the
    58      test into a binary."""
    59  
    60      go = go_context(ctx)
    61  
    62      # Compile the library to test with internal white box tests
    63      internal_library = go.new_library(go, testfilter = "exclude")
    64      internal_source = go.library_to_source(go, ctx.attr, internal_library, ctx.coverage_instrumented())
    65      internal_archive = go.archive(go, internal_source)
    66      go_srcs = split_srcs(internal_source.srcs).go
    67  
    68      # Compile the library with the external black box tests
    69      external_library = go.new_library(
    70          go,
    71          name = internal_library.name + "_test",
    72          importpath = internal_library.importpath + "_test",
    73          testfilter = "only",
    74      )
    75      external_source = go.library_to_source(go, struct(
    76          srcs = [struct(files = go_srcs)],
    77          data = ctx.attr.data,
    78          embedsrcs = [struct(files = internal_source.embedsrcs)],
    79          deps = internal_archive.direct + [internal_archive],
    80          x_defs = ctx.attr.x_defs,
    81      ), external_library, ctx.coverage_instrumented())
    82      external_source, internal_archive = _recompile_external_deps(go, external_source, internal_archive, [t.label for t in ctx.attr.embed])
    83      external_archive = go.archive(go, external_source, is_external_pkg = True)
    84  
    85      # now generate the main function
    86      repo_relative_rundir = ctx.attr.rundir or ctx.label.package or "."
    87      if ctx.label.workspace_name:
    88          # The test is contained in an external repository (Label.workspace_name is always the empty
    89          # string for the main repository, which is the canonical repository name of this repo).
    90          # The test runner cd's into the directory corresponding to the main repository, so walk up
    91          # and then down.
    92          run_dir = "../" + ctx.label.workspace_name + "/" + repo_relative_rundir
    93      else:
    94          run_dir = repo_relative_rundir
    95  
    96      main_go = go.declare_file(go, path = "testmain.go")
    97      arguments = go.builder_args(go, "gentestmain")
    98      arguments.add("-output", main_go)
    99      if go.coverage_enabled:
   100          if go.mode.race:
   101              arguments.add("-cover_mode", "atomic")
   102          else:
   103              arguments.add("-cover_mode", "set")
   104          arguments.add("-cover_format", go.cover_format)
   105      arguments.add(
   106          # the l is the alias for the package under test, the l_test must be the
   107          # same with the test suffix
   108          "-import",
   109          "l=" + internal_source.library.importpath,
   110      )
   111      arguments.add(
   112          "-import",
   113          "l_test=" + external_source.library.importpath,
   114      )
   115      arguments.add("-pkgname", internal_source.library.importpath)
   116      arguments.add_all(go_srcs, before_each = "-src", format_each = "l=%s")
   117      ctx.actions.run(
   118          inputs = go_srcs,
   119          outputs = [main_go],
   120          mnemonic = "GoTestGenTest",
   121          executable = go.toolchain._builder,
   122          arguments = [arguments],
   123          toolchain = GO_TOOLCHAIN_LABEL,
   124          env = go.env,
   125      )
   126  
   127      test_gc_linkopts = gc_linkopts(ctx)
   128      if not go.mode.debug:
   129          # Disable symbol table and DWARF generation for test binaries.
   130          test_gc_linkopts.extend(["-s", "-w"])
   131  
   132      # Link in the run_dir global for bzltestutil.
   133      # We add "+initfirst/" to the package path so the package is initialized
   134      # before user code. See comment above the init function
   135      # in bzltestutil/init.go.
   136      test_gc_linkopts.extend(["-X", "+initfirst/github.com/bazelbuild/rules_go/go/tools/bzltestutil/chdir.RunDir=" + run_dir])
   137  
   138      # Now compile the test binary itself
   139      test_library = GoLibrary(
   140          name = go.label.name + "~testmain",
   141          label = go.label,
   142          importpath = "testmain",
   143          importmap = "testmain",
   144          importpath_aliases = (),
   145          pathtype = INFERRED_PATH,
   146          is_main = True,
   147          resolve = None,
   148      )
   149      test_deps = external_archive.direct + [external_archive] + ctx.attr._testmain_additional_deps
   150      if ctx.configuration.coverage_enabled:
   151          test_deps.append(go.coverdata)
   152      test_source = go.library_to_source(go, struct(
   153          srcs = [struct(files = [main_go])],
   154          deps = test_deps,
   155      ), test_library, False)
   156      test_archive, executable, runfiles = go.binary(
   157          go,
   158          name = ctx.label.name,
   159          source = test_source,
   160          test_archives = [internal_archive.data],
   161          gc_linkopts = test_gc_linkopts,
   162          version_file = ctx.version_file,
   163          info_file = ctx.info_file,
   164      )
   165  
   166      env = {}
   167      for k, v in ctx.attr.env.items():
   168          env[k] = ctx.expand_location(v, ctx.attr.data)
   169  
   170      run_environment_info = RunEnvironmentInfo(env, ctx.attr.env_inherit)
   171  
   172      # Bazel only looks for coverage data if the test target has an
   173      # InstrumentedFilesProvider. If the provider is found and at least one
   174      # source file is present, Bazel will set the COVERAGE_OUTPUT_FILE
   175      # environment variable during tests and will save that file to the build
   176      # events + test outputs.
   177      return [
   178          test_archive,
   179          DefaultInfo(
   180              files = depset([executable]),
   181              runfiles = runfiles,
   182              executable = executable,
   183          ),
   184          OutputGroupInfo(
   185              compilation_outputs = [internal_archive.data.file],
   186          ),
   187          coverage_common.instrumented_files_info(
   188              ctx,
   189              source_attributes = ["srcs"],
   190              dependency_attributes = ["data", "deps", "embed", "embedsrcs"],
   191              extensions = ["go"],
   192          ),
   193          run_environment_info,
   194      ]
   195  
   196  _go_test_kwargs = {
   197      "implementation": _go_test_impl,
   198      "attrs": {
   199          "data": attr.label_list(
   200              allow_files = True,
   201              doc = """List of files needed by this rule at run-time. This may include data files
   202              needed or other programs that may be executed. The [bazel] package may be
   203              used to locate run files; they may appear in different places depending on the
   204              operating system and environment. See [data dependencies] for more
   205              information on data files.
   206              """,
   207          ),
   208          "srcs": attr.label_list(
   209              allow_files = go_exts + asm_exts + cgo_exts,
   210              doc = """The list of Go source files that are compiled to create the package.
   211              Only `.go` and `.s` files are permitted, unless the `cgo`
   212              attribute is set, in which case,
   213              `.c .cc .cpp .cxx .h .hh .hpp .hxx .inc .m .mm`
   214              files are also permitted. Files may be filtered at build time
   215              using Go [build constraints].
   216              """,
   217          ),
   218          "deps": attr.label_list(
   219              providers = [GoLibrary],
   220              doc = """List of Go libraries this test imports directly.
   221              These may be go_library rules or compatible rules with the [GoLibrary] provider.
   222              """,
   223              cfg = go_transition,
   224          ),
   225          "embed": attr.label_list(
   226              providers = [GoLibrary],
   227              doc = """List of Go libraries whose sources should be compiled together with this
   228              package's sources. Labels listed here must name `go_library`,
   229              `go_proto_library`, or other compatible targets with the [GoLibrary] and
   230              [GoSource] providers. Embedded libraries must have the same `importpath` as
   231              the embedding library. At most one embedded library may have `cgo = True`,
   232              and the embedding library may not also have `cgo = True`. See [Embedding]
   233              for more information.
   234              """,
   235              cfg = go_transition,
   236          ),
   237          "embedsrcs": attr.label_list(
   238              allow_files = True,
   239              doc = """The list of files that may be embedded into the compiled package using
   240              `//go:embed` directives. All files must be in the same logical directory
   241              or a subdirectory as source files. All source files containing `//go:embed`
   242              directives must be in the same logical directory. It's okay to mix static and
   243              generated source files and static and generated embeddable files.
   244              """,
   245          ),
   246          "env": attr.string_dict(
   247              doc = """Environment variables to set for the test execution.
   248              The values (but not keys) are subject to
   249              [location expansion](https://docs.bazel.build/versions/main/skylark/macros.html) but not full
   250              [make variable expansion](https://docs.bazel.build/versions/main/be/make-variables.html).
   251              """,
   252          ),
   253          "env_inherit": attr.string_list(
   254              doc = """Environment variables to inherit from the external environment.
   255              """,
   256          ),
   257          "importpath": attr.string(
   258              doc = """The import path of this test. Tests can't actually be imported, but this
   259              may be used by [go_path] and other tools to report the location of source
   260              files. This may be inferred from embedded libraries.
   261              """,
   262          ),
   263          "gc_goopts": attr.string_list(
   264              doc = """List of flags to add to the Go compilation command when using the gc compiler.
   265              Subject to ["Make variable"] substitution and [Bourne shell tokenization].
   266              """,
   267          ),
   268          "gc_linkopts": attr.string_list(
   269              doc = """List of flags to add to the Go link command when using the gc compiler.
   270              Subject to ["Make variable"] substitution and [Bourne shell tokenization].
   271              """,
   272          ),
   273          "rundir": attr.string(
   274              doc = """ A directory to cd to before the test is run.
   275              This should be a path relative to the root directory of the
   276              repository in which the test is defined, which can be the main or an
   277              external repository.
   278  
   279              The default behaviour is to change to the relative path
   280              corresponding to the test's package, which replicates the normal
   281              behaviour of `go test` so it is easy to write compatible tests.
   282  
   283              Setting it to `.` makes the test behave the normal way for a bazel
   284              test, except that the working directory is always that of the test's
   285              repository, which is not necessarily the main repository.
   286  
   287              Note: If runfile symlinks are disabled (such as on Windows by
   288              default), the test will run in the working directory set by Bazel,
   289              which is the subdirectory of the runfiles directory corresponding to
   290              the main repository.
   291              """,
   292          ),
   293          "x_defs": attr.string_dict(
   294              doc = """Map of defines to add to the go link command.
   295              See [Defines and stamping] for examples of how to use these.
   296              """,
   297          ),
   298          "linkmode": attr.string(
   299              default = "auto",
   300              values = ["auto"] + LINKMODES,
   301              doc = """Determines how the binary should be built and linked. This accepts some of
   302              the same values as `go build -buildmode` and works the same way.
   303              <br><br>
   304              <ul>
   305              <li>`auto` (default): Controlled by `//go/config:linkmode`, which defaults to `normal`.</li>
   306              <li>`normal`: Builds a normal executable with position-dependent code.</li>
   307              <li>`pie`: Builds a position-independent executable.</li>
   308              <li>`plugin`: Builds a shared library that can be loaded as a Go plugin. Only supported on platforms that support plugins.</li>
   309              <li>`c-shared`: Builds a shared library that can be linked into a C program.</li>
   310              <li>`c-archive`: Builds an archive that can be linked into a C program.</li>
   311              </ul>
   312              """,
   313          ),
   314          "cgo": attr.bool(
   315              doc = """
   316              If `True`, the package may contain [cgo] code, and `srcs` may contain
   317              C, C++, Objective-C, and Objective-C++ files and non-Go assembly files.
   318              When cgo is enabled, these files will be compiled with the C/C++ toolchain
   319              and included in the package. Note that this attribute does not force cgo
   320              to be enabled. Cgo is enabled for non-cross-compiling builds when a C/C++
   321              toolchain is configured.
   322              """,
   323          ),
   324          "cdeps": attr.label_list(
   325              doc = """The list of other libraries that the c code depends on.
   326              This can be anything that would be allowed in [cc_library deps]
   327              Only valid if `cgo` = `True`.
   328              """,
   329          ),
   330          "cppopts": attr.string_list(
   331              doc = """List of flags to add to the C/C++ preprocessor command.
   332              Subject to ["Make variable"] substitution and [Bourne shell tokenization].
   333              Only valid if `cgo` = `True`.
   334              """,
   335          ),
   336          "copts": attr.string_list(
   337              doc = """List of flags to add to the C compilation command.
   338              Subject to ["Make variable"] substitution and [Bourne shell tokenization].
   339              Only valid if `cgo` = `True`.
   340              """,
   341          ),
   342          "cxxopts": attr.string_list(
   343              doc = """List of flags to add to the C++ compilation command.
   344              Subject to ["Make variable"] substitution and [Bourne shell tokenization].
   345              Only valid if `cgo` = `True`.
   346              """,
   347          ),
   348          "clinkopts": attr.string_list(
   349              doc = """List of flags to add to the C link command.
   350              Subject to ["Make variable"] substitution and [Bourne shell tokenization].
   351              Only valid if `cgo` = `True`.
   352              """,
   353          ),
   354          "pure": attr.string(
   355              default = "auto",
   356              doc = """Controls whether cgo source code and dependencies are compiled and linked,
   357              similar to setting `CGO_ENABLED`. May be one of `on`, `off`,
   358              or `auto`. If `auto`, pure mode is enabled when no C/C++
   359              toolchain is configured or when cross-compiling. It's usually better to
   360              control this on the command line with
   361              `--@io_bazel_rules_go//go/config:pure`. See [mode attributes], specifically
   362              [pure].
   363              """,
   364          ),
   365          "static": attr.string(
   366              default = "auto",
   367              doc = """Controls whether a binary is statically linked. May be one of `on`,
   368              `off`, or `auto`. Not available on all platforms or in all
   369              modes. It's usually better to control this on the command line with
   370              `--@io_bazel_rules_go//go/config:static`. See [mode attributes],
   371              specifically [static].
   372              """,
   373          ),
   374          "race": attr.string(
   375              default = "auto",
   376              doc = """Controls whether code is instrumented for race detection. May be one of
   377              `on`, `off`, or `auto`. Not available when cgo is
   378              disabled. In most cases, it's better to control this on the command line with
   379              `--@io_bazel_rules_go//go/config:race`. See [mode attributes], specifically
   380              [race].
   381              """,
   382          ),
   383          "msan": attr.string(
   384              default = "auto",
   385              doc = """Controls whether code is instrumented for memory sanitization. May be one of
   386              `on`, `off`, or `auto`. Not available when cgo is
   387              disabled. In most cases, it's better to control this on the command line with
   388              `--@io_bazel_rules_go//go/config:msan`. See [mode attributes], specifically
   389              [msan].
   390              """,
   391          ),
   392          "gotags": attr.string_list(
   393              doc = """Enables a list of build tags when evaluating [build constraints]. Useful for
   394              conditional compilation.
   395              """,
   396          ),
   397          "goos": attr.string(
   398              default = "auto",
   399              doc = """Forces a binary to be cross-compiled for a specific operating system. It's
   400              usually better to control this on the command line with `--platforms`.
   401  
   402              This disables cgo by default, since a cross-compiling C/C++ toolchain is
   403              rarely available. To force cgo, set `pure` = `off`.
   404  
   405              See [Cross compilation] for more information.
   406              """,
   407          ),
   408          "goarch": attr.string(
   409              default = "auto",
   410              doc = """Forces a binary to be cross-compiled for a specific architecture. It's usually
   411              better to control this on the command line with `--platforms`.
   412  
   413              This disables cgo by default, since a cross-compiling C/C++ toolchain is
   414              rarely available. To force cgo, set `pure` = `off`.
   415  
   416              See [Cross compilation] for more information.
   417              """,
   418          ),
   419          "_go_context_data": attr.label(default = "//:go_context_data", cfg = go_transition),
   420          "_testmain_additional_deps": attr.label_list(
   421              providers = [GoLibrary],
   422              default = ["//go/tools/bzltestutil"],
   423              cfg = go_transition,
   424          ),
   425          # Required for Bazel to collect coverage of instrumented C/C++ binaries
   426          # executed by go_test.
   427          # This is just a shell script and thus cheap enough to depend on
   428          # unconditionally.
   429          "_collect_cc_coverage": attr.label(
   430              default = "@bazel_tools//tools/test:collect_cc_coverage",
   431              cfg = "exec",
   432          ),
   433          # Required for Bazel to merge coverage reports for Go and other
   434          # languages into a single report per test.
   435          # Using configuration_field ensures that the tool is only built when
   436          # run with bazel coverage, not with bazel test.
   437          "_lcov_merger": attr.label(
   438              default = configuration_field(fragment = "coverage", name = "output_generator"),
   439              cfg = "exec",
   440          ),
   441          "_allowlist_function_transition": attr.label(
   442              default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
   443          ),
   444      },
   445      "executable": True,
   446      "test": True,
   447      "toolchains": [GO_TOOLCHAIN],
   448      "doc": """This builds a set of tests that can be run with `bazel test`.<br><br>
   449      To run all tests in the workspace, and print output on failure (the
   450      equivalent of `go test ./...`), run<br>
   451      ```
   452      bazel test --test_output=errors //...
   453      ```<br><br>
   454      To run a Go benchmark test, run<br>
   455      ```
   456      bazel run //path/to:test -- -test.bench=.
   457      ```<br><br>
   458      You can run specific tests by passing the `--test_filter=pattern
   459      <test_filter_>` argument to Bazel. You can pass arguments to tests by passing
   460      `--test_arg=arg <test_arg_>` arguments to Bazel, and you can set environment
   461      variables in the test environment by passing
   462      `--test_env=VAR=value <test_env_>`. You can terminate test execution after the first
   463      failure by passing the `--test_runner_fail_fast <test_runner_fail_fast_>` argument
   464      to Bazel. This is equivalent to passing `--test_arg=-failfast <test_arg_>`.<br><br>
   465      To write structured testlog information to Bazel's `XML_OUTPUT_FILE`, tests
   466      ran with `bazel test` execute using a wrapper. This functionality can be
   467      disabled by setting `GO_TEST_WRAP=0` in the test environment. Additionally,
   468      the testbinary can be invoked with `-test.v` by setting
   469      `GO_TEST_WRAP_TESTV=1` in the test environment; this will result in the
   470      `XML_OUTPUT_FILE` containing more granular data.<br><br>
   471      ***Note:*** To interoperate cleanly with old targets generated by [Gazelle], `name`
   472      should be `go_default_test` for internal tests and
   473      `go_default_xtest` for external tests. Gazelle now generates
   474      the name  based on the last component of the path. For example, a test
   475      in `//foo/bar` is named `bar_test`, and uses internal and external
   476      sources.
   477      """,
   478  }
   479  
   480  go_test = rule(**_go_test_kwargs)
   481  
   482  def _recompile_external_deps(go, external_source, internal_archive, library_labels):
   483      """Recompiles some archives in order to split internal and external tests.
   484  
   485      go_test, like 'go test', splits tests into two separate archives: an
   486      internal archive ('package foo') and an external archive
   487      ('package foo_test'). The library under test is embedded into the internal
   488      archive. The external archive may import it and may depend on symbols
   489      defined in the internal test files.
   490  
   491      To avoid conflicts, the library under test must not be linked into the test
   492      binary, since the internal test archive embeds the same sources.
   493      Libraries imported by the external test that transitively import the
   494      library under test must be recompiled too, or the linker will complain that
   495      export data they were compiled with doesn't match the export data they
   496      are linked with.
   497  
   498      This function identifies which archives may need to be recompiled, then
   499      declares new output files and actions to recompile them. This is an
   500      unfortunately an expensive process requiring O(V+E) time and space in the
   501      size of the test's dependency graph for each test.
   502  
   503      Args:
   504          go: go object returned by go_context.
   505          external_source: GoSource for the external archive.
   506          internal_archive: GoArchive for the internal archive.
   507          library_labels: labels for embedded libraries under test.
   508  
   509      Returns:
   510          external_soruce: recompiled GoSource for the external archive. If no
   511              recompilation is needed, the original GoSource is returned.
   512          internal_archive: recompiled GoArchive for the internal archive. If no
   513              recompilation is needed, the original GoSource is returned.
   514      """
   515  
   516      # If no libraries are embedded in the internal archive, then nothing needs
   517      # to be recompiled.
   518      if not library_labels:
   519          return external_source, internal_archive
   520  
   521      # Build a map from labels to GoArchiveData.
   522      # If none of the librares embedded in the internal archive are in the
   523      # dependency graph, then nothing needs to be recompiled.
   524      arc_data_list = depset(transitive = [get_archive(dep).transitive for dep in external_source.deps]).to_list()
   525      label_to_arc_data = {a.label: a for a in arc_data_list}
   526      if all([l not in label_to_arc_data for l in library_labels]):
   527          return external_source, internal_archive
   528  
   529      # Build a depth-first post-order list of dependencies starting with the
   530      # external archive. Each archive appears after its dependencies and before
   531      # its dependents.
   532      #
   533      # This is tricky because Starlark doesn't support recursion or while loops.
   534      # We simulate a while loop by iterating over a list of 2N elements where
   535      # N is the number of archives. Each archive is pushed onto the stack
   536      # twice: once before its dependencies are pushed, and once after.
   537  
   538      # dep_list is the post-order list of dependencies we're building.
   539      dep_list = []
   540  
   541      # stack is a stack of targets to process. We're done when it's empty.
   542      stack = [get_archive(dep).data.label for dep in external_source.deps]
   543  
   544      # deps_pushed tracks the status of each target.
   545      # DEPS_UNPROCESSED means the target is on the stack, but its dependencies
   546      # are not.
   547      # Non-negative integers are the number of dependencies on the stack that
   548      # still need to be processed.
   549      # A target is on the stack if its status is DEPS_UNPROCESSED or 0.
   550      DEPS_UNPROCESSED = -1
   551      deps_pushed = {l: DEPS_UNPROCESSED for l in stack}
   552  
   553      # dependents maps labels to lists of known dependents. When a target is
   554      # processed, its dependents' deps_pushed count is deprecated.
   555      dependents = {l: [] for l in stack}
   556  
   557      # step is a list to iterate over to simulate a while loop. i tracks
   558      # iterations.
   559      step = [None] * (2 * len(arc_data_list))
   560      i = 0
   561      for _ in step:
   562          if len(stack) == 0:
   563              break
   564          i += 1
   565  
   566          label = stack.pop()
   567          if deps_pushed[label] == 0:
   568              # All deps have been added to dep_list. Append this target to the
   569              # list. If a dependent is not waiting for anything else, push
   570              # it back onto the stack.
   571              dep_list.append(label)
   572              for p in dependents.get(label, []):
   573                  deps_pushed[p] -= 1
   574                  if deps_pushed[p] == 0:
   575                      stack.append(p)
   576              continue
   577  
   578          # deps_pushed[label] == None, indicating we don't know whether this
   579          # targets dependencies have been processed. Other targets processed
   580          # earlier may depend on them.
   581          deps_pushed[label] = 0
   582          arc_data = label_to_arc_data[label]
   583          for c in arc_data._dep_labels:
   584              if c not in deps_pushed:
   585                  # Dependency not seen yet; push it.
   586                  stack.append(c)
   587                  deps_pushed[c] = None
   588                  deps_pushed[label] += 1
   589                  dependents[c] = [label]
   590              elif deps_pushed[c] != 0:
   591                  # Dependency pushed, not processed; wait for it.
   592                  deps_pushed[label] += 1
   593                  dependents[c].append(label)
   594          if deps_pushed[label] == 0:
   595              # No dependencies to wait for; push self.
   596              stack.append(label)
   597      if i != len(step):
   598          fail("assertion failed: iterated %d times instead of %d" % (i, len(step)))
   599  
   600      # Determine which dependencies need to be recompiled because they depend
   601      # on embedded libraries.
   602      need_recompile = {}
   603      for label in dep_list:
   604          arc_data = label_to_arc_data[label]
   605          need_recompile[label] = any([
   606              dep in library_labels or need_recompile[dep]
   607              for dep in arc_data._dep_labels
   608          ])
   609  
   610      # Recompile the internal archive without dependencies that need
   611      # recompilation. This breaks a cycle which occurs because the deps list
   612      # is shared between the internal and external archive. The internal archive
   613      # can't import anything that imports itself.
   614      internal_source = internal_archive.source
   615  
   616      internal_deps = []
   617  
   618      # Pass internal dependencies that need to be recompiled down to the builder to check if the internal archive
   619      # tries to import any of the dependencies. If there is, that means that there is a dependency cycle.
   620      need_recompile_deps = []
   621      for dep in internal_source.deps:
   622          dep_data = get_archive(dep).data
   623          if not need_recompile[dep_data.label]:
   624              internal_deps.append(dep)
   625          else:
   626              need_recompile_deps.append(dep_data.importpath)
   627  
   628      x_defs = dict(internal_source.x_defs)
   629      x_defs.update(internal_archive.x_defs)
   630      attrs = structs.to_dict(internal_source)
   631      attrs["deps"] = internal_deps
   632      attrs["x_defs"] = x_defs
   633      internal_source = GoSource(**attrs)
   634      internal_archive = go.archive(go, internal_source, _recompile_suffix = ".recompileinternal", recompile_internal_deps = need_recompile_deps)
   635  
   636      # Build a map from labels to possibly recompiled GoArchives.
   637      label_to_archive = {}
   638      i = 0
   639      for label in dep_list:
   640          i += 1
   641          recompile_suffix = ".recompile%d" % i
   642  
   643          # If this library is the internal archive, use the recompiled version.
   644          if label == internal_archive.data.label:
   645              label_to_archive[label] = internal_archive
   646              continue
   647  
   648          # If this is a library embedded into the internal test archive,
   649          # use the internal test archive instead.
   650          if label in library_labels:
   651              label_to_archive[label] = internal_archive
   652              continue
   653  
   654          # Create a stub GoLibrary and GoSource from the archive data.
   655          arc_data = label_to_arc_data[label]
   656          library = GoLibrary(
   657              name = arc_data.name,
   658              label = arc_data.label,
   659              importpath = arc_data.importpath,
   660              importmap = arc_data.importmap,
   661              importpath_aliases = arc_data.importpath_aliases,
   662              pathtype = arc_data.pathtype,
   663              resolve = None,
   664              testfilter = None,
   665              is_main = False,
   666          )
   667          deps = [label_to_archive[d] for d in arc_data._dep_labels]
   668          source = GoSource(
   669              library = library,
   670              mode = go.mode,
   671              srcs = as_list(arc_data.srcs),
   672              orig_srcs = as_list(arc_data.orig_srcs),
   673              orig_src_map = dict(zip(arc_data.srcs, arc_data._orig_src_map)),
   674              cover = arc_data._cover,
   675              embedsrcs = as_list(arc_data._embedsrcs),
   676              x_defs = dict(arc_data._x_defs),
   677              deps = deps,
   678              gc_goopts = as_list(arc_data._gc_goopts),
   679              runfiles = go._ctx.runfiles(files = arc_data.data_files),
   680              cgo = arc_data._cgo,
   681              cdeps = as_list(arc_data._cdeps),
   682              cppopts = as_list(arc_data._cppopts),
   683              copts = as_list(arc_data._copts),
   684              cxxopts = as_list(arc_data._cxxopts),
   685              clinkopts = as_list(arc_data._clinkopts),
   686              cgo_exports = as_list(arc_data._cgo_exports),
   687          )
   688  
   689          # If this archive needs to be recompiled, use go.archive.
   690          # Otherwise, create a stub GoArchive, using the original file.
   691          if need_recompile[label]:
   692              recompile_suffix = ".recompile%d" % i
   693              archive = go.archive(go, source, _recompile_suffix = recompile_suffix)
   694          else:
   695              archive = GoArchive(
   696                  source = source,
   697                  data = arc_data,
   698                  direct = deps,
   699                  libs = depset(direct = [arc_data.file], transitive = [a.libs for a in deps]),
   700                  transitive = depset(direct = [arc_data], transitive = [a.transitive for a in deps]),
   701                  x_defs = source.x_defs,
   702                  cgo_deps = depset(direct = arc_data._cgo_deps, transitive = [a.cgo_deps for a in deps]),
   703                  cgo_exports = depset(direct = list(source.cgo_exports), transitive = [a.cgo_exports for a in deps]),
   704                  runfiles = source.runfiles,
   705                  mode = go.mode,
   706              )
   707          label_to_archive[label] = archive
   708  
   709      # Finally, we need to replace external_source.deps with the recompiled
   710      # archives.
   711      attrs = structs.to_dict(external_source)
   712      attrs["deps"] = [label_to_archive[get_archive(dep).data.label] for dep in external_source.deps]
   713      return GoSource(**attrs), internal_archive