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)
View as plain text