github.com/sercand/please@v13.4.0+incompatible/src/parse/rules/go_rules.build_defs (about) 1 """ Rules to build Go code. 2 3 Go has a strong built-in concept of packages so it's probably a good idea to match Please 4 rules to Go packages. 5 """ 6 7 _GOPATH = ' '.join(['-I %s -I %s/pkg/%s_%s' % (p, p, CONFIG.OS, CONFIG.ARCH) for p in CONFIG.GOPATH.split(':')]) 8 # This links all the .a files up one level. This is necessary for some Go tools to find them. 9 _LINK_PKGS_CMD = ' '.join([ 10 'for FN in `find . -name "*.a" | sort`; do ', 11 'DN=${FN%/*}; BN=${FN##*/}; if [ "${DN##*/}" == "${BN%.a}" ]; then ', 12 'ln -s $TMP_DIR/$FN ${DN%/*}; fi; done' 13 ]) 14 15 16 def go_library(name:str, srcs:list, asm_srcs:list=None, hdrs:list=None, out:str=None, deps:list=[], 17 visibility:list=None, test_only:bool&testonly=False, complete:bool=True, 18 _needs_transitive_deps=False, _all_srcs=False, cover:bool=True, 19 filter_srcs:bool=True, _link_private:bool=False, _link_extra:bool=True): 20 """Generates a Go library which can be reused by other rules. 21 22 Args: 23 name (str): Name of the rule. 24 srcs (list): Go source files to compile. 25 asm_srcs (list): Source files to assemble with `go tool assemble`. 26 hdrs (list): Header files needed for assembly. Has no effect if asm_srcs is not given. 27 out (str): Name of the output library to compile (defaults to name suffixed with .a) 28 deps (list): Dependencies 29 visibility (list): Visibility specification 30 test_only (bool): If True, is only visible to test rules. 31 complete (bool): Indicates whether the library is complete or not (ie. buildable with 32 `go tool build -complete`). In nearly all cases this is True (the main 33 exception being for cgo). 34 cover (bool): Indicates whether this library should be considered for coverage annotations. 35 Libraries are only annotated when using `plz cover` (or `plz build -c cover`), 36 but if this is false they never will be. Can be useful for e.g. third-party 37 code that you never want to be instrumented. 38 filter_srcs (bool): If True, filters source files through Go's standard build constraints. 39 """ 40 out = out or name + '.a' 41 labels = [] 42 src_labels = [] 43 private = out.startswith('_') 44 if _link_private or not private: 45 src_labels = ['link:plz-out/go/src/${PKG}'] 46 if not private: 47 labels = ['link:plz-out/go/pkg/${OS}_${ARCH}/${PKG}'] 48 libname = out[:-2] if len(out) > 2 else out 49 if libname != basename(package_name()) and _link_extra: 50 # Libraries that are in a directory not of their own name will need the sources in 51 # a subdirectory for go to be able to transparently import them. 52 src_labels += [f'link:plz-out/go/src/$PKG/{libname}'] 53 if asm_srcs: 54 lib_rule = go_library( 55 name = f'_{name}#lib', 56 srcs = srcs, 57 deps = deps, 58 test_only = test_only, 59 complete = False, 60 cover = cover, 61 _needs_transitive_deps = _needs_transitive_deps, 62 _all_srcs = _all_srcs, 63 ) 64 asm_rule = build_rule( 65 name = name, 66 tag = 'asm', 67 srcs = { 68 'asm': asm_srcs, 69 'hdrs': hdrs, 70 }, 71 outs = [name + '.o'], 72 building_description = 'Assembling...', 73 cmd = 'eval `$TOOL env` && $TOOL tool asm -trimpath $TMP_DIR -I ${GOROOT}/pkg/include -D GOOS_${OS} -D GOARCH_${ARCH} -o $OUT $SRCS_ASM', 74 tools=[CONFIG.GO_TOOL], 75 test_only = test_only, 76 ) 77 return build_rule( 78 name = name, 79 srcs = { 80 'lib': [lib_rule], 81 'asm': [asm_rule], 82 }, 83 outs=[out], 84 tools=[CONFIG.GO_TOOL], 85 cmd = 'cp $SRCS_LIB $OUT && chmod +w $OUT && $TOOL tool pack r $OUT $SRCS_ASM', 86 visibility = visibility, 87 building_description = 'Packing...', 88 requires = ['go'], 89 labels = labels, 90 provides = {'go': ':' + name, 'go_src': lib_rule}, 91 test_only = test_only, 92 ) 93 94 # go_test and cgo_library need access to the sources as well. 95 src_rule = filegroup( 96 name = name, 97 tag = 'srcs', 98 srcs=srcs, 99 exported_deps=deps, 100 visibility=visibility, 101 requires=['go'], 102 labels = src_labels, 103 test_only=test_only, 104 ) 105 106 tools = { 'go': [CONFIG.GO_TOOL] } 107 if filter_srcs: 108 tools['filter'] = [CONFIG.GO_FILTER_TOOL] 109 110 return build_rule( 111 name=name, 112 srcs=srcs, 113 deps=deps + [src_rule], 114 outs=[out], 115 cmd=_go_library_cmds(complete=complete, all_srcs=_all_srcs, cover=cover, filter_srcs=filter_srcs), 116 visibility=visibility, 117 building_description="Compiling...", 118 requires=['go', 'go_src'] if _all_srcs else ['go'], 119 provides={'go': ':' + name, 'go_src': src_rule}, 120 labels = labels, 121 test_only=test_only, 122 tools=tools, 123 needs_transitive_deps=_needs_transitive_deps, 124 ) 125 126 127 def cgo_library(name:str, srcs:list, go_srcs:list=[], c_srcs:list=[], hdrs:list=[], 128 out:str=None, compiler_flags:list&cflags=[], linker_flags:list&ldflags=[], pkg_config:list=[], 129 subdir:str='', deps:list=[], visibility:list=None, test_only:bool&testonly=False): 130 """Generates a Go library which can be reused by other rules. 131 132 Note that by its nature this is something of a hybrid of Go and C rules. It can depend 133 on C / C++ rules, given the limitations of cgo (i.e. you will have to interact with them 134 through a C interface, although the objects themselves can contain C++). As mentioned 135 below, you will likely be better off wrapping your dependencies into a cc_static_library 136 rule and depending on that rather than depending directly on cc_library rules. 137 138 Note also that this does not honour Go's syntactic comments; you have to explicitly 139 specify which Go files are cgo vs. which are not, as well as C headers & sources and 140 any required cflags or ldflags. 141 142 Args: 143 name (str): Name of the rule. 144 srcs (list): Go source files to compile that have 'import "C"' declarations in them. 145 go_srcs (list): Any Go source files that do *not* have 'import "C"' declarations. 146 c_srcs (list): Any C source files to include. 147 hdrs (list): Any C header files to include. 148 out (str): Name of output file. Defaults to name + '.a'. 149 compiler_flags (list): List of compiler flags to be passed when compiling the C code. 150 linker_flags (list): List of linker flags to be passed when linking a Go binary. 151 pkg_config (list): List of packages to pass to pkg-config. 152 subdir (str): Subdirectory that source files are in. Required if they're not in the 153 current directory. 154 deps (list): Dependencies. Note that if you intend to depend on cc_library rules, 155 you will likely be better off wrapping them into a cc_static_library 156 and depending on that. 157 visibility (list): Visibility specification 158 test_only (bool): If True, is only visible to test rules. 159 """ 160 file_srcs = [src for src in srcs if not src.startswith('/') and not src.startswith(':')] 161 post_build = lambda rule, output: [add_out(rule, 'c' if line.endswith('.c') else 'go', line) for line in output] 162 subdir2 = (subdir + '/') if subdir and not subdir.endswith('/') else subdir 163 164 cgo_rule = build_rule( 165 name = name, 166 tag = 'cgo', 167 srcs = srcs + hdrs, 168 outs = { 169 'go': [subdir2 + src.replace('.go', '.cgo1.go') for src in file_srcs] + [subdir2 + '_cgo_gotypes.go'], 170 'c': [subdir2 + src.replace('.go', '.cgo2.c') for src in file_srcs] + [subdir2 + '_cgo_export.c'], 171 'h': [subdir2 + '_cgo_export.h'], 172 }, 173 cmd = ' && '.join([ 174 (f'OUT_DIR="$TMP_DIR/{subdir}"') if subdir else 'OUT_DIR="$TMP_DIR"', 175 'mkdir -p $OUT_DIR', 176 'cd $PKG_DIR/' + subdir, 177 '$TOOL tool cgo -objdir $OUT_DIR -importpath ${PKG#*src/} *.go', 178 # Remove the .o file which BSD sed gets upset about in the next command 179 'rm -f $OUT_DIR/_cgo_.o $OUT_DIR/_cgo_main.c', 180 # cgo leaves absolute paths in these files which we must get rid of :( 181 'find $OUT_DIR -type f -maxdepth 1 | xargs sed -i -e "s|$TMP_DIR/||g"', 182 'cd $TMP_DIR', 183 f'ls {subdir2}*.c {subdir2}*.go', 184 ]), 185 tools = [CONFIG.GO_TOOL], 186 post_build = post_build if file_srcs != srcs else None, 187 requires = ['go', 'cc_hdrs'], 188 ) 189 190 # Compile the various bits 191 c_rule = c_library( 192 name = f'_{name}#c', 193 srcs = [cgo_rule + '|c'] + c_srcs, 194 hdrs = [cgo_rule + '|h'] + hdrs, 195 compiler_flags = compiler_flags + [ 196 '-Wno-error', 197 '-Wno-unused-parameter', # Generated code doesn't compile clean 198 ], 199 pkg_config_libs = pkg_config, 200 test_only = test_only, 201 deps = deps, 202 ) 203 go_rule = go_library( 204 name = f'_{name}#go', 205 srcs = [cgo_rule + '|go'] + go_srcs, 206 test_only = test_only, 207 complete = False, 208 deps = deps, 209 ) 210 # And finally combine the compiled C code into the Go archive object so go tool link can find it later. 211 return _merge_cgo_obj( 212 name = name, 213 a_rule = f':_{name}#go', 214 o_rule = c_rule, 215 visibility = visibility, 216 test_only = test_only, 217 linker_flags = linker_flags, 218 out = out, 219 labels = ['link:plz-out/go/pkg/%s_%s' % (CONFIG.OS, CONFIG.ARCH)], 220 provides = { 221 'go': ':' + name, 222 'go_src': go_rule, 223 'cgo': c_rule, 224 }, 225 deps = deps, 226 ) 227 228 229 def _merge_cgo_obj(name, a_rule, o_rule=None, visibility=None, test_only=False, tag='', 230 linker_flags:list=[], deps=None, provides=None, out=None, labels:list=[]): 231 """Defines a rule to merge a cgo object into a Go library.""" 232 if o_rule: 233 cmd = 'cp $SRCS_A $OUT && chmod +w $OUT && $TOOLS_AR x $SRCS_O && $TOOLS_GO tool pack r $OUT *.o' 234 else: 235 cmd = 'cp $SRCS_A $OUT && chmod +w $OUT && $TOOLS_AR x $PKG/*#c.a && $TOOLS_GO tool pack r $OUT *.o' 236 237 return build_rule( 238 name = name, 239 tag = tag, 240 srcs = { 241 'a': [a_rule], 242 'o': [o_rule] if o_rule else [], 243 }, 244 outs = [out or name + '.a'], 245 cmd = cmd, 246 tools = { 247 'go': [CONFIG.GO_TOOL], 248 'ar': [CONFIG.AR_TOOL], 249 }, 250 visibility = visibility, 251 test_only = test_only, 252 labels = ['cc:ld:' + flag for flag in linker_flags] + labels, 253 requires = ['go', 'cgo'], 254 provides = provides, 255 deps = deps, 256 ) 257 258 259 def go_binary(name:str, srcs:list=[], asm_srcs:list=[], out:str=None, deps:list=[], 260 visibility:list=None, test_only:bool&testonly=False, static:bool=CONFIG.GO_DEFAULT_STATIC, 261 filter_srcs:bool=True, definitions:str|list|dict=None): 262 """Compiles a Go binary. 263 264 Args: 265 name (str): Name of the rule. 266 srcs (list): Go source files, one of which contains the main function. 267 asm_srcs (list): Assembly source files. 268 out (str): Name of the output file to create. Defaults to the same as `name`. 269 deps (list): Dependencies 270 visibility (list): Visibility specification 271 test_only (bool): If True, is only visible to test rules. 272 static (bool): If True, passes flags to the linker to try to force fully static linking. 273 (specifically `-linkmode external -extldflags static`). 274 Typically this increases size & link time a little but in return the binary 275 has absolutely no external dependencies. 276 Note that it may have negative consequences if the binary contains any cgo 277 (including net/http DNS lookup code potentially). 278 filter_srcs (bool): If True, filters source files through Go's standard build constraints. 279 definitions (str | list | dict): If set to a string, defines importpath.name=value 280 when calling the Go linker. If set to a list, pass each value as a 281 definition to the linker. If set to a dict, each key/value pair is 282 used to contruct the list of definitions passed to the linker. 283 """ 284 lib = go_library( 285 name=f'_{name}#lib', 286 srcs=srcs or [name + '.go'], 287 filter_srcs=filter_srcs, 288 asm_srcs=asm_srcs, 289 deps=deps, 290 test_only=test_only, 291 _link_private = True, 292 _link_extra = False, 293 ) 294 cmds, tools = _go_binary_cmds(static=static, definitions=definitions) 295 return build_rule( 296 name=name, 297 srcs=[lib], 298 deps=deps, 299 outs=[out or name], 300 cmd=cmds, 301 building_description="Linking...", 302 needs_transitive_deps=True, 303 binary=True, 304 output_is_complete=True, 305 test_only=test_only, 306 tools=tools, 307 visibility=visibility, 308 requires=['go'], 309 pre_build=_collect_linker_flags(static), 310 ) 311 312 313 def go_test(name:str, srcs:list, data:list=None, deps:list=[], worker:str='', visibility:list=None, 314 flags:str='', container:bool|dict=False, sandbox:bool=None, cgo:bool=False, 315 external:bool=False, timeout:int=0, flaky:bool|int=0, test_outputs:list=None, 316 labels:list&features&tags=None, size:str=None, static:bool=CONFIG.GO_DEFAULT_STATIC, 317 definitions:str|list|dict=None): 318 """Defines a Go test rule. 319 320 Args: 321 name (str): Name of the rule. 322 srcs (list): Go source files to compile. 323 data (list): Runtime data files for the test. 324 deps (list): Dependencies 325 worker (str): Reference to worker script, A persistent worker process that is used to set up the test. 326 visibility (list): Visibility specification 327 flags (str): Flags to apply to the test invocation. 328 container (bool | dict): True to run this test in a container. 329 sandbox (bool): Sandbox the test on Linux to restrict access to namespaces such as network. 330 cgo (bool): True if this test depends on a cgo_library. 331 external (bool): True if this test is external to the library it's testing, i.e. it uses the 332 feature of Go that allows it to be in the same directory with a _test suffix. 333 timeout (int): Timeout in seconds to allow the test to run for. 334 flaky (int | bool): True to mark the test as flaky, or an integer to specify how many reruns. 335 test_outputs (list): Extra test output files to generate from this test. 336 labels (list): Labels for this rule. 337 size (str): Test size (enormous, large, medium or small). 338 static (bool): If True, passes flags to the linker to try to force fully static linking. 339 (specifically `-linkmode external -extldflags static`). 340 Typically this increases size & link time a little but in return the binary 341 has absolutely no external dependencies. 342 Note that it may have negative consequences if the binary contains any cgo 343 (including net/http DNS lookup code potentially). 344 definitions (str | list | dict): If set to a string, defines importpath.name=value 345 when calling the Go linker. If set to a list, pass each value as a 346 definition to the linker. If set to a dict, each key/value pair is 347 used to contruct the list of definitions passed to the linker. 348 """ 349 timeout, labels = _test_size_and_timeout(size, timeout, labels) 350 # Unfortunately we have to recompile this to build the test together with its library. 351 lib_rule = go_library( 352 name = '_%s#lib' % name, 353 srcs = srcs, 354 out = name + ('_lib.a' if cgo else '.a'), 355 deps = deps, 356 test_only = True, 357 _all_srcs = not external, 358 _link_extra = False, 359 complete = False, 360 ) 361 if cgo: 362 lib_rule = _merge_cgo_obj( 363 name = name, 364 tag = 'cgo', 365 a_rule = lib_rule, 366 visibility = visibility, 367 labels = ['link:plz-out/go/pkg/%s_%s' % (CONFIG.OS, CONFIG.ARCH)], 368 test_only = True, 369 deps = deps, 370 ) 371 main_rule = build_rule( 372 name='_%s#main' % name, 373 srcs=srcs, 374 outs=[name + '_main.go'], 375 deps=deps, 376 cmd={ 377 'dbg': f'$TOOLS_TEST $TOOLS_GO -o $OUT -i "{CONFIG.GO_IMPORT_PATH}" $SRCS', 378 'opt': f'$TOOLS_TEST $TOOLS_GO -o $OUT -i "{CONFIG.GO_IMPORT_PATH}" $SRCS', 379 'cover': f'$TOOLS_TEST $TOOLS_GO -d . -o $OUT -i "{CONFIG.GO_IMPORT_PATH}" $SRCS ', 380 }, 381 needs_transitive_deps=True, # Need all .a files to template coverage variables 382 requires=['go'], 383 test_only=True, 384 tools={ 385 'go': [CONFIG.GO_TOOL], 386 'test': [CONFIG.GO_TEST_TOOL], 387 }, 388 post_build=lambda name, output: _replace_test_package(name, output, static, definitions=definitions, cgo=cgo), 389 ) 390 deps.append(lib_rule) 391 lib_rule = go_library( 392 name='_%s#main_lib' % name, 393 srcs=[main_rule], 394 deps=deps, 395 _needs_transitive_deps=True, # Rather annoyingly this is only needed for coverage 396 test_only=True, 397 ) 398 cmds, tools = _go_binary_cmds(static=static, definitions=definitions, gcov=cgo) 399 400 test_cmd = '$TEST %s 2>&1 | tee $RESULTS_FILE' % flags 401 if worker: 402 test_cmd = f'$(worker {worker}) && {test_cmd} ' 403 deps += [worker] 404 405 return build_rule( 406 name=name, 407 srcs=[lib_rule], 408 data=data, 409 deps=deps, 410 outs=[name], 411 tools=tools, 412 cmd=cmds, 413 test_cmd=test_cmd, 414 visibility=visibility, 415 container=container, 416 test_sandbox=sandbox, 417 test_timeout=timeout, 418 flaky=flaky, 419 test_outputs=test_outputs, 420 requires=['go'], 421 labels=labels, 422 binary=True, 423 test=True, 424 building_description="Compiling...", 425 needs_transitive_deps=True, 426 output_is_complete=True, 427 ) 428 429 430 def cgo_test(name:str, srcs:list, data:list=None, deps:list=None, visibility:list=None, 431 flags:str='', container:bool|dict=False, sandbox:bool=None, timeout:int=0, flaky:bool|int=0, 432 test_outputs:list=None, labels:list&features&tags=None, size:str=None, static:bool=False): 433 """Defines a Go test rule over a cgo_library. 434 435 If the library you are testing is a cgo_library, you must use this instead of go_test. 436 It's ok to depend on a cgo_library though as long as it's not the same package 437 as your test; in that (any any other case of testing a go_library) you must use go_test. 438 439 Args: 440 name (str): Name of the rule. 441 srcs (list): Go source files to compile. 442 data (list): Runtime data files for the test. 443 deps (list): Dependencies 444 visibility (list): Visibility specification 445 flags (str): Flags to apply to the test invocation. 446 container (bool | dict): True to run this test in a container. 447 sandbox (bool): Sandbox the test on Linux to restrict access to namespaces such as network. 448 timeout (int): Timeout in seconds to allow the test to run for. 449 flaky (int | bool): True to mark the test as flaky, or an integer to specify how many reruns. 450 test_outputs (list): Extra test output files to generate from this test. 451 labels (list): Labels for this rule. 452 size (str): Test size (enormous, large, medium or small). 453 static (bool): If True, passes flags to the linker to try to force fully static linking. 454 (specifically `-linkmode external -extldflags static`). 455 Typically this increases size & link time a little but in return the binary 456 has absolutely no external dependencies. 457 It may not be easy to make cgo tests work when linked statically; depending 458 on your toolchain it may not be possible or may fail. 459 """ 460 return go_test( 461 name = name, 462 srcs = srcs, 463 data = data, 464 deps = deps, 465 cgo = True, 466 static = static, 467 visibility = visibility, 468 flags = flags, 469 container = container, 470 sandbox = sandbox, 471 timeout = timeout, 472 flaky = flaky, 473 test_outputs = test_outputs, 474 labels = labels, 475 size = size, 476 ) 477 478 479 def go_get(name:str, get:str|list, repo:str='', deps:list=[], exported_deps:list=None, 480 visibility:list=None, patch:str=None, binary:bool=False, test_only:bool&testonly=False, 481 install:list=None, revision:str|list=None, strip:list=None, hashes:list=None, 482 extra_outs:list=[]): 483 """Defines a dependency on a third-party Go library. 484 485 Note that unlike a normal `go get` call, this does *not* install transitive dependencies. 486 You will need to add those as separate rules; `go list -f '{{.Deps}}' <package>` can be 487 useful to discover what they should be. 488 489 Note also that while a single go_get is sufficient to compile all parts of a library, 490 one may also want separate ones for a binary. Since two rules can't both output the same 491 source files (and you only want to download them once anyway) you should handle that by 492 marking the non-binary rule as a dependency of the binary one - if you don't there may 493 be warnings issued about conflicting output files. 494 495 Args: 496 name (str): Name of the rule 497 get (str): Target to get (eg. "github.com/gorilla/mux"). Can also be a list of multiple in 498 which case they are fetched separately and compiled together, which can be useful 499 for avoiding issues with circular dependencies. 500 repo (str): Location of a Git repository to fetch from. 501 deps (list): Dependencies 502 exported_deps (list): Dependencies to make available to anything using this rule. 503 visibility (list): Visibility specification 504 patch (str): Patch file to apply 505 binary (bool): True if the output of the rule is a binary. 506 test_only (bool): If true this rule will only be visible to tests. 507 install (list): Allows specifying the exact list of packages to install. If this is not passed, 508 the package passed to 'get' is installed. If you pass this for subpackages, you 509 will need to explicitly add an empty string to the list if you want to install 510 the root package from this repo. 511 revision (str): Git hash to check out before building. Only works for git at present, 512 not for other version control systems. 513 strip (list): List of paths to strip from the installed target. 514 hashes (list): List of hashes to verify the downloaded sources against. 515 extra_outs (list): List of additional output files after the compile. 516 """ 517 if isinstance(get, str): 518 get = [get] 519 revision = [revision] 520 tags = ['get'] 521 else: 522 tags = [basename(dirname(g)) for g in get] 523 revision = revision or [None for g in get] 524 all_installs = [] 525 outs = extra_outs 526 provides = None 527 for getone, revision, tag in zip(get, revision, tags): 528 get_rule, getroot = _go_get_download( 529 name = name, 530 tag = tag, 531 get = getone, 532 repo = repo, 533 patch = patch, 534 hashes = hashes, 535 test_only = test_only, 536 revision = revision, 537 strip = strip, 538 ) 539 outs += [getroot] 540 deps += [get_rule] 541 provides = {'go': ':' + name, 'go_src': get_rule} 542 if install: 543 all_installs += [i if i.startswith(getroot) else (getroot + '/' + i) for i in install] 544 else: 545 all_installs += [getone] 546 547 # Now compile it in a separate step. 548 cmd = [ 549 f'export GOPATH="{CONFIG.GOPATH}" GO111MODULE="off"', 550 '$TOOLS_GO install -toolexec "$TOOLS_BUILDID" -gcflags "-trimpath $TMP_DIR" -asmflags "-trimpath $TMP_DIR" ' + ' '.join(all_installs or install), 551 ] 552 if package_name(): 553 cmd += [ 554 # The outputs tend to end up in subdirectories (Go seems to match them to the location the source came from) 555 'rm -rf bin' if binary else 'rm -rf pkg', 556 'mv $PKG_DIR/bin .' if binary else 'mv $PKG_DIR/pkg .', 557 ] 558 if binary: 559 outs = ['bin/' + name] 560 else: 561 outs = [f'pkg/{CONFIG.OS}_{CONFIG.ARCH}/{out}' for out in outs] 562 # Outputs are created one directory down from where we want them. 563 # For most it doesn't matter but the top-level one will get lost. 564 cmd += [f' if [ -f {out}.a ]; then mkdir -p {out} && mv {out}.a {out}; fi' for out in outs] 565 return build_rule( 566 name = name, 567 outs = outs, 568 deps = deps, 569 exported_deps = exported_deps, 570 tools = { 571 'go': [CONFIG.GO_TOOL], 572 'buildid': [CONFIG.BUILDID_TOOL], 573 }, 574 visibility = visibility, 575 building_description = 'Compiling...', 576 cmd = ' && '.join(cmd), 577 binary = binary, 578 requires = ['go'], 579 test_only = test_only, 580 labels = ['link:plz-out/go'], 581 sandbox = False, 582 needs_transitive_deps = True, 583 provides = provides, 584 ) 585 586 587 def _go_get_download(name:str, tag:str, get:str, repo:str='', patch:str=None, hashes:list=None, 588 test_only:bool&testonly=False, revision:str=None, strip:list=None, 589 labels:list=[]): 590 if hashes and not revision: 591 log.warning("You shouldn't specify hashes on go_get without specifying revision as well") 592 labels = [f'go_get:{get}@{revision}' if revision else f'go_get:{get}'] 593 getroot = get[:-4] if get.endswith('/...') else get 594 subdir = 'src/' + getroot 595 596 # Some special optimisation for github, which lets us download zipfiles at a particular sha instead of 597 # cloning the whole repo. Obviously that is a lot faster than cloning entire repos. 598 if repo.startswith('github.com') and revision: 599 cmd, get_deps, tools = _go_github_repo_cmd(name, getroot, repo, revision) 600 elif get.startswith('github.com') and revision: 601 cmd, get_deps, tools = _go_github_repo_cmd(name, getroot, getroot, revision) 602 elif get.startswith('golang.org/x/') and revision and not repo: 603 # We know about these guys... 604 cmd, get_deps, tools = _go_github_repo_cmd(name, getroot, 'github.com/golang/' + getroot[len('golang.org/x/'):], revision) 605 else: 606 get_deps = [] 607 if repo: 608 # we've been told exactly where to get the source from. 609 cmd = [f'git clone {repo} src/{getroot}'] 610 else: 611 # Ultimate fallback to go get. 612 # This has some more restrictions than the above (e.g. go get won't fetch a directory 613 # with no Go files in it, even if passed -d). 614 cmd = [ 615 'rm -rf src pkg', 616 'export GOPATH="$TMP_DIR" GO111MODULE="off"', 617 '$TOOL get -d ' + get, 618 ] 619 if revision: 620 # Annoyingly -C does not work on git checkout :( 621 cmd.append(f'(cd {subdir} && git checkout -q {revision})') 622 cmd.append('find . -name .git | xargs rm -rf') 623 tools = [CONFIG.GO_TOOL] 624 if patch: 625 cmd.append(f'patch -s -d {subdir} -p1 < $TMP_DIR/$SRCS_PATCH') 626 if strip: 627 cmd += [f'rm -rf {subdir}/{s}' for s in strip] 628 return build_rule( 629 name = name, 630 tag = tag, 631 srcs = { 632 'patch': [patch], 633 'get': get_deps, 634 }, 635 outs = [subdir], 636 tools = tools, 637 building_description = 'Fetching...', 638 cmd = ' && '.join(cmd), 639 requires = ['go'], 640 test_only = test_only, 641 labels = labels + ['link:plz-out/go'], 642 hashes = hashes, 643 sandbox = False, 644 ), getroot 645 646 647 def _go_github_repo_cmd(name, get, repo, revision): 648 """Returns a partial command to fetch a Go repo from Github.""" 649 parts = get.split('/') 650 out = '/'.join(parts[:3]) 651 if repo.count('/') >= 2: 652 parts = repo.split('/') 653 repo = '/'.join(parts[:3]) 654 dest = parts[2] 655 else: 656 dest = parts[-1] 657 remote_rule = remote_file( 658 name = name, 659 _tag = 'download', 660 url = f'https://{repo}/archive/{revision}.zip', 661 out = name + '.zip', 662 ) 663 return [ 664 'rm -rf src/' + out, 665 '$TOOL x $SRCS_GET', 666 f'mv {dest}*/ src/{out}', 667 ], [remote_rule], [CONFIG.JARCAT_TOOL] 668 669 670 def _replace_test_package(name, output, static, definitions, cgo): 671 """Post-build function, called after we template the main function. 672 673 The purpose is to replace the real library with the specific one we've 674 built for this test which has the actual test functions in it. 675 """ 676 if not name.endswith('#main') or not name.startswith('_'): 677 raise ValueError('unexpected rule name: ' + name) 678 lib = name[:-5] + '#main_lib' 679 new_name = name[1:-5] 680 for line in output: 681 if line.startswith('Package: '): 682 ldflags, pkg_config = _get_ldflags_and_pkgconfig(name) 683 pkg_name = line[9:] 684 name_changed = pkg_name != new_name 685 if name_changed or ldflags or pkg_config: # Might not be necessary if names match already. 686 binary_cmds, _ = _go_binary_cmds(static=static, ldflags=ldflags, pkg_config=pkg_config, definitions, cgo) 687 if name_changed: 688 for k, v in binary_cmds.items(): 689 set_command(new_name, k, f'mv -f $PKG_DIR/{new_name}.a $PKG_DIR/{pkg_name}.a && {v}') 690 else: 691 for k, v in binary_cmds.items(): 692 set_command(new_name, k, v) 693 if name_changed: 694 for k, v in _go_library_cmds().items(): 695 set_command(lib, k, f'mv -f $PKG_DIR/{new_name}.a $PKG_DIR/{pkg_name}.a && {v}') 696 697 698 def _go_library_cmds(complete=True, all_srcs=False, cover=True, filter_srcs=True): 699 """Returns the commands to run for building a Go library.""" 700 filter_cmd = 'export SRCS="$(${TOOLS_FILTER} ${SRCS})"; ' if filter_srcs else '' 701 # Invokes the Go compiler. 702 complete_flag = '-complete ' if complete else '' 703 compile_cmd = '$TOOLS_GO tool compile -trimpath $TMP_DIR %s%s -pack -o $OUT ' % (complete_flag, _GOPATH) 704 # Annotates files for coverage. 705 cover_cmd = 'for SRC in $SRCS; do BN=$(basename $SRC); go tool cover -mode=set -var=GoCover_${BN//[.-]/_} $SRC > _tmp.go && mv -f _tmp.go $SRC; done' 706 prefix = ('export SRCS="$PKG_DIR/*.go"; ' + _LINK_PKGS_CMD) if all_srcs else _LINK_PKGS_CMD 707 prefix += _go_import_path_cmd(CONFIG.GO_IMPORT_PATH) 708 cmds = { 709 'dbg': f'{prefix}; {filter_cmd}{compile_cmd} -N -l $SRCS', 710 'opt': f'{prefix}; {filter_cmd}{compile_cmd} $SRCS', 711 } 712 if cover: 713 cmds['cover'] = f'{prefix}; {filter_cmd}{cover_cmd} && {compile_cmd} $SRCS' 714 return cmds 715 716 717 def _go_binary_cmds(static=False, ldflags='', pkg_config='', definitions=None, gcov=False): 718 """Returns the commands to run for linking a Go binary.""" 719 _link_cmd = '$TOOLS_GO tool link -tmpdir $TMP_DIR -extld $TOOLS_LD %s -o ${OUT} ' % _GOPATH.replace('-I ', '-L ') 720 prefix = _LINK_PKGS_CMD + _go_import_path_cmd(CONFIG.GO_IMPORT_PATH) 721 722 linkerdefs = [] 723 if definitions is None: 724 pass 725 elif isinstance(definitions, str): 726 linkerdefs += [f'{definitions}'] 727 elif isinstance(definitions, list): 728 linkerdefs += [f'{linkerdef}' for linkerdef in definitions] 729 elif isinstance(definitions, dict): 730 linkerdefs = [k if v is None else f'{k}={v}' for k, v in sorted(definitions.items())] 731 732 defs = ' '.join([f'-X "{linkerdef}"' for linkerdef in linkerdefs]) 733 734 if static: 735 flags = f'-linkmode external -extldflags "-static {ldflags} {pkg_config}"' 736 elif ldflags or pkg_config: 737 flags = f'-extldflags "{ldflags} {pkg_config}"' 738 else: 739 flags = f'' 740 741 if len(defs) > 0: 742 flags += defs 743 744 cmds = { 745 'dbg': f'{prefix} && {_link_cmd} {flags} $SRCS', 746 'opt': f'{prefix} && {_link_cmd} {flags} -s -w $SRCS', 747 } 748 if gcov and CONFIG.CPP_COVERAGE: 749 cmds['cover'] = f'{prefix} && {_link_cmd} {flags} -extldflags="-lgcov" $SRCS' 750 751 return cmds, { 752 'go': [CONFIG.GO_TOOL], 753 'ld': [CONFIG.LD_TOOL if CONFIG.LINK_WITH_LD_TOOL else CONFIG.CC_TOOL], 754 } 755 756 757 def _go_import_path_cmd(import_path): 758 """Returns a partial command which is used for setting up the Go import path.""" 759 if not import_path: 760 return '' 761 elif import_path.startswith('/'): 762 raise ConfigError('GO_IMPORT_PATH cannot start with a /') 763 elif '/' in import_path: 764 return ' && mkdir -p %s && ln -s $TMP_DIR %s' % (dirname(import_path), import_path) 765 else: 766 return ' && ln -s $TMP_DIR ' + import_path 767 768 769 def _collect_linker_flags(static): 770 """Returns a pre-build function to apply transitive linker flags to a go_binary rule.""" 771 def collect_linker_flags(name): 772 ldflags, pkg_config = _get_ldflags_and_pkgconfig(name) 773 if ldflags or pkg_config: 774 cmds, _ = _go_binary_cmds(static=static, ldflags=ldflags, pkg_config=pkg_config) 775 for k, v in cmds.items(): 776 set_command(name, k, v) 777 return collect_linker_flags 778 779 780 def _get_ldflags_and_pkgconfig(name): 781 """Returns the ldflags and pkg-config invocations for a target.""" 782 labels = get_labels(name, 'cc:') 783 ldflags = ' '.join([l[3:] for l in labels if l.startswith('ld:')]) 784 pkg_config = ' '.join([l[3:] for l in labels if l.startswith('pc:')]) 785 return (ldflags, f'`pkg-config --libs {pkg_config}`') if pkg_config else (ldflags, '')