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