aboutsummaryrefslogtreecommitdiff
path: root/go/private/rules/binary.bzl
blob: 51530b04ca0160c614196f5a46ed35f83f635321 (plain)
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
# Copyright 2014 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

load(
    "//go/private:context.bzl",
    "go_context",
)
load(
    "//go/private:common.bzl",
    "asm_exts",
    "cgo_exts",
    "go_exts",
)
load(
    "//go/private:go_toolchain.bzl",
    "GO_TOOLCHAIN",
)
load(
    "//go/private:providers.bzl",
    "GoLibrary",
    "GoSDK",
)
load(
    "//go/private/rules:transition.bzl",
    "go_transition",
)
load(
    "//go/private:mode.bzl",
    "LINKMODES_EXECUTABLE",
    "LINKMODE_C_ARCHIVE",
    "LINKMODE_C_SHARED",
    "LINKMODE_NORMAL",
    "LINKMODE_PLUGIN",
    "LINKMODE_SHARED",
)

_EMPTY_DEPSET = depset([])

def _include_path(hdr):
    if not hdr.root.path:
        fail("Expected hdr to be a generated file, got source file: " + hdr.path)

    root_relative_path = hdr.path[len(hdr.root.path + "/"):]
    if not root_relative_path.startswith("external/"):
        return hdr.root.path

    # All headers should be includeable via a path relative to their repository
    # root, regardless of whether the repository is external or not. If it is,
    # we thus need to append "external/<external repo name>" to the path.
    return "/".join([hdr.root.path] + root_relative_path.split("/")[0:2])

def new_cc_import(
        go,
        hdrs = _EMPTY_DEPSET,
        defines = _EMPTY_DEPSET,
        local_defines = _EMPTY_DEPSET,
        dynamic_library = None,
        static_library = None,
        alwayslink = False,
        linkopts = []):
    return CcInfo(
        compilation_context = cc_common.create_compilation_context(
            defines = defines,
            local_defines = local_defines,
            headers = hdrs,
            includes = depset([_include_path(hdr) for hdr in hdrs.to_list()]),
        ),
        linking_context = cc_common.create_linking_context(
            linker_inputs = depset([
                cc_common.create_linker_input(
                    owner = go.label,
                    libraries = depset([
                        cc_common.create_library_to_link(
                            actions = go.actions,
                            cc_toolchain = go.cgo_tools.cc_toolchain,
                            feature_configuration = go.cgo_tools.feature_configuration,
                            dynamic_library = dynamic_library,
                            static_library = static_library,
                            alwayslink = alwayslink,
                        ),
                    ]),
                    user_link_flags = depset(linkopts),
                ),
            ]),
        ),
    )

def _go_binary_impl(ctx):
    """go_binary_impl emits actions for compiling and linking a go executable."""
    go = go_context(ctx)

    is_main = go.mode.link not in (LINKMODE_SHARED, LINKMODE_PLUGIN)
    library = go.new_library(go, importable = False, is_main = is_main)
    source = go.library_to_source(go, ctx.attr, library, ctx.coverage_instrumented())
    name = ctx.attr.basename
    if not name:
        name = ctx.label.name
    executable = None
    if ctx.attr.out:
        # Use declare_file instead of attr.output(). When users set output files
        # directly, Bazel warns them not to use the same name as the rule, which is
        # the common case with go_binary.
        executable = ctx.actions.declare_file(ctx.attr.out)
    archive, executable, runfiles = go.binary(
        go,
        name = name,
        source = source,
        gc_linkopts = gc_linkopts(ctx),
        version_file = ctx.version_file,
        info_file = ctx.info_file,
        executable = executable,
    )

    providers = [
        library,
        source,
        archive,
        OutputGroupInfo(
            cgo_exports = archive.cgo_exports,
            compilation_outputs = [archive.data.file],
        ),
    ]

    if go.mode.link in LINKMODES_EXECUTABLE:
        env = {}
        for k, v in ctx.attr.env.items():
            env[k] = ctx.expand_location(v, ctx.attr.data)
        providers.append(RunEnvironmentInfo(environment = env))

        # The executable is automatically added to the runfiles.
        providers.append(DefaultInfo(
            files = depset([executable]),
            runfiles = runfiles,
            executable = executable,
        ))
    else:
        # Workaround for https://github.com/bazelbuild/bazel/issues/15043
        # As of Bazel 5.1.1, native rules do not pick up the "files" of a data
        # dependency's DefaultInfo, only the "data_runfiles". Since transitive
        # non-data dependents should not pick up the executable as a runfile
        # implicitly, the deprecated "default_runfiles" and "data_runfiles"
        # constructor parameters have to be used.
        providers.append(DefaultInfo(
            files = depset([executable]),
            default_runfiles = runfiles,
            data_runfiles = runfiles.merge(ctx.runfiles([executable])),
        ))

    # If the binary's linkmode is c-archive or c-shared, expose CcInfo
    if go.cgo_tools and go.mode.link in (LINKMODE_C_ARCHIVE, LINKMODE_C_SHARED):
        cc_import_kwargs = {
            "linkopts": {
                "darwin": [],
                "ios": [],
                "windows": ["-mthreads"],
            }.get(go.mode.goos, ["-pthread"]),
        }
        cgo_exports = archive.cgo_exports.to_list()
        if cgo_exports:
            header = ctx.actions.declare_file("{}.h".format(name))
            ctx.actions.symlink(
                output = header,
                target_file = cgo_exports[0],
            )
            cc_import_kwargs["hdrs"] = depset([header])
        if go.mode.link == LINKMODE_C_SHARED:
            cc_import_kwargs["dynamic_library"] = executable
        elif go.mode.link == LINKMODE_C_ARCHIVE:
            cc_import_kwargs["static_library"] = executable
            cc_import_kwargs["alwayslink"] = True
        ccinfo = new_cc_import(go, **cc_import_kwargs)
        ccinfo = cc_common.merge_cc_infos(
            cc_infos = [ccinfo, source.cc_info],
        )
        providers.append(ccinfo)

    return providers

