github.com/bazelbuild/rules_go@v0.47.2-0.20240515105122-e7ddb9ea474e/go/private/rules/cgo.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      "get_versioned_shared_lib_extension",
    18      "has_simple_shared_lib_extension",
    19      "hdr_exts",
    20  )
    21  load(
    22      "//go/private:mode.bzl",
    23      "LINKMODE_NORMAL",
    24      "extldflags_from_cc_toolchain",
    25  )
    26  
    27  def cgo_configure(go, srcs, cdeps, cppopts, copts, cxxopts, clinkopts):
    28      """cgo_configure returns the inputs and compile / link options
    29      that are required to build a cgo archive.
    30  
    31      Args:
    32          go: a GoContext.
    33          srcs: list of source files being compiled. Include options are added
    34              for the headers.
    35          cdeps: list of Targets for C++ dependencies. Include and link options
    36              may be added.
    37          cppopts: list of C preprocessor options for the library.
    38          copts: list of C compiler options for the library.
    39          cxxopts: list of C++ compiler options for the library.
    40          clinkopts: list of linker options for the library.
    41  
    42      Returns: a struct containing:
    43          inputs: depset of files that must be available for the build.
    44          deps: depset of files for dynamic libraries.
    45          runfiles: runfiles object for the C/C++ dependencies.
    46          cppopts: complete list of preprocessor options
    47          copts: complete list of C compiler options.
    48          cxxopts: complete list of C++ compiler options.
    49          objcopts: complete list of Objective-C compiler options.
    50          objcxxopts: complete list of Objective-C++ compiler options.
    51          clinkopts: complete list of linker options.
    52      """
    53      if not go.cgo_tools:
    54          fail("Go toolchain does not support cgo")
    55  
    56      cppopts = list(cppopts)
    57      copts = go.cgo_tools.c_compile_options + copts
    58      cxxopts = go.cgo_tools.cxx_compile_options + cxxopts
    59      objcopts = go.cgo_tools.objc_compile_options + copts
    60      objcxxopts = go.cgo_tools.objcxx_compile_options + cxxopts
    61      clinkopts = extldflags_from_cc_toolchain(go) + clinkopts
    62  
    63      # NOTE(#2545): avoid unnecessary dynamic link
    64      if "-static-libstdc++" in clinkopts:
    65          clinkopts = [
    66              option
    67              for option in clinkopts
    68              if option not in ("-lstdc++", "-lc++")
    69          ]
    70  
    71      if go.mode != LINKMODE_NORMAL:
    72          for opt_list in (copts, cxxopts, objcopts, objcxxopts):
    73              if "-fPIC" not in opt_list:
    74                  opt_list.append("-fPIC")
    75  
    76      seen_includes = {}
    77      seen_quote_includes = {}
    78      seen_system_includes = {}
    79      have_hdrs = any([f.basename.endswith(ext) for f in srcs for ext in hdr_exts])
    80      if have_hdrs:
    81          # Add include paths for all sources so we can use include paths relative
    82          # to any source file or any header file. The go command requires all
    83          # sources to be in the same directory, but that's not necessarily the
    84          # case here.
    85          #
    86          # Use -I so either <> or "" includes may be used (same as go command).
    87          for f in srcs:
    88              _include_unique(cppopts, "-I", f.dirname, seen_includes)
    89  
    90      inputs_direct = []
    91      inputs_transitive = []
    92      deps_direct = []
    93      lib_opts = []
    94      runfiles = go._ctx.runfiles(collect_data = True)
    95  
    96      # Always include the sandbox as part of the build. Bazel does this, but it
    97      # doesn't appear in the CompilationContext.
    98      _include_unique(cppopts, "-iquote", ".", seen_quote_includes)
    99      for d in cdeps:
   100          runfiles = runfiles.merge(d.data_runfiles)
   101          if CcInfo in d:
   102              cc_transitive_headers = d[CcInfo].compilation_context.headers
   103              inputs_transitive.append(cc_transitive_headers)
   104              cc_libs, cc_link_flags = _cc_libs_and_flags(d)
   105              inputs_direct.extend(cc_libs)
   106              deps_direct.extend(cc_libs)
   107              cc_defines = d[CcInfo].compilation_context.defines.to_list()
   108              cppopts.extend(["-D" + define for define in cc_defines])
   109              cc_includes = d[CcInfo].compilation_context.includes.to_list()
   110              for inc in cc_includes:
   111                  _include_unique(cppopts, "-I", inc, seen_includes)
   112              cc_quote_includes = d[CcInfo].compilation_context.quote_includes.to_list()
   113              for inc in cc_quote_includes:
   114                  _include_unique(cppopts, "-iquote", inc, seen_quote_includes)
   115              cc_system_includes = d[CcInfo].compilation_context.system_includes.to_list()
   116              for inc in cc_system_includes:
   117                  _include_unique(cppopts, "-isystem", inc, seen_system_includes)
   118              for lib in cc_libs:
   119                  # If both static and dynamic variants are available, Bazel will only give
   120                  # us the static variant. We'll get one file for each transitive dependency,
   121                  # so the same file may appear more than once.
   122                  if lib.basename.startswith("lib"):
   123                      if has_simple_shared_lib_extension(lib.basename):
   124                          # If the loader would be able to find the library using rpaths,
   125                          # use -L and -l instead of hard coding the path to the library in
   126                          # the binary. This gives users more flexibility. The linker will add
   127                          # rpaths later. We can't add them here because they are relative to
   128                          # the binary location, and we don't know where that is.
   129                          libname = lib.basename[len("lib"):lib.basename.rindex(".")]
   130                          clinkopts.extend(["-L", lib.dirname, "-l", libname])
   131                          inputs_direct.append(lib)
   132                          continue
   133                      extension = get_versioned_shared_lib_extension(lib.basename)
   134                      if extension.startswith("so"):
   135                          # With a versioned .so file, we must use the full filename,
   136                          # otherwise the library will not be found by the linker.
   137                          libname = ":%s" % lib.basename
   138                          clinkopts.extend(["-L", lib.dirname, "-l", libname])
   139                          inputs_direct.append(lib)
   140                          continue
   141                      elif extension.startswith("dylib"):
   142                          # A standard versioned dylib is named as libMagick.2.dylib, which is
   143                          # treated as a simple shared library. Non-standard versioned dylibs such as
   144                          # libclntsh.dylib.12.1, users have to create a unversioned symbolic link,
   145                          # so it can be treated as a simple shared library too.
   146                          continue
   147                  lib_opts.append(lib.path)
   148              clinkopts.extend(cc_link_flags)
   149  
   150          elif hasattr(d, "objc"):
   151              cppopts.extend(["-D" + define for define in d.objc.define.to_list()])
   152              for inc in d.objc.include.to_list():
   153                  _include_unique(cppopts, "-I", inc, seen_includes)
   154              for inc in d.objc.iquote.to_list():
   155                  _include_unique(cppopts, "-iquote", inc, seen_quote_includes)
   156              for inc in d.objc.include_system.to_list():
   157                  _include_unique(cppopts, "-isystem", inc, seen_system_includes)
   158  
   159              # TODO(jayconrod): do we need to link against dynamic libraries or
   160              # frameworks? We link against *_fully_linked.a, so maybe not?
   161  
   162          else:
   163              fail("unknown library has neither cc nor objc providers: %s" % d.label)
   164  
   165      inputs = depset(direct = inputs_direct, transitive = inputs_transitive)
   166      deps = depset(direct = deps_direct)
   167  
   168      # HACK: some C/C++ toolchains will ignore libraries (including dynamic libs
   169      # specified with -l flags) unless they appear after .o or .a files with
   170      # undefined symbols they provide. Put all the .a files from cdeps first,
   171      # so that we actually link with -lstdc++ and others.
   172      clinkopts = lib_opts + clinkopts
   173  
   174      return struct(
   175          inputs = inputs,
   176          deps = deps,
   177          runfiles = runfiles,
   178          cppopts = cppopts,
   179          copts = copts,
   180          cxxopts = cxxopts,
   181          objcopts = objcopts,
   182          objcxxopts = objcxxopts,
   183          clinkopts = clinkopts,
   184      )
   185  
   186  def _cc_libs_and_flags(target):
   187      # Copied from get_libs_for_static_executable in migration instructions
   188      # from bazelbuild/bazel#7036.
   189      libs = []
   190      flags = []
   191      for li in target[CcInfo].linking_context.linker_inputs.to_list():
   192          flags.extend(li.user_link_flags)
   193          for library_to_link in li.libraries:
   194              if library_to_link.static_library != None:
   195                  libs.append(library_to_link.static_library)
   196              elif library_to_link.pic_static_library != None:
   197                  libs.append(library_to_link.pic_static_library)
   198              elif library_to_link.interface_library != None:
   199                  libs.append(library_to_link.interface_library)
   200              elif library_to_link.dynamic_library != None:
   201                  libs.append(library_to_link.dynamic_library)
   202      return libs, flags
   203  
   204  def _include_unique(opts, flag, include, seen):
   205      if include in seen:
   206          return
   207      seen[include] = True
   208      opts.extend([flag, include])