github.com/bazelbuild/rules_go@v0.47.2-0.20240515105122-e7ddb9ea474e/go/private/tools/path.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:paths.bzl",
    17      "paths",
    18  )
    19  load(
    20      "//go/private:common.bzl",
    21      "as_iterable",
    22      "as_list",
    23  )
    24  load(
    25      "//go/private:providers.bzl",
    26      "GoArchive",
    27      "GoPath",
    28      "effective_importpath_pkgpath",
    29      "get_archive",
    30  )
    31  
    32  def _go_path_impl(ctx):
    33      # Gather all archives. Note that there may be multiple packages with the same
    34      # importpath (e.g., multiple vendored libraries, internal tests). The same
    35      # package may also appear in different modes.
    36      mode_to_deps = {}
    37      for dep in ctx.attr.deps:
    38          archive = get_archive(dep)
    39          if archive.mode not in mode_to_deps:
    40              mode_to_deps[archive.mode] = []
    41          mode_to_deps[archive.mode].append(archive)
    42      mode_to_archive = {}
    43      for mode, archives in mode_to_deps.items():
    44          direct = [a.data for a in archives]
    45          transitive = []
    46          if ctx.attr.include_transitive:
    47              transitive = [a.transitive for a in archives]
    48          mode_to_archive[mode] = depset(direct = direct, transitive = transitive)
    49  
    50      # Collect sources and data files from archives. Merge archives into packages.
    51      pkg_map = {}  # map from package path to structs
    52      for mode, archives in mode_to_archive.items():
    53          for archive in as_iterable(archives):
    54              importpath, pkgpath = effective_importpath_pkgpath(archive)
    55              if importpath == "":
    56                  continue  # synthetic archive or inferred location
    57              pkg = struct(
    58                  importpath = importpath,
    59                  dir = "src/" + pkgpath,
    60                  srcs = as_list(archive.orig_srcs),
    61                  data = as_list(archive.data_files),
    62                  embedsrcs = as_list(archive._embedsrcs),
    63                  pkgs = {mode: archive.file},
    64              )
    65              if pkgpath in pkg_map:
    66                  _merge_pkg(pkg_map[pkgpath], pkg)
    67              else:
    68                  pkg_map[pkgpath] = pkg
    69  
    70      # Build a manifest file that includes all files to copy/link/zip.
    71      inputs = []
    72      manifest_entries = []
    73      manifest_entry_map = {}
    74      for pkg in pkg_map.values():
    75          # src_dir is the path to the directory holding the source.
    76          # Paths to embedded sources will be relative to this path.
    77          src_dir = None
    78  
    79          for f in pkg.srcs:
    80              src_dir = f.dirname
    81              dst = pkg.dir + "/" + f.basename
    82              _add_manifest_entry(manifest_entries, manifest_entry_map, inputs, f, dst)
    83          for f in pkg.embedsrcs:
    84              if src_dir == None:
    85                  fail("cannot relativize {}: src_dir is unset".format(f.path))
    86              embedpath = paths.relativize(f.path, f.root.path)
    87              dst = pkg.dir + "/" + paths.relativize(embedpath.lstrip(ctx.bin_dir.path + "/"), src_dir.lstrip(ctx.bin_dir.path + "/"))
    88              _add_manifest_entry(manifest_entries, manifest_entry_map, inputs, f, dst)
    89      if ctx.attr.include_pkg:
    90          for pkg in pkg_map.values():
    91              for mode, f in pkg.pkgs.items():
    92                  # TODO(jayconrod): include other mode attributes, e.g., race.
    93                  installsuffix = mode.goos + "_" + mode.goarch
    94                  dst = "pkg/" + installsuffix + "/" + pkg.dir[len("src/"):] + ".a"
    95                  _add_manifest_entry(manifest_entries, manifest_entry_map, inputs, f, dst)
    96      if ctx.attr.include_data:
    97          for pkg in pkg_map.values():
    98              for f in pkg.data:
    99                  parts = f.path.split("/")
   100                  if "testdata" in parts:
   101                      i = parts.index("testdata")
   102                      dst = pkg.dir + "/" + "/".join(parts[i:])
   103                  else:
   104                      dst = pkg.dir + "/" + f.basename
   105                  _add_manifest_entry(manifest_entries, manifest_entry_map, inputs, f, dst)
   106      for f in ctx.files.data:
   107          _add_manifest_entry(
   108              manifest_entries,
   109              manifest_entry_map,
   110              inputs,
   111              f,
   112              f.basename,
   113          )
   114      manifest_file = ctx.actions.declare_file(ctx.label.name + "~manifest")
   115      manifest_entries_json = [json.encode(e) for e in manifest_entries]
   116      manifest_content = "[\n  " + ",\n  ".join(manifest_entries_json) + "\n]"
   117      ctx.actions.write(manifest_file, manifest_content)
   118      inputs.append(manifest_file)
   119  
   120      # Execute the builder
   121      if ctx.attr.mode == "archive":
   122          out = ctx.actions.declare_file(ctx.label.name + ".zip")
   123          out_path = out.path
   124          out_short_path = out.short_path
   125          outputs = [out]
   126          out_file = out
   127      elif ctx.attr.mode == "copy":
   128          out = ctx.actions.declare_directory(ctx.label.name)
   129          out_path = out.path
   130          out_short_path = out.short_path
   131          outputs = [out]
   132          out_file = out
   133      else:  # link
   134          # Declare individual outputs in link mode. Symlinks can't point outside
   135          # tree artifacts.
   136          outputs = [
   137              ctx.actions.declare_file(ctx.label.name + "/" + e.dst)
   138              for e in manifest_entries
   139          ]
   140          tag = ctx.actions.declare_file(ctx.label.name + "/.tag")
   141          ctx.actions.write(tag, "")
   142          out_path = tag.dirname
   143          out_short_path = tag.short_path.rpartition("/")[0]
   144          out_file = tag
   145      args = ctx.actions.args()
   146      args.add("-manifest", manifest_file)
   147      args.add("-out", out_path)
   148      args.add("-mode", ctx.attr.mode)
   149      ctx.actions.run(
   150          outputs = outputs,
   151          inputs = inputs,
   152          mnemonic = "GoPath",
   153          executable = ctx.executable._go_path,
   154          arguments = [args],
   155      )
   156  
   157      return [
   158          DefaultInfo(
   159              files = depset(outputs),
   160              runfiles = ctx.runfiles(files = outputs),
   161          ),
   162          GoPath(
   163              gopath = out_short_path,
   164              gopath_file = out_file,
   165              packages = pkg_map.values(),
   166          ),
   167      ]
   168  
   169  go_path = rule(
   170      _go_path_impl,
   171      attrs = {
   172          "deps": attr.label_list(
   173              providers = [GoArchive],
   174              doc = """A list of targets that build Go packages. A directory will be generated from
   175              files in these targets and their transitive dependencies. All targets must
   176              provide [GoArchive] ([go_library], [go_binary], [go_test], and similar
   177              rules have this).
   178  
   179              Only targets with explicit `importpath` attributes will be included in the
   180              generated directory. Synthetic packages (like the main package produced by
   181              [go_test]) and packages with inferred import paths will not be
   182              included. The values of `importmap` attributes may influence the placement
   183              of packages within the generated directory (for example, in vendor
   184              directories).
   185  
   186              The generated directory will contain original source files, including .go,
   187              .s, .h, and .c files compiled by cgo. It will not contain files generated by
   188              tools like cover and cgo, but it will contain generated files passed in
   189              `srcs` attributes like .pb.go files. The generated directory will also
   190              contain runfiles found in `data` attributes.
   191              """,
   192          ),
   193          "data": attr.label_list(
   194              allow_files = True,
   195              doc = """
   196              A list of targets producing data files that will be stored next to the
   197              `src/` directory. Useful for including things like licenses and readmes.
   198              """,
   199          ),
   200          "mode": attr.string(
   201              default = "copy",
   202              values = [
   203                  "archive",
   204                  "copy",
   205                  "link",
   206              ],
   207              doc = """
   208              Determines how the generated directory is provided. May be one of:
   209              <ul>
   210                  <li><code>"archive"</code>: The generated directory is packaged as a single .zip file.</li>
   211                  <li><code>"copy"</code>: The generated directory is a single tree artifact. Source files
   212                  are copied into the tree.</li>
   213                  <li><code>"link"</code>: <b>Unmaintained due to correctness issues</b>. Source files
   214                  are symlinked into the tree. All of the symlink files are provided as separate output
   215                  files.</li>
   216              </ul>
   217  
   218              ***Note:*** In <code>"copy"</code> mode, when a <code>GoPath</code> is consumed as a set of input
   219              files or run files, Bazel may provide symbolic links instead of regular files.
   220              Any program that consumes these files should dereference links, e.g., if you
   221              run <code>tar</code>, use the <code>--dereference</code> flag.
   222              """,
   223          ),
   224          "include_data": attr.bool(
   225              default = True,
   226              doc = """
   227              When true, data files referenced by libraries, binaries, and tests will be
   228              included in the output directory. Files listed in the `data` attribute
   229              for this rule will be included regardless of this attribute.
   230              """,
   231          ),
   232          "include_pkg": attr.bool(
   233              default = False,
   234              doc = """
   235              When true, a `pkg` subdirectory containing the compiled libraries will be created in the
   236              generated `GOPATH` containing compiled libraries.
   237              """,
   238          ),
   239          "include_transitive": attr.bool(
   240              default = True,
   241              doc = """
   242              When true, the transitive dependency graph will be included in the generated `GOPATH`. This is
   243              the default behaviour. When false, only the direct dependencies will be included in the
   244              generated `GOPATH`.
   245              """,
   246          ),
   247          "_go_path": attr.label(
   248              default = "//go/tools/builders:go_path",
   249              executable = True,
   250              cfg = "exec",
   251          ),
   252      },
   253      doc = """`go_path` builds a directory structure that can be used with
   254      tools that understand the GOPATH directory layout. This directory structure
   255      can be built by zipping, copying, or linking files.
   256      `go_path` can depend on one or more Go targets (i.e., [go_library], [go_binary], or [go_test]).
   257      It will include packages from those targets, as well as their transitive dependencies.
   258      Packages will be in subdirectories named after their `importpath` or `importmap` attributes under a `src/` directory.
   259      """,
   260  )
   261  
   262  def _merge_pkg(x, y):
   263      x_srcs = {f.path: None for f in x.srcs}
   264      x_data = {f.path: None for f in x.data}
   265      x_embedsrcs = {f.path: None for f in x.embedsrcs}
   266      x.srcs.extend([f for f in y.srcs if f.path not in x_srcs])
   267      x.data.extend([f for f in y.data if f.path not in x_data])
   268      x.embedsrcs.extend([f for f in y.embedsrcs if f.path not in x_embedsrcs])
   269      x.pkgs.update(y.pkgs)
   270  
   271  def _add_manifest_entry(entries, entry_map, inputs, src, dst):
   272      if dst in entry_map:
   273          if entry_map[dst] != src.path:
   274              fail("{}: references multiple files ({} and {})".format(dst, entry_map[dst], src.path))
   275          return
   276      entries.append(struct(src = src.path, dst = dst))
   277      entry_map[dst] = src.path
   278      inputs.append(src)