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)