...

Text file src/github.com/bazelbuild/rules_go/go/private/rules/test.bzl

Documentation: github.com/bazelbuild/rules_go/go/private/rules

     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
    15load(
    16    "//go/private:context.bzl",
    17    "go_context",
    18)
    19load(
    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)
    29load(
    30    "//go/private/rules:binary.bzl",
    31    "gc_linkopts",
    32)
    33load(
    34    "//go/private:providers.bzl",
    35    "GoArchive",
    36    "GoLibrary",
    37    "GoSource",
    38    "INFERRED_PATH",
    39    "get_archive",
    40)
    41load(
    42    "//go/private/rules:transition.bzl",
    43    "go_transition",
    44)
    45load(
    46    "//go/private:mode.bzl",
    47    "LINKMODES",
    48)
    49load(
    50    "@bazel_skylib//lib:structs.bzl",
    51    "structs",
    52)
    53
    54def _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)
    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
   480go_test = rule(**_go_test_kwargs)
   481
   482def _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

View as plain text