_go_binary_kwargs = {
    "implementation": _go_binary_impl,
    "attrs": {
        "srcs": attr.label_list(
            allow_files = go_exts + asm_exts + cgo_exts,
            doc = """The list of Go source files that are compiled to create the package.
            Only `.go` and `.s` files are permitted, unless the `cgo`
            attribute is set, in which case,
            `.c .cc .cpp .cxx .h .hh .hpp .hxx .inc .m .mm`
            files are also permitted. Files may be filtered at build time
            using Go [build constraints].
            """,
        ),
        "data": attr.label_list(
            allow_files = True,
            doc = """List of files needed by this rule at run-time. This may include data files
            needed or other programs that may be executed. The [bazel] package may be
            used to locate run files; they may appear in different places depending on the
            operating system and environment. See [data dependencies] for more
            information on data files.
            """,
        ),
        "deps": attr.label_list(
            providers = [GoLibrary],
            doc = """List of Go libraries this package imports directly.
            These may be `go_library` rules or compatible rules with the [GoLibrary] provider.
            """,
            cfg = go_transition,
        ),
        "embed": attr.label_list(
            providers = [GoLibrary],
            doc = """List of Go libraries whose sources should be compiled together with this
            binary's sources. Labels listed here must name `go_library`,
            `go_proto_library`, or other compatible targets with the [GoLibrary] and
            [GoSource] providers. Embedded libraries must all have the same `importpath`,
            which must match the `importpath` for this `go_binary` if one is
            specified. At most one embedded library may have `cgo = True`, and the
            embedding binary may not also have `cgo = True`. See [Embedding] for
            more information.
            """,
            cfg = go_transition,
        ),
        "embedsrcs": attr.label_list(
            allow_files = True,
            doc = """The list of files that may be embedded into the compiled package using
            `//go:embed` directives. All files must be in the same logical directory
            or a subdirectory as source files. All source files containing `//go:embed`
            directives must be in the same logical directory. It's okay to mix static and
            generated source files and static and generated embeddable files.
            """,
        ),
        "env": attr.string_dict(
            doc = """Environment variables to set when the binary is executed with bazel run.
            The values (but not keys) are subject to
            [location expansion](https://docs.bazel.build/versions/main/skylark/macros.html) but not full
            [make variable expansion](https://docs.bazel.build/versions/main/be/make-variables.html).
            """,
        ),
        "importpath": attr.string(
            doc = """The import path of this binary. Binaries can't actually be imported, but this
            may be used by [go_path] and other tools to report the location of source
            files. This may be inferred from embedded libraries.
            """,
        ),
        "gc_goopts": attr.string_list(
            doc = """List of flags to add to the Go compilation command when using the gc compiler.
            Subject to ["Make variable"] substitution and [Bourne shell tokenization].
            """,
        ),
        "gc_linkopts": attr.string_list(
            doc = """List of flags to add to the Go link command when using the gc compiler.
            Subject to ["Make variable"] substitution and [Bourne shell tokenization].
            """,
        ),
        "x_defs": attr.string_dict(
            doc = """Map of defines to add to the go link command.
            See [Defines and stamping] for examples of how to use these.
            """,
        ),
        "basename": attr.string(
            doc = """The basename of this binary. The binary
            basename may also be platform-dependent: on Windows, we add an .exe extension.
            """,
        ),
        "out": attr.string(
            doc = """Sets the output filename for the generated executable. When set, `go_binary`
            will write this file without mode-specific directory prefixes, without
            linkmode-specific prefixes like "lib", and without platform-specific suffixes
            like ".exe". Note that without a mode-specific directory prefix, the
            output file (but not its dependencies) will be invalidated in Bazel's cache
            when changing configurations.
            """,
        ),
        "cgo": attr.bool(
            doc = """If `True`, the package may contain [cgo] code, and `srcs` may contain
            C, C++, Objective-C, and Objective-C++ files and non-Go assembly files.
            When cgo is enabled, these files will be compiled with the C/C++ toolchain
            and included in the package. Note that this attribute does not force cgo
            to be enabled. Cgo is enabled for non-cross-compiling builds when a C/C++
            toolchain is configured.
            """,
        ),
        "cdeps": attr.label_list(
            doc = """The list of other libraries that the c code depends on.
            This can be anything that would be allowed in [cc_library deps]
            Only valid if `cgo` = `True`.
            """,
        ),
        "cppopts": attr.string_list(
            doc = """List of flags to add to the C/C++ preprocessor command.
            Subject to ["Make variable"] substitution and [Bourne shell tokenization].
            Only valid if `cgo` = `True`.
            """,
        ),
        "copts": attr.string_list(
            doc = """List of flags to add to the C compilation command.
            Subject to ["Make variable"] substitution and [Bourne shell tokenization].
            Only valid if `cgo` = `True`.
            """,
        ),
        "cxxopts": attr.string_list(
            doc = """List of flags to add to the C++ compilation command.
            Subject to ["Make variable"] substitution and [Bourne shell tokenization].
            Only valid if `cgo` = `True`.
            """,
        ),
        "clinkopts": attr.string_list(
            doc = """List of flags to add to the C link command.
            Subject to ["Make variable"] substitution and [Bourne shell tokenization].
            Only valid if `cgo` = `True`.
            """,
        ),
        "pure": attr.string(
            default = "auto",
            doc = """Controls whether cgo source code and dependencies are compiled and linked,
            similar to setting `CGO_ENABLED`. May be one of `on`, `off`,
            or `auto`. If `auto`, pure mode is enabled when no C/C++
            toolchain is configured or when cross-compiling. It's usually better to
            control this on the command line with
            `--@io_bazel_rules_go//go/config:pure`. See [mode attributes], specifically
            [pure].
            """,
        ),
        "static": attr.string(
            default = "auto",
            doc = """Controls whether a binary is statically linked. May be one of `on`,
            `off`, or `auto`. Not available on all platforms or in all
            modes. It's usually better to control this on the command line with
            `--@io_bazel_rules_go//go/config:static`. See [mode attributes],
            specifically [static].
            """,
        ),
        "race": attr.string(
            default = "auto",
            doc = """Controls whether code is instrumented for race detection. May be one of
            `on`, `off`, or `auto`. Not available when cgo is
            disabled. In most cases, it's better to control this on the command line with
            `--@io_bazel_rules_go//go/config:race`. See [mode attributes], specifically
            [race].
            """,
        ),
        "msan": attr.string(
            default = "auto",
            doc = """Controls whether code is instrumented for memory sanitization. May be one of
            `on`, `off`, or `auto`. Not available when cgo is
            disabled. In most cases, it's better to control this on the command line with
            `--@io_bazel_rules_go//go/config:msan`. See [mode attributes], specifically
            [msan].
            """,
        ),
        "gotags": attr.string_list(
            doc = """Enables a list of build tags when evaluating [build constraints]. Useful for
            conditional compilation.
            """,
        ),
        "goos": attr.string(
            default = "auto",
            doc = """Forces a binary to be cross-compiled for a specific operating system. It's
            usually better to control this on the command line with `--platforms`.

            This disables cgo by default, since a cross-compiling C/C++ toolchain is
            rarely available. To force cgo, set `pure` = `off`.

            See [Cross compilation] for more information.
            """,
        ),
        "goarch": attr.string(
            default = "auto",
            doc = """Forces a binary to be cross-compiled for a specific architecture. It's usually
            better to control this on the command line with `--platforms`.

            This disables cgo by default, since a cross-compiling C/C++ toolchain is
            rarely available. To force cgo, set `pure` = `off`.

            See [Cross compilation] for more information.
            """,
        ),
        "linkmode": attr.string(
            default = LINKMODE_NORMAL,
            doc = """Determines how the binary should be built and linked. This accepts some of
            the same values as `go build -buildmode` and works the same way.
            <br><br>
            <ul>
            <li>`normal`: Builds a normal executable with position-dependent code.</li>
            <li>`pie`: Builds a position-independent executable.</li>
            <li>`plugin`: Builds a shared library that can be loaded as a Go plugin. Only supported on platforms that support plugins.</li>
            <li>`c-shared`: Builds a shared library that can be linked into a C program.</li>
            <li>`c-archive`: Builds an archive that can be linked into a C program.</li>
            </ul>
            """,
        ),
        "_go_context_data": attr.label(default = "//:go_context_data", cfg = go_transition),
        "_allowlist_function_transition": attr.label(
            default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
        ),
    },
    "toolchains": [GO_TOOLCHAIN],
    "doc": """This builds an executable from a set of source files,
    which must all be in the `main` package. You can run the binary with
    `bazel run`, or you can build it with `bazel build` and run it directly.<br><br>
    ***Note:*** `name` should be the same as the desired name of the generated binary.<br><br>
    **Providers:**
    <ul>
      <li>[GoLibrary]</li>
      <li>[GoSource]</li>
      <li>[GoArchive]</li>
    </ul>
    """,
}

