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