github.com/bazelbuild/rules_go@v0.47.2-0.20240515105122-e7ddb9ea474e/go/private/rules/binary.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      "//go/private:common.bzl",
    17      "GO_TOOLCHAIN",
    18      "asm_exts",
    19      "cgo_exts",
    20      "go_exts",
    21      "syso_exts",
    22  )
    23  load(
    24      "//go/private:context.bzl",
    25      "go_context",
    26  )
    27  load(
    28      "//go/private:mode.bzl",
    29      "LINKMODES",
    30      "LINKMODES_EXECUTABLE",
    31      "LINKMODE_C_ARCHIVE",
    32      "LINKMODE_C_SHARED",
    33      "LINKMODE_PLUGIN",
    34      "LINKMODE_SHARED",
    35  )
    36  load(
    37      "//go/private:providers.bzl",
    38      "GoLibrary",
    39      "GoSDK",
    40  )
    41  load(
    42      "//go/private/rules:transition.bzl",
    43      "go_transition",
    44  )
    45  
    46  _EMPTY_DEPSET = depset([])
    47  
    48  def _include_path(hdr):
    49      if not hdr.root.path:
    50          fail("Expected hdr to be a generated file, got source file: " + hdr.path)
    51  
    52      root_relative_path = hdr.path[len(hdr.root.path + "/"):]
    53      if not root_relative_path.startswith("external/"):
    54          return hdr.root.path
    55  
    56      # All headers should be includeable via a path relative to their repository
    57      # root, regardless of whether the repository is external or not. If it is,
    58      # we thus need to append "external/<external repo name>" to the path.
    59      return "/".join([hdr.root.path] + root_relative_path.split("/")[0:2])
    60  
    61  def new_cc_import(
    62          go,
    63          hdrs = _EMPTY_DEPSET,
    64          defines = _EMPTY_DEPSET,
    65          local_defines = _EMPTY_DEPSET,
    66          dynamic_library = None,
    67          static_library = None,
    68          alwayslink = False,
    69          linkopts = []):
    70      return CcInfo(
    71          compilation_context = cc_common.create_compilation_context(
    72              defines = defines,
    73              local_defines = local_defines,
    74              headers = hdrs,
    75              includes = depset([_include_path(hdr) for hdr in hdrs.to_list()]),
    76          ),
    77          linking_context = cc_common.create_linking_context(
    78              linker_inputs = depset([
    79                  cc_common.create_linker_input(
    80                      owner = go.label,
    81                      libraries = depset([
    82                          cc_common.create_library_to_link(
    83                              actions = go.actions,
    84                              cc_toolchain = go.cgo_tools.cc_toolchain,
    85                              feature_configuration = go.cgo_tools.feature_configuration,
    86                              dynamic_library = dynamic_library,
    87                              static_library = static_library,
    88                              alwayslink = alwayslink,
    89                          ),
    90                      ]),
    91                      user_link_flags = depset(linkopts),
    92                  ),
    93              ]),
    94          ),
    95      )
    96  
    97  def _go_binary_impl(ctx):
    98      """go_binary_impl emits actions for compiling and linking a go executable."""
    99      go = go_context(ctx)
   100  
   101      is_main = go.mode.link not in (LINKMODE_SHARED, LINKMODE_PLUGIN)
   102      library = go.new_library(go, importable = False, is_main = is_main)
   103      source = go.library_to_source(go, ctx.attr, library, ctx.coverage_instrumented())
   104      name = ctx.attr.basename
   105      if not name:
   106          name = ctx.label.name
   107      executable = None
   108      if ctx.attr.out:
   109          # Use declare_file instead of attr.output(). When users set output files
   110          # directly, Bazel warns them not to use the same name as the rule, which is
   111          # the common case with go_binary.
   112          executable = ctx.actions.declare_file(ctx.attr.out)
   113      archive, executable, runfiles = go.binary(
   114          go,
   115          name = name,
   116          source = source,
   117          gc_linkopts = gc_linkopts(ctx),
   118          version_file = ctx.version_file,
   119          info_file = ctx.info_file,
   120          executable = executable,
   121      )
   122  
   123      providers = [
   124          library,
   125          source,
   126          archive,
   127          OutputGroupInfo(
   128              cgo_exports = archive.cgo_exports,
   129              compilation_outputs = [archive.data.file],
   130          ),
   131      ]
   132  
   133      if go.mode.link in LINKMODES_EXECUTABLE:
   134          env = {}
   135          for k, v in ctx.attr.env.items():
   136              env[k] = ctx.expand_location(v, ctx.attr.data)
   137          providers.append(RunEnvironmentInfo(environment = env))
   138  
   139          # The executable is automatically added to the runfiles.
   140          providers.append(DefaultInfo(
   141              files = depset([executable]),
   142              runfiles = runfiles,
   143              executable = executable,
   144          ))
   145      else:
   146          # Workaround for https://github.com/bazelbuild/bazel/issues/15043
   147          # As of Bazel 5.1.1, native rules do not pick up the "files" of a data
   148          # dependency's DefaultInfo, only the "data_runfiles". Since transitive
   149          # non-data dependents should not pick up the executable as a runfile
   150          # implicitly, the deprecated "default_runfiles" and "data_runfiles"
   151          # constructor parameters have to be used.
   152          providers.append(DefaultInfo(
   153              files = depset([executable]),
   154              default_runfiles = runfiles,
   155              data_runfiles = runfiles.merge(ctx.runfiles([executable])),
   156          ))
   157  
   158      # If the binary's linkmode is c-archive or c-shared, expose CcInfo
   159      if go.cgo_tools and go.mode.link in (LINKMODE_C_ARCHIVE, LINKMODE_C_SHARED):
   160          cc_import_kwargs = {
   161              "linkopts": {
   162                  "darwin": [],
   163                  "ios": [],
   164                  "windows": ["-mthreads"],
   165              }.get(go.mode.goos, ["-pthread"]),
   166          }
   167          cgo_exports = archive.cgo_exports.to_list()
   168          if cgo_exports:
   169              header = ctx.actions.declare_file("{}.h".format(name))
   170              ctx.actions.symlink(
   171                  output = header,
   172                  target_file = cgo_exports[0],
   173              )
   174              cc_import_kwargs["hdrs"] = depset([header])
   175          if go.mode.link == LINKMODE_C_SHARED:
   176              cc_import_kwargs["dynamic_library"] = executable
   177          elif go.mode.link == LINKMODE_C_ARCHIVE:
   178              cc_import_kwargs["static_library"] = executable
   179              cc_import_kwargs["alwayslink"] = True
   180          ccinfo = new_cc_import(go, **cc_import_kwargs)
   181          ccinfo = cc_common.merge_cc_infos(
   182              cc_infos = [ccinfo, source.cc_info],
   183          )
   184          providers.append(ccinfo)
   185  
   186      return providers
   187  
   188  _go_binary_kwargs = {
   189      "implementation": _go_binary_impl,
   190      "attrs": {
   191          "srcs": attr.label_list(
   192              allow_files = go_exts + asm_exts + cgo_exts + syso_exts,
   193              doc = """The list of Go source files that are compiled to create the package.
   194              Only `.go`, `.s`, and `.syso` files are permitted, unless the `cgo`
   195              attribute is set, in which case,
   196              `.c .cc .cpp .cxx .h .hh .hpp .hxx .inc .m .mm`
   197              files are also permitted. Files may be filtered at build time
   198              using Go [build constraints].
   199              """,
   200          ),
   201          "data": attr.label_list(
   202              allow_files = True,
   203              doc = """List of files needed by this rule at run-time. This may include data files
   204              needed or other programs that may be executed. The [bazel] package may be
   205              used to locate run files; they may appear in different places depending on the
   206              operating system and environment. See [data dependencies] for more
   207              information on data files.
   208              """,
   209          ),
   210          "deps": attr.label_list(
   211              providers = [GoLibrary],
   212              doc = """List of Go libraries this package imports directly.
   213              These may be `go_library` rules or compatible rules with the [GoLibrary] provider.
   214              """,
   215              cfg = go_transition,
   216          ),
   217          "embed": attr.label_list(
   218              providers = [GoLibrary],
   219              doc = """List of Go libraries whose sources should be compiled together with this
   220              binary's sources. Labels listed here must name `go_library`,
   221              `go_proto_library`, or other compatible targets with the [GoLibrary] and
   222              [GoSource] providers. Embedded libraries must all have the same `importpath`,
   223              which must match the `importpath` for this `go_binary` if one is
   224              specified. At most one embedded library may have `cgo = True`, and the
   225              embedding binary may not also have `cgo = True`. See [Embedding] for
   226              more information.
   227              """,
   228              cfg = go_transition,
   229          ),
   230          "embedsrcs": attr.label_list(
   231              allow_files = True,
   232              doc = """The list of files that may be embedded into the compiled package using
   233              `//go:embed` directives. All files must be in the same logical directory
   234              or a subdirectory as source files. All source files containing `//go:embed`
   235              directives must be in the same logical directory. It's okay to mix static and
   236              generated source files and static and generated embeddable files.
   237              """,
   238          ),
   239          "env": attr.string_dict(
   240              doc = """Environment variables to set when the binary is executed with bazel run.
   241              The values (but not keys) are subject to
   242              [location expansion](https://docs.bazel.build/versions/main/skylark/macros.html) but not full
   243              [make variable expansion](https://docs.bazel.build/versions/main/be/make-variables.html).
   244              """,
   245          ),
   246          "importpath": attr.string(
   247              doc = """The import path of this binary. Binaries can't actually be imported, but this
   248              may be used by [go_path] and other tools to report the location of source
   249              files. This may be inferred from embedded libraries.
   250              """,
   251          ),
   252          "gc_goopts": attr.string_list(
   253              doc = """List of flags to add to the Go compilation command when using the gc compiler.
   254              Subject to ["Make variable"] substitution and [Bourne shell tokenization].
   255              """,
   256          ),
   257          "gc_linkopts": attr.string_list(
   258              doc = """List of flags to add to the Go link command when using the gc compiler.
   259              Subject to ["Make variable"] substitution and [Bourne shell tokenization].
   260              """,
   261          ),
   262          "x_defs": attr.string_dict(
   263              doc = """Map of defines to add to the go link command.
   264              See [Defines and stamping] for examples of how to use these.
   265              """,
   266          ),
   267          "basename": attr.string(
   268              doc = """The basename of this binary. The binary
   269              basename may also be platform-dependent: on Windows, we add an .exe extension.
   270              """,
   271          ),
   272          "out": attr.string(
   273              doc = """Sets the output filename for the generated executable. When set, `go_binary`
   274              will write this file without mode-specific directory prefixes, without
   275              linkmode-specific prefixes like "lib", and without platform-specific suffixes
   276              like ".exe". Note that without a mode-specific directory prefix, the
   277              output file (but not its dependencies) will be invalidated in Bazel's cache
   278              when changing configurations.
   279              """,
   280          ),
   281          "cgo": attr.bool(
   282              doc = """If `True`, the package may contain [cgo] code, and `srcs` may contain
   283              C, C++, Objective-C, and Objective-C++ files and non-Go assembly files.
   284              When cgo is enabled, these files will be compiled with the C/C++ toolchain
   285              and included in the package. Note that this attribute does not force cgo
   286              to be enabled. Cgo is enabled for non-cross-compiling builds when a C/C++
   287              toolchain is configured.
   288              """,
   289          ),
   290          "cdeps": attr.label_list(
   291              doc = """The list of other libraries that the c code depends on.
   292              This can be anything that would be allowed in [cc_library deps]
   293              Only valid if `cgo` = `True`.
   294              """,
   295          ),
   296          "cppopts": attr.string_list(
   297              doc = """List of flags to add to the C/C++ preprocessor command.
   298              Subject to ["Make variable"] substitution and [Bourne shell tokenization].
   299              Only valid if `cgo` = `True`.
   300              """,
   301          ),
   302          "copts": attr.string_list(
   303              doc = """List of flags to add to the C compilation command.
   304              Subject to ["Make variable"] substitution and [Bourne shell tokenization].
   305              Only valid if `cgo` = `True`.
   306              """,
   307          ),
   308          "cxxopts": attr.string_list(
   309              doc = """List of flags to add to the C++ compilation command.
   310              Subject to ["Make variable"] substitution and [Bourne shell tokenization].
   311              Only valid if `cgo` = `True`.
   312              """,
   313          ),
   314          "clinkopts": attr.string_list(
   315              doc = """List of flags to add to the C link command.
   316              Subject to ["Make variable"] substitution and [Bourne shell tokenization].
   317              Only valid if `cgo` = `True`.
   318              """,
   319          ),
   320          "pure": attr.string(
   321              default = "auto",
   322              doc = """Controls whether cgo source code and dependencies are compiled and linked,
   323              similar to setting `CGO_ENABLED`. May be one of `on`, `off`,
   324              or `auto`. If `auto`, pure mode is enabled when no C/C++
   325              toolchain is configured or when cross-compiling. It's usually better to
   326              control this on the command line with
   327              `--@io_bazel_rules_go//go/config:pure`. See [mode attributes], specifically
   328              [pure].
   329              """,
   330          ),
   331          "static": attr.string(
   332              default = "auto",
   333              doc = """Controls whether a binary is statically linked. May be one of `on`,
   334              `off`, or `auto`. Not available on all platforms or in all
   335              modes. It's usually better to control this on the command line with
   336              `--@io_bazel_rules_go//go/config:static`. See [mode attributes],
   337              specifically [static].
   338              """,
   339          ),
   340          "race": attr.string(
   341              default = "auto",
   342              doc = """Controls whether code is instrumented for race detection. May be one of
   343              `on`, `off`, or `auto`. Not available when cgo is
   344              disabled. In most cases, it's better to control this on the command line with
   345              `--@io_bazel_rules_go//go/config:race`. See [mode attributes], specifically
   346              [race].
   347              """,
   348          ),
   349          "msan": attr.string(
   350              default = "auto",
   351              doc = """Controls whether code is instrumented for memory sanitization. May be one of
   352              `on`, `off`, or `auto`. Not available when cgo is
   353              disabled. In most cases, it's better to control this on the command line with
   354              `--@io_bazel_rules_go//go/config:msan`. See [mode attributes], specifically
   355              [msan].
   356              """,
   357          ),
   358          "gotags": attr.string_list(
   359              doc = """Enables a list of build tags when evaluating [build constraints]. Useful for
   360              conditional compilation.
   361              """,
   362          ),
   363          "goos": attr.string(
   364              default = "auto",
   365              doc = """Forces a binary to be cross-compiled for a specific operating system. It's
   366              usually better to control this on the command line with `--platforms`.
   367  
   368              This disables cgo by default, since a cross-compiling C/C++ toolchain is
   369              rarely available. To force cgo, set `pure` = `off`.
   370  
   371              See [Cross compilation] for more information.
   372              """,
   373          ),
   374          "goarch": attr.string(
   375              default = "auto",
   376              doc = """Forces a binary to be cross-compiled for a specific architecture. It's usually
   377              better to control this on the command line with `--platforms`.
   378  
   379              This disables cgo by default, since a cross-compiling C/C++ toolchain is
   380              rarely available. To force cgo, set `pure` = `off`.
   381  
   382              See [Cross compilation] for more information.
   383              """,
   384          ),
   385          "linkmode": attr.string(
   386              default = "auto",
   387              values = ["auto"] + LINKMODES,
   388              doc = """Determines how the binary should be built and linked. This accepts some of
   389              the same values as `go build -buildmode` and works the same way.
   390              <br><br>
   391              <ul>
   392              <li>`auto` (default): Controlled by `//go/config:linkmode`, which defaults to `normal`.</li>
   393              <li>`normal`: Builds a normal executable with position-dependent code.</li>
   394              <li>`pie`: Builds a position-independent executable.</li>
   395              <li>`plugin`: Builds a shared library that can be loaded as a Go plugin. Only supported on platforms that support plugins.</li>
   396              <li>`c-shared`: Builds a shared library that can be linked into a C program.</li>
   397              <li>`c-archive`: Builds an archive that can be linked into a C program.</li>
   398              </ul>
   399              """,
   400          ),
   401          "pgoprofile": attr.label(
   402              allow_files = True,
   403              doc = """Provides a pprof file to be used for profile guided optimization when compiling go targets.
   404              A pprof file can also be provided via `--@io_bazel_rules_go//go/config:pgoprofile=<label of a pprof file>`.
   405              Profile guided optimization is only supported on go 1.20+.
   406              See https://go.dev/doc/pgo for more information.
   407              """,
   408              default = "//go/config:empty",
   409          ),
   410          "_go_context_data": attr.label(default = "//:go_context_data", cfg = go_transition),
   411          "_allowlist_function_transition": attr.label(
   412              default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
   413          ),
   414      },
   415      "toolchains": [GO_TOOLCHAIN],
   416      "doc": """This builds an executable from a set of source files,
   417      which must all be in the `main` package. You can run the binary with
   418      `bazel run`, or you can build it with `bazel build` and run it directly.<br><br>
   419      ***Note:*** `name` should be the same as the desired name of the generated binary.<br><br>
   420      **Providers:**
   421      <ul>
   422        <li>[GoLibrary]</li>
   423        <li>[GoSource]</li>
   424        <li>[GoArchive]</li>
   425      </ul>
   426      """,
   427  }
   428  
   429  go_binary = rule(executable = True, **_go_binary_kwargs)
   430  go_non_executable_binary = rule(executable = False, **_go_binary_kwargs)
   431  
   432  def _go_tool_binary_impl(ctx):
   433      sdk = ctx.attr.sdk[GoSDK]
   434      name = ctx.label.name
   435      if sdk.goos == "windows":
   436          name += ".exe"
   437  
   438      out = ctx.actions.declare_file(name)
   439      if sdk.goos == "windows":
   440          # Using pre-declared directory for temporary output as there is no safe
   441          # way under Windows to create unique temporary dir.
   442          gotmp = ctx.actions.declare_directory("gotmp")
   443          cmd = """@echo off
   444  set GOMAXPROCS=1
   445  set GOCACHE=%cd%\\{gotmp}\\gocache
   446  set GOPATH=%cd%"\\{gotmp}\\gopath
   447  {go} build -o {out} -trimpath -ldflags \"{ldflags}\" {srcs}
   448  set GO_EXIT_CODE=%ERRORLEVEL%
   449  RMDIR /S /Q "{gotmp}"
   450  MKDIR "{gotmp}"
   451  exit /b %GO_EXIT_CODE%
   452  """.format(
   453              gotmp = gotmp.path.replace("/", "\\"),
   454              go = sdk.go.path.replace("/", "\\"),
   455              out = out.path,
   456              srcs = " ".join([f.path for f in ctx.files.srcs]),
   457              ldflags = ctx.attr.ldflags,
   458          )
   459          bat = ctx.actions.declare_file(name + ".bat")
   460          ctx.actions.write(
   461              output = bat,
   462              content = cmd,
   463          )
   464          ctx.actions.run(
   465              executable = bat,
   466              inputs = sdk.headers + sdk.tools + sdk.srcs + ctx.files.srcs + [sdk.go],
   467              outputs = [out, gotmp],
   468              mnemonic = "GoToolchainBinaryBuild",
   469          )
   470      else:
   471          # Note: GOPATH is needed for Go 1.16.
   472          cmd = """GOTMP=$(mktemp -d);trap "rm -rf \"$GOTMP\"" EXIT;GOMAXPROCS=1 GOCACHE="$GOTMP"/gocache GOPATH="$GOTMP"/gopath {go} build -o {out} -trimpath -ldflags '{ldflags}' {srcs}""".format(
   473              go = sdk.go.path,
   474              out = out.path,
   475              srcs = " ".join([f.path for f in ctx.files.srcs]),
   476              ldflags = ctx.attr.ldflags,
   477          )
   478          ctx.actions.run_shell(
   479              command = cmd,
   480              inputs = sdk.headers + sdk.tools + sdk.srcs + sdk.libs + ctx.files.srcs + [sdk.go],
   481              outputs = [out],
   482              mnemonic = "GoToolchainBinaryBuild",
   483          )
   484  
   485      return [DefaultInfo(
   486          files = depset([out]),
   487          executable = out,
   488      )]
   489  
   490  go_tool_binary = rule(
   491      implementation = _go_tool_binary_impl,
   492      attrs = {
   493          "srcs": attr.label_list(
   494              allow_files = True,
   495              doc = "Source files for the binary. Must be in 'package main'.",
   496          ),
   497          "sdk": attr.label(
   498              mandatory = True,
   499              providers = [GoSDK],
   500              doc = "The SDK containing tools and libraries to build this binary",
   501          ),
   502          "ldflags": attr.string(
   503              doc = "Raw value to pass to go build via -ldflags without tokenization",
   504          ),
   505      },
   506      executable = True,
   507      doc = """Used instead of go_binary for executables used in the toolchain.
   508  
   509  go_tool_binary depends on tools and libraries that are part of the Go SDK.
   510  It does not depend on other toolchains. It can only compile binaries that
   511  just have a main package and only depend on the standard library and don't
   512  require build constraints.
   513  """,
   514  )
   515  
   516  def gc_linkopts(ctx):
   517      gc_linkopts = [
   518          ctx.expand_make_variables("gc_linkopts", f, {})
   519          for f in ctx.attr.gc_linkopts
   520      ]
   521      return gc_linkopts