go_binary = rule(executable = True, **_go_binary_kwargs)
go_non_executable_binary = rule(executable = False, **_go_binary_kwargs)

def _go_tool_binary_impl(ctx):
    sdk = ctx.attr.sdk[GoSDK]
    name = ctx.label.name
    if sdk.goos == "windows":
        name += ".exe"

    out = ctx.actions.declare_file(name)
    if sdk.goos == "windows":
        gopath = ctx.actions.declare_directory("gopath")
        gocache = ctx.actions.declare_directory("gocache")
        cmd = "@echo off\nset GOMAXPROCS=1\nset GOCACHE=%cd%\\{gocache}\nset GOPATH=%cd%\\{gopath}\n{go} build -o {out} -trimpath {srcs}".format(
            gopath = gopath.path,
            gocache = gocache.path,
            go = sdk.go.path.replace("/", "\\"),
            out = out.path,
            srcs = " ".join([f.path for f in ctx.files.srcs]),
        )
        bat = ctx.actions.declare_file(name + ".bat")
        ctx.actions.write(
            output = bat,
            content = cmd,
        )
        ctx.actions.run(
            executable = bat,
            inputs = sdk.headers + sdk.tools + sdk.srcs + ctx.files.srcs + [sdk.go],
            outputs = [out, gopath, gocache],
            mnemonic = "GoToolchainBinaryBuild",
        )
    else:
        # Note: GOPATH is needed for Go 1.16.
        cmd = "GOMAXPROCS=1 GOCACHE=$(mktemp -d) GOPATH=$(mktemp -d) {go} build -o {out} -trimpath {srcs}".format(
            go = sdk.go.path,
            out = out.path,
            srcs = " ".join([f.path for f in ctx.files.srcs]),
        )
        ctx.actions.run_shell(
            command = cmd,
            inputs = sdk.headers + sdk.tools + sdk.srcs + sdk.libs + ctx.files.srcs + [sdk.go],
            outputs = [out],
            mnemonic = "GoToolchainBinaryBuild",
        )

    return [DefaultInfo(
        files = depset([out]),
        executable = out,
    )]

go_tool_binary = rule(
    implementation = _go_tool_binary_impl,
    attrs = {
        "srcs": attr.label_list(
            allow_files = True,
            doc = "Source files for the binary. Must be in 'package main'.",
        ),
        "sdk": attr.label(
            mandatory = True,
            providers = [GoSDK],
            doc = "The SDK containing tools and libraries to build this binary",
        ),
    },
    executable = True,
    doc = """Used instead of go_binary for executables used in the toolchain.

go_tool_binary depends on tools and libraries that are part of the Go SDK.
It does not depend on other toolchains. It can only compile binaries that
just have a main package and only depend on the standard library and don't
require build constraints.
""",
)

def gc_linkopts(ctx):
    gc_linkopts = [
        ctx.expand_make_variables("gc_linkopts", f, {})
        for f in ctx.attr.gc_linkopts
    ]
    return gc_linkopts