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