| # Copyright 2014 The Bazel Authors. All rights reserved. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| load( |
| "//go/private:providers.bzl", |
| "GoArchive", |
| "GoPath", |
| "effective_importpath_pkgpath", |
| "get_archive", |
| ) |
| load( |
| "//go/private:common.bzl", |
| "as_iterable", |
| "as_list", |
| ) |
| load( |
| "@bazel_skylib//lib:paths.bzl", |
| "paths", |
| ) |
| |
| def _go_path_impl(ctx): |
| # Gather all archives. Note that there may be multiple packages with the same |
| # importpath (e.g., multiple vendored libraries, internal tests). The same |
| # package may also appear in different modes. |
| mode_to_deps = {} |
| for dep in ctx.attr.deps: |
| archive = get_archive(dep) |
| if archive.mode not in mode_to_deps: |
| mode_to_deps[archive.mode] = [] |
| mode_to_deps[archive.mode].append(archive) |
| mode_to_archive = {} |
| for mode, archives in mode_to_deps.items(): |
| direct = [a.data for a in archives] |
| transitive = [] |
| if ctx.attr.include_transitive: |
| transitive = [a.transitive for a in archives] |
| mode_to_archive[mode] = depset(direct = direct, transitive = transitive) |
| |
| # Collect sources and data files from archives. Merge archives into packages. |
| pkg_map = {} # map from package path to structs |
| for mode, archives in mode_to_archive.items(): |
| for archive in as_iterable(archives): |
| importpath, pkgpath = effective_importpath_pkgpath(archive) |
| if importpath == "": |
| continue # synthetic archive or inferred location |
| pkg = struct( |
| importpath = importpath, |
| dir = "src/" + pkgpath, |
| srcs = as_list(archive.orig_srcs), |
| data = as_list(archive.data_files), |
| embedsrcs = as_list(archive._embedsrcs), |
| pkgs = {mode: archive.file}, |
| ) |
| if pkgpath in pkg_map: |
| _merge_pkg(pkg_map[pkgpath], pkg) |
| else: |
| pkg_map[pkgpath] = pkg |
| |
| # Build a manifest file that includes all files to copy/link/zip. |
| inputs = [] |
| manifest_entries = [] |
| manifest_entry_map = {} |
| for pkg in pkg_map.values(): |
| # src_dir is the path to the directory holding the source. |
| # Paths to embedded sources will be relative to this path. |
| src_dir = None |
| |
| for f in pkg.srcs: |
| src_dir = f.dirname |
| dst = pkg.dir + "/" + f.basename |
| _add_manifest_entry(manifest_entries, manifest_entry_map, inputs, f, dst) |
| for f in pkg.embedsrcs: |
| if src_dir == None: |
| fail("cannot relativize {}: src_dir is unset".format(f.path)) |
| dst = pkg.dir + "/" + paths.relativize(f.path, src_dir) |
| _add_manifest_entry(manifest_entries, manifest_entry_map, inputs, f, dst) |
| if ctx.attr.include_pkg: |
| for pkg in pkg_map.values(): |
| for mode, f in pkg.pkgs.items(): |
| # TODO(jayconrod): include other mode attributes, e.g., race. |
| installsuffix = mode.goos + "_" + mode.goarch |
| dst = "pkg/" + installsuffix + "/" + pkg.dir[len("src/"):] + ".a" |
| _add_manifest_entry(manifest_entries, manifest_entry_map, inputs, f, dst) |
| if ctx.attr.include_data: |
| for pkg in pkg_map.values(): |
| for f in pkg.data: |
| parts = f.path.split("/") |
| if "testdata" in parts: |
| i = parts.index("testdata") |
| dst = pkg.dir + "/" + "/".join(parts[i:]) |
| else: |
| dst = pkg.dir + "/" + f.basename |
| _add_manifest_entry(manifest_entries, manifest_entry_map, inputs, f, dst) |
| for f in ctx.files.data: |
| _add_manifest_entry( |
| manifest_entries, |
| manifest_entry_map, |
| inputs, |
| f, |
| f.basename, |
| ) |
| manifest_file = ctx.actions.declare_file(ctx.label.name + "~manifest") |
| manifest_entries_json = [e.to_json() for e in manifest_entries] |
| manifest_content = "[\n " + ",\n ".join(manifest_entries_json) + "\n]" |
| ctx.actions.write(manifest_file, manifest_content) |
| inputs.append(manifest_file) |
| |
| # Execute the builder |
| if ctx.attr.mode == "archive": |
| out = ctx.actions.declare_file(ctx.label.name + ".zip") |
| out_path = out.path |
| out_short_path = out.short_path |
| outputs = [out] |
| out_file = out |
| elif ctx.attr.mode == "copy": |
| out = ctx.actions.declare_directory(ctx.label.name) |
| out_path = out.path |
| out_short_path = out.short_path |
| outputs = [out] |
| out_file = out |
| else: # link |
| # Declare individual outputs in link mode. Symlinks can't point outside |
| # tree artifacts. |
| outputs = [ |
| ctx.actions.declare_file(ctx.label.name + "/" + e.dst) |
| for e in manifest_entries |
| ] |
| tag = ctx.actions.declare_file(ctx.label.name + "/.tag") |
| ctx.actions.write(tag, "") |
| out_path = tag.dirname |
| out_short_path = tag.short_path.rpartition("/")[0] |
| out_file = tag |
| args = ctx.actions.args() |
| args.add("-manifest", manifest_file) |
| args.add("-out", out_path) |
| args.add("-mode", ctx.attr.mode) |
| ctx.actions.run( |
| outputs = outputs, |
| inputs = inputs, |
| mnemonic = "GoPath", |
| executable = ctx.executable._go_path, |
| arguments = [args], |
| ) |
| |
| return [ |
| DefaultInfo( |
| files = depset(outputs), |
| runfiles = ctx.runfiles(files = outputs), |
| ), |
| GoPath( |
| gopath = out_short_path, |
| gopath_file = out_file, |
| packages = pkg_map.values(), |
| ), |
| ] |
| |
| go_path = rule( |
| _go_path_impl, |
| attrs = { |
| "deps": attr.label_list( |
| providers = [GoArchive], |
| doc = """A list of targets that build Go packages. A directory will be generated from |
| files in these targets and their transitive dependencies. All targets must |
| provide [GoArchive] ([go_library], [go_binary], [go_test], and similar |
| rules have this). |
| |
| Only targets with explicit `importpath` attributes will be included in the |
| generated directory. Synthetic packages (like the main package produced by |
| [go_test]) and packages with inferred import paths will not be |
| included. The values of `importmap` attributes may influence the placement |
| of packages within the generated directory (for example, in vendor |
| directories). |
| |
| The generated directory will contain original source files, including .go, |
| .s, .h, and .c files compiled by cgo. It will not contain files generated by |
| tools like cover and cgo, but it will contain generated files passed in |
| `srcs` attributes like .pb.go files. The generated directory will also |
| contain runfiles found in `data` attributes. |
| """, |
| ), |
| "data": attr.label_list( |
| allow_files = True, |
| doc = """ |
| A list of targets producing data files that will be stored next to the |
| `src/` directory. Useful for including things like licenses and readmes. |
| """, |
| ), |
| "mode": attr.string( |
| default = "copy", |
| values = [ |
| "archive", |
| "copy", |
| "link", |
| ], |
| doc = """ |
| Determines how the generated directory is provided. May be one of: |
| <ul> |
| <li><code>"archive"</code>: The generated directory is packaged as a single .zip file.</li> |
| <li><code>"copy"</code>: The generated directory is a single tree artifact. Source files |
| are copied into the tree.</li> |
| <li><code>"link"</code>: Source files are symlinked into the tree. All of the symlink |
| files are provided as separate output files.</li> |
| </ul> |
| |
| ***Note:*** In <code>"copy"</code> mode, when a <code>GoPath</code> is consumed as a set of input |
| files or run files, Bazel may provide symbolic links instead of regular files. |
| Any program that consumes these files should dereference links, e.g., if you |
| run <code>tar</code>, use the <code>--dereference</code> flag. |
| """, |
| ), |
| "include_data": attr.bool( |
| default = True, |
| doc = """ |
| When true, data files referenced by libraries, binaries, and tests will be |
| included in the output directory. Files listed in the `data` attribute |
| for this rule will be included regardless of this attribute. |
| """, |
| ), |
| "include_pkg": attr.bool( |
| default = False, |
| doc = """ |
| When true, a `pkg` subdirectory containing the compiled libraries will be created in the |
| generated `GOPATH` containing compiled libraries. |
| """, |
| ), |
| "include_transitive": attr.bool( |
| default = True, |
| doc = """ |
| When true, the transitive dependency graph will be included in the generated `GOPATH`. This is |
| the default behaviour. When false, only the direct dependencies will be included in the |
| generated `GOPATH`. |
| """, |
| ), |
| "_go_path": attr.label( |
| default = "//go/tools/builders:go_path", |
| executable = True, |
| cfg = "exec", |
| ), |
| }, |
| doc = """`go_path` builds a directory structure that can be used with |
| tools that understand the GOPATH directory layout. This directory structure |
| can be built by zipping, copying, or linking files. |
| `go_path` can depend on one or more Go targets (i.e., [go_library], [go_binary], or [go_test]). |
| It will include packages from those targets, as well as their transitive dependencies. |
| Packages will be in subdirectories named after their `importpath` or `importmap` attributes under a `src/` directory. |
| """, |
| ) |
| |
| def _merge_pkg(x, y): |
| x_srcs = {f.path: None for f in x.srcs} |
| x_data = {f.path: None for f in x.data} |
| x_embedsrcs = {f.path: None for f in x.embedsrcs} |
| x.srcs.extend([f for f in y.srcs if f.path not in x_srcs]) |
| x.data.extend([f for f in y.data if f.path not in x_srcs]) |
| x.embedsrcs.extend([f for f in y.embedsrcs if f.path not in x_embedsrcs]) |
| x.pkgs.update(y.pkgs) |
| |
| def _add_manifest_entry(entries, entry_map, inputs, src, dst): |
| if dst in entry_map: |
| if entry_map[dst] != src.path: |
| fail("{}: references multiple files ({} and {})".format(dst, entry_map[dst], src.path)) |
| return |
| entries.append(struct(src = src.path, dst = dst)) |
| entry_map[dst] = src.path |
| inputs.append(src) |