xref: /aosp_15_r20/external/bazelbuild-rules_go/go/private/tools/path.bzl (revision 9bb1b549b6a84214c53be0924760be030e66b93a)
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:providers.bzl",
17    "GoArchive",
18    "GoPath",
19    "effective_importpath_pkgpath",
20    "get_archive",
21)
22load(
23    "//go/private:common.bzl",
24    "as_iterable",
25    "as_list",
26)
27load(
28    "@bazel_skylib//lib:paths.bzl",
29    "paths",
30)
31
32def _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 = [e.to_json() 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
169go_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
262def _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
271def _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)
279