xref: /aosp_15_r20/external/starlark-go/syntax/testdata/scan.star (revision 4947cdc739c985f6d86941e22894f5cefe7c9e9a)
1# Copyright 2014 The Bazel Authors. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#    http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15# (From https://github.com/bazelbuild/rules_go/blob/master/go/def.bzl@a6f9d0c)
16
17load("//go/private:repositories.bzl", "go_repositories")
18load("//go/private:go_repository.bzl", "go_repository", "new_go_repository")
19load("//go/private:go_prefix.bzl", "go_prefix")
20load("//go/private:json.bzl", "json_marshal")
21
22"""These are bare-bones Go rules.
23
24In order of priority:
25
26- BUILD file must be written by hand.
27
28- No support for SWIG
29
30- No test sharding or test XML.
31
32"""
33
34_DEFAULT_LIB = "go_default_library"
35
36_VENDOR_PREFIX = "/vendor/"
37
38go_filetype = FileType([
39    ".go",
40    ".s",
41    ".S",
42    ".h",  # may be included by .s
43])
44
45# be consistent to cc_library.
46hdr_exts = [
47    ".h",
48    ".hh",
49    ".hpp",
50    ".hxx",
51    ".inc",
52]
53
54cc_hdr_filetype = FileType(hdr_exts)
55
56# Extensions of files we can build with the Go compiler or with cc_library.
57# This is a subset of the extensions recognized by go/build.
58cgo_filetype = FileType([
59    ".go",
60    ".c",
61    ".cc",
62    ".cxx",
63    ".cpp",
64    ".s",
65    ".S",
66    ".h",
67    ".hh",
68    ".hpp",
69    ".hxx",
70])
71
72################
73
74def go_environment_vars(ctx):
75    """Return a map of environment variables for use with actions, based on
76    the arguments. Uses the ctx.fragments.cpp.cpu attribute, if present,
77    and picks a default of target_os="linux" and target_arch="amd64"
78    otherwise.
79
80    Args:
81      The starlark Context.
82
83    Returns:
84      A dict of environment variables for running Go tool commands that build for
85      the target OS and architecture.
86    """
87    default_toolchain = {"GOOS": "linux", "GOARCH": "amd64"}
88    bazel_to_go_toolchain = {
89        "k8": {"GOOS": "linux", "GOARCH": "amd64"},
90        "piii": {"GOOS": "linux", "GOARCH": "386"},
91        "darwin": {"GOOS": "darwin", "GOARCH": "amd64"},
92        "darwin_x86_64": {"GOOS": "darwin", "GOARCH": "amd64"},
93        "freebsd": {"GOOS": "freebsd", "GOARCH": "amd64"},
94        "armeabi-v7a": {"GOOS": "linux", "GOARCH": "arm"},
95        "arm": {"GOOS": "linux", "GOARCH": "arm"},
96    }
97    env = {}
98    if hasattr(ctx.file, "go_tool"):
99        env["GOROOT"] = ctx.file.go_tool.dirname + "/.."
100    env.update(bazel_to_go_toolchain.get(ctx.fragments.cpp.cpu, default_toolchain))
101    return env
102
103def _is_darwin_cpu(ctx):
104    cpu = ctx.fragments.cpp.cpu
105    return cpu == "darwin" or cpu == "darwin_x86_64"
106
107def _emit_generate_params_action(cmds, ctx, fn):
108    cmds_all = [
109        # Use bash explicitly. /bin/sh is default, and it may be linked to a
110        # different shell, e.g., /bin/dash on Ubuntu.
111        "#!/bin/bash",
112        "set -e",
113    ]
114    cmds_all += cmds
115    cmds_all_str = "\n".join(cmds_all) + "\n"
116    f = ctx.new_file(ctx.configuration.bin_dir, fn)
117    ctx.file_action(
118        output = f,
119        content = cmds_all_str,
120        executable = True,
121    )
122    return f
123
124def _emit_go_asm_action(ctx, source, hdrs, out_obj):
125    """Construct the command line for compiling Go Assembly code.
126    Constructs a symlink tree to accomodate for workspace name.
127    Args:
128      ctx: The starlark Context.
129      source: a source code artifact
130      hdrs: list of .h files that may be included
131      out_obj: the artifact (configured target?) that should be produced
132    """
133    params = {
134        "go_tool": ctx.file.go_tool.path,
135        "includes": [f.dirname for f in hdrs] + [ctx.file.go_include.path],
136        "source": source.path,
137        "out": out_obj.path,
138    }
139
140    inputs = hdrs + ctx.files.toolchain + [source]
141    ctx.action(
142        inputs = inputs,
143        outputs = [out_obj],
144        mnemonic = "GoAsmCompile",
145        executable = ctx.executable._asm,
146        arguments = [json_marshal(params)],
147    )
148
149def _go_importpath(ctx):
150    """Returns the expected importpath of the go_library being built.
151
152    Args:
153      ctx: The starlark Context
154
155    Returns:
156      Go importpath of the library
157    """
158    path = ctx.attr.importpath
159    if path != "":
160        return path
161    path = ctx.attr.go_prefix.go_prefix
162    if path.endswith("/"):
163        path = path[:-1]
164    if ctx.label.package:
165        path += "/" + ctx.label.package
166    if ctx.label.name != _DEFAULT_LIB:
167        path += "/" + ctx.label.name
168    if path.rfind(_VENDOR_PREFIX) != -1:
169        path = path[len(_VENDOR_PREFIX) + path.rfind(_VENDOR_PREFIX):]
170    if path[0] == "/":
171        path = path[1:]
172    return path
173
174def _emit_go_compile_action(ctx, sources, deps, libpaths, out_object, gc_goopts):
175    """Construct the command line for compiling Go code.
176
177    Args:
178      ctx: The starlark Context.
179      sources: an iterable of source code artifacts (or CTs? or labels?)
180      deps: an iterable of dependencies. Each dependency d should have an
181        artifact in d.transitive_go_libraries representing all imported libraries.
182      libpaths: the set of paths to search for imported libraries.
183      out_object: the object file that should be produced
184      gc_goopts: additional flags to pass to the compiler.
185    """
186    if ctx.coverage_instrumented():
187        sources = _emit_go_cover_action(ctx, sources)
188
189    # Compile filtered files.
190    args = [
191        "-cgo",
192        ctx.file.go_tool.path,
193        "tool",
194        "compile",
195        "-o",
196        out_object.path,
197        "-trimpath",
198        "-abs-.",
199        "-I",
200        "-abs-.",
201    ]
202    inputs = depset(sources + ctx.files.toolchain)
203    for dep in deps:
204        inputs += dep.transitive_go_libraries
205    for path in libpaths:
206        args += ["-I", path]
207    args += gc_goopts + [("" if i.basename.startswith("_cgo") else "-filter-") + i.path for i in sources]
208    ctx.action(
209        inputs = list(inputs),
210        outputs = [out_object],
211        mnemonic = "GoCompile",
212        executable = ctx.executable._filter_exec,
213        arguments = args,
214        env = go_environment_vars(ctx),
215    )
216
217    return sources
218
219def _emit_go_pack_action(ctx, out_lib, objects):
220    """Construct the command line for packing objects together.
221
222    Args:
223      ctx: The starlark Context.
224      out_lib: the archive that should be produced
225      objects: an iterable of object files to be added to the output archive file.
226    """
227    ctx.action(
228        inputs = objects + ctx.files.toolchain,
229        outputs = [out_lib],
230        mnemonic = "GoPack",
231        executable = ctx.file.go_tool,
232        arguments = ["tool", "pack", "c", out_lib.path] + [a.path for a in objects],
233        env = go_environment_vars(ctx),
234    )
235
236def _emit_go_cover_action(ctx, sources):
237    """Construct the command line for test coverage instrument.
238
239    Args:
240      ctx: The starlark Context.
241      sources: an iterable of Go source files.
242
243    Returns:
244      A list of Go source code files which might be coverage instrumented.
245    """
246    outputs = []
247
248    # TODO(linuxerwang): make the mode configurable.
249    count = 0
250
251    for src in sources:
252        if not src.path.endswith(".go") or src.path.endswith("_test.go"):
253            outputs += [src]
254            continue
255
256        cover_var = "GoCover_%d" % count
257        out = ctx.new_file(src, src.basename[:-3] + "_" + cover_var + ".cover.go")
258        outputs += [out]
259        ctx.action(
260            inputs = [src] + ctx.files.toolchain,
261            outputs = [out],
262            mnemonic = "GoCover",
263            executable = ctx.file.go_tool,
264            arguments = ["tool", "cover", "--mode=set", "-var=%s" % cover_var, "-o", out.path, src.path],
265            env = go_environment_vars(ctx),
266        )
267        count += 1
268
269    return outputs
270
271def go_library_impl(ctx):
272    """Implements the go_library() rule."""
273
274    sources = depset(ctx.files.srcs)
275    go_srcs = depset([s for s in sources if s.basename.endswith(".go")])
276    asm_srcs = [s for s in sources if s.basename.endswith(".s") or s.basename.endswith(".S")]
277    asm_hdrs = [s for s in sources if s.basename.endswith(".h")]
278    deps = ctx.attr.deps
279    dep_runfiles = [d.data_runfiles for d in deps]
280
281    cgo_object = None
282    if hasattr(ctx.attr, "cgo_object"):
283        cgo_object = ctx.attr.cgo_object
284
285    if ctx.attr.library:
286        go_srcs += ctx.attr.library.go_sources
287        asm_srcs += ctx.attr.library.asm_sources
288        asm_hdrs += ctx.attr.library.asm_headers
289        deps += ctx.attr.library.direct_deps
290        dep_runfiles += [ctx.attr.library.data_runfiles]
291        if ctx.attr.library.cgo_object:
292            if cgo_object:
293                fail("go_library %s cannot have cgo_object because the package " +
294                     "already has cgo_object in %s" % (
295                         ctx.label.name,
296                         ctx.attr.library.name,
297                     ))
298            cgo_object = ctx.attr.library.cgo_object
299    if not go_srcs:
300        fail("may not be empty", "srcs")
301
302    transitive_cgo_deps = depset([], order = "topological")
303    if cgo_object:
304        dep_runfiles += [cgo_object.data_runfiles]
305        transitive_cgo_deps += cgo_object.cgo_deps
306
307    extra_objects = [cgo_object.cgo_obj] if cgo_object else []
308    for src in asm_srcs:
309        obj = ctx.new_file(src, "%s.dir/%s.o" % (ctx.label.name, src.basename[:-2]))
310        _emit_go_asm_action(ctx, src, asm_hdrs, obj)
311        extra_objects += [obj]
312
313    lib_name = _go_importpath(ctx) + ".a"
314    out_lib = ctx.new_file(lib_name)
315    out_object = ctx.new_file(ctx.label.name + ".o")
316    search_path = out_lib.path[:-len(lib_name)]
317    gc_goopts = _gc_goopts(ctx)
318    transitive_go_libraries = depset([out_lib])
319    transitive_go_library_paths = depset([search_path])
320    for dep in deps:
321        transitive_go_libraries += dep.transitive_go_libraries
322        transitive_cgo_deps += dep.transitive_cgo_deps
323        transitive_go_library_paths += dep.transitive_go_library_paths
324
325    go_srcs = _emit_go_compile_action(
326        ctx,
327        sources = go_srcs,
328        deps = deps,
329        libpaths = transitive_go_library_paths,
330        out_object = out_object,
331        gc_goopts = gc_goopts,
332    )
333    _emit_go_pack_action(ctx, out_lib, [out_object] + extra_objects)
334
335    dylibs = []
336    if cgo_object:
337        dylibs += [d for d in cgo_object.cgo_deps if d.path.endswith(".so")]
338
339    runfiles = ctx.runfiles(files = dylibs, collect_data = True)
340    for d in dep_runfiles:
341        runfiles = runfiles.merge(d)
342
343    return struct(
344        label = ctx.label,
345        files = depset([out_lib]),
346        runfiles = runfiles,
347        go_sources = go_srcs,
348        asm_sources = asm_srcs,
349        asm_headers = asm_hdrs,
350        cgo_object = cgo_object,
351        direct_deps = ctx.attr.deps,
352        transitive_cgo_deps = transitive_cgo_deps,
353        transitive_go_libraries = transitive_go_libraries,
354        transitive_go_library_paths = transitive_go_library_paths,
355        gc_goopts = gc_goopts,
356    )
357
358def _c_linker_options(ctx, blocklist = []):
359    """Extracts flags to pass to $(CC) on link from the current context
360
361    Args:
362      ctx: the current context
363      blocklist: Any flags starts with any of these prefixes are filtered out from
364        the return value.
365
366    Returns:
367      A list of command line flags
368    """
369    cpp = ctx.fragments.cpp
370    features = ctx.features
371    options = cpp.compiler_options(features)
372    options += cpp.unfiltered_compiler_options(features)
373    options += cpp.link_options
374    options += cpp.mostly_static_link_options(ctx.features, False)
375    filtered = []
376    for opt in options:
377        if any([opt.startswith(prefix) for prefix in blocklist]):
378            continue
379        filtered.append(opt)
380    return filtered
381
382def _gc_goopts(ctx):
383    gc_goopts = [
384        ctx.expand_make_variables("gc_goopts", f, {})
385        for f in ctx.attr.gc_goopts
386    ]
387    if ctx.attr.library:
388        gc_goopts += ctx.attr.library.gc_goopts
389    return gc_goopts
390
391def _gc_linkopts(ctx):
392    gc_linkopts = [
393        ctx.expand_make_variables("gc_linkopts", f, {})
394        for f in ctx.attr.gc_linkopts
395    ]
396    for k, v in ctx.attr.x_defs.items():
397        gc_linkopts += ["-X", "%s='%s'" % (k, v)]
398    return gc_linkopts
399
400def _extract_extldflags(gc_linkopts, extldflags):
401    """Extracts -extldflags from gc_linkopts and combines them into a single list.
402
403    Args:
404      gc_linkopts: a list of flags passed in through the gc_linkopts attributes.
405        ctx.expand_make_variables should have already been applied.
406      extldflags: a list of flags to be passed to the external linker.
407
408    Return:
409      A tuple containing the filtered gc_linkopts with external flags removed,
410      and a combined list of external flags.
411    """
412    filtered_gc_linkopts = []
413    is_extldflags = False
414    for opt in gc_linkopts:
415        if is_extldflags:
416            is_extldflags = False
417            extldflags += [opt]
418        elif opt == "-extldflags":
419            is_extldflags = True
420        else:
421            filtered_gc_linkopts += [opt]
422    return filtered_gc_linkopts, extldflags
423
424def _emit_go_link_action(
425        ctx,
426        transitive_go_library_paths,
427        transitive_go_libraries,
428        cgo_deps,
429        libs,
430        executable,
431        gc_linkopts):
432    """Sets up a symlink tree to libraries to link together."""
433    config_strip = len(ctx.configuration.bin_dir.path) + 1
434    pkg_depth = executable.dirname[config_strip:].count("/") + 1
435
436    ld = "%s" % ctx.fragments.cpp.compiler_executable
437    extldflags = _c_linker_options(ctx) + [
438        "-Wl,-rpath,$ORIGIN/" + ("../" * pkg_depth),
439    ]
440    for d in cgo_deps:
441        if d.basename.endswith(".so"):
442            short_dir = d.dirname[len(d.root.path):]
443            extldflags += ["-Wl,-rpath,$ORIGIN/" + ("../" * pkg_depth) + short_dir]
444    gc_linkopts, extldflags = _extract_extldflags(gc_linkopts, extldflags)
445
446    link_cmd = [
447        ctx.file.go_tool.path,
448        "tool",
449        "link",
450        "-L",
451        ".",
452    ]
453    for path in transitive_go_library_paths:
454        link_cmd += ["-L", path]
455    link_cmd += [
456        "-o",
457        executable.path,
458    ] + gc_linkopts + ['"${STAMP_XDEFS[@]}"']
459
460    # workaround for a bug in ld(1) on Mac OS X.
461    # http://lists.apple.com/archives/Darwin-dev/2006/Sep/msg00084.html
462    # TODO(yugui) Remove this workaround once rules_go stops supporting XCode 7.2
463    # or earlier.
464    if not _is_darwin_cpu(ctx):
465        link_cmd += ["-s"]
466
467    link_cmd += [
468        "-extld",
469        ld,
470        "-extldflags",
471        "'%s'" % " ".join(extldflags),
472    ] + [lib.path for lib in libs]
473
474    # Avoided -s on OSX but but it requires dsymutil to be on $PATH.
475    # TODO(yugui) Remove this workaround once rules_go stops supporting XCode 7.2
476    # or earlier.
477    cmds = ["export PATH=$PATH:/usr/bin"]
478
479    cmds += [
480        "STAMP_XDEFS=()",
481    ]
482
483    stamp_inputs = []
484    if ctx.attr.linkstamp:
485        # read workspace status files, converting "KEY value" lines
486        # to "-X $linkstamp.KEY=value" arguments to the go linker.
487        stamp_inputs = [ctx.info_file, ctx.version_file]
488        for f in stamp_inputs:
489            cmds += [
490                "while read -r key value || [[ -n $key ]]; do",
491                "  STAMP_XDEFS+=(-X \"%s.$key=$value\")" % ctx.attr.linkstamp,
492                "done < " + f.path,
493            ]
494
495    cmds += [" ".join(link_cmd)]
496
497    f = _emit_generate_params_action(cmds, ctx, lib.basename + ".GoLinkFile.params")
498
499    ctx.action(
500        inputs = [f] + (list(transitive_go_libraries) + [lib] + list(cgo_deps) +
501                        ctx.files.toolchain + ctx.files._crosstool) + stamp_inputs,
502        outputs = [executable],
503        command = f.path,
504        mnemonic = "GoLink",
505        env = go_environment_vars(ctx),
506    )
507
508def go_binary_impl(ctx):
509    """go_binary_impl emits actions for compiling and linking a go executable."""
510    lib_result = go_library_impl(ctx)
511    _emit_go_link_action(
512        ctx,
513        transitive_go_libraries = lib_result.transitive_go_libraries,
514        transitive_go_library_paths = lib_result.transitive_go_library_paths,
515        cgo_deps = lib_result.transitive_cgo_deps,
516        libs = lib_result.files,
517        executable = ctx.outputs.executable,
518        gc_linkopts = _gc_linkopts(ctx),
519    )
520
521    return struct(
522        files = depset([ctx.outputs.executable]),
523        runfiles = lib_result.runfiles,
524        cgo_object = lib_result.cgo_object,
525    )
526
527def go_test_impl(ctx):
528    """go_test_impl implements go testing.
529
530    It emits an action to run the test generator, and then compiles the
531    test into a binary."""
532
533    lib_result = go_library_impl(ctx)
534    main_go = ctx.new_file(ctx.label.name + "_main_test.go")
535    main_object = ctx.new_file(ctx.label.name + "_main_test.o")
536    main_lib = ctx.new_file(ctx.label.name + "_main_test.a")
537    go_import = _go_importpath(ctx)
538
539    cmds = [
540        "UNFILTERED_TEST_FILES=(%s)" %
541        " ".join(["'%s'" % f.path for f in lib_result.go_sources]),
542        "FILTERED_TEST_FILES=()",
543        "while read -r line; do",
544        '  if [ -n "$line" ]; then',
545        '    FILTERED_TEST_FILES+=("$line")',
546        "  fi",
547        'done < <(\'%s\' -cgo "${UNFILTERED_TEST_FILES[@]}")' %
548        ctx.executable._filter_tags.path,
549        " ".join([
550            "'%s'" % ctx.executable.test_generator.path,
551            "--package",
552            go_import,
553            "--output",
554            "'%s'" % main_go.path,
555            '"${FILTERED_TEST_FILES[@]}"',
556        ]),
557    ]
558    f = _emit_generate_params_action(
559        cmds,
560        ctx,
561        ctx.label.name + ".GoTestGenTest.params",
562    )
563    inputs = (list(lib_result.go_sources) + list(ctx.files.toolchain) +
564              [f, ctx.executable._filter_tags, ctx.executable.test_generator])
565    ctx.action(
566        inputs = inputs,
567        outputs = [main_go],
568        command = f.path,
569        mnemonic = "GoTestGenTest",
570        env = dict(go_environment_vars(ctx), RUNDIR = ctx.label.package),
571    )
572
573    _emit_go_compile_action(
574        ctx,
575        sources = depset([main_go]),
576        deps = ctx.attr.deps + [lib_result],
577        libpaths = lib_result.transitive_go_library_paths,
578        out_object = main_object,
579        gc_goopts = _gc_goopts(ctx),
580    )
581    _emit_go_pack_action(ctx, main_lib, [main_object])
582    _emit_go_link_action(
583        ctx,
584        transitive_go_library_paths = lib_result.transitive_go_library_paths,
585        transitive_go_libraries = lib_result.transitive_go_libraries,
586        cgo_deps = lib_result.transitive_cgo_deps,
587        libs = [main_lib],
588        executable = ctx.outputs.executable,
589        gc_linkopts = _gc_linkopts(ctx),
590    )
591
592    # TODO(bazel-team): the Go tests should do a chdir to the directory
593    # holding the data files, so open-source go tests continue to work
594    # without code changes.
595    runfiles = ctx.runfiles(files = [ctx.outputs.executable])
596    runfiles = runfiles.merge(lib_result.runfiles)
597    return struct(
598        files = depset([ctx.outputs.executable]),
599        runfiles = runfiles,
600    )
601
602go_env_attrs = {
603    "toolchain": attr.label(
604        default = Label("//go/toolchain:toolchain"),
605        allow_files = True,
606        cfg = "host",
607    ),
608    "go_tool": attr.label(
609        default = Label("//go/toolchain:go_tool"),
610        single_file = True,
611        allow_files = True,
612        cfg = "host",
613    ),
614    "go_prefix": attr.label(
615        providers = ["go_prefix"],
616        default = Label(
617            "//:go_prefix",
618            relative_to_caller_repository = True,
619        ),
620        allow_files = False,
621        cfg = "host",
622    ),
623    "go_src": attr.label(
624        default = Label("//go/toolchain:go_src"),
625        allow_files = True,
626        cfg = "host",
627    ),
628    "go_include": attr.label(
629        default = Label("//go/toolchain:go_include"),
630        single_file = True,
631        allow_files = True,
632        cfg = "host",
633    ),
634    "go_root": attr.label(
635        providers = ["go_root"],
636        default = Label(
637            "//go/toolchain:go_root",
638        ),
639        allow_files = False,
640        cfg = "host",
641    ),
642    "_filter_tags": attr.label(
643        default = Label("//go/tools/filter_tags"),
644        cfg = "host",
645        executable = True,
646        single_file = True,
647    ),
648    "_filter_exec": attr.label(
649        default = Label("//go/tools/filter_exec"),
650        cfg = "host",
651        executable = True,
652        single_file = True,
653    ),
654    "_asm": attr.label(
655        default = Label("//go/tools/builders:asm"),
656        cfg = "host",
657        executable = True,
658        single_file = True,
659    ),
660}
661
662go_library_attrs = go_env_attrs + {
663    "data": attr.label_list(
664        allow_files = True,
665        cfg = "data",
666    ),
667    "srcs": attr.label_list(allow_files = go_filetype),
668    "deps": attr.label_list(
669        providers = [
670            "transitive_go_library_paths",
671            "transitive_go_libraries",
672            "transitive_cgo_deps",
673        ],
674    ),
675    "importpath": attr.string(),
676    "library": attr.label(
677        providers = [
678            "direct_deps",
679            "go_sources",
680            "asm_sources",
681            "cgo_object",
682            "gc_goopts",
683        ],
684    ),
685    "gc_goopts": attr.string_list(),
686}
687
688_crosstool_attrs = {
689    "_crosstool": attr.label(
690        default = Label("//tools/defaults:crosstool"),
691    ),
692}
693
694go_link_attrs = go_library_attrs + _crosstool_attrs + {
695    "gc_linkopts": attr.string_list(),
696    "linkstamp": attr.string(),
697    "x_defs": attr.string_dict(),
698}
699
700go_library = rule(
701    go_library_impl,
702    attrs = go_library_attrs + {
703        "cgo_object": attr.label(
704            providers = [
705                "cgo_obj",
706                "cgo_deps",
707            ],
708        ),
709    },
710    fragments = ["cpp"],
711)
712
713go_binary = rule(
714    go_binary_impl,
715    attrs = go_library_attrs + _crosstool_attrs + go_link_attrs,
716    executable = True,
717    fragments = ["cpp"],
718)
719
720go_test = rule(
721    go_test_impl,
722    attrs = go_library_attrs + _crosstool_attrs + go_link_attrs + {
723        "test_generator": attr.label(
724            executable = True,
725            default = Label(
726                "//go/tools:generate_test_main",
727            ),
728            cfg = "host",
729        ),
730    },
731    executable = True,
732    fragments = ["cpp"],
733    test = True,
734)
735
736def _pkg_dir(workspace_root, package_name):
737    if workspace_root and package_name:
738        return workspace_root + "/" + package_name
739    if workspace_root:
740        return workspace_root
741    if package_name:
742        return package_name
743    return "."
744
745def _exec_path(path):
746    if path.startswith("/"):
747        return path
748    return "${execroot}/" + path
749
750def _cgo_filter_srcs_impl(ctx):
751    srcs = ctx.files.srcs
752    dsts = []
753    cmds = []
754    for src in srcs:
755        stem, _, ext = src.path.rpartition(".")
756        dst_basename = "%s.filtered.%s" % (stem, ext)
757        dst = ctx.new_file(src, dst_basename)
758        cmds += [
759            "if '%s' -cgo -quiet '%s'; then" %
760            (ctx.executable._filter_tags.path, src.path),
761            "  cp '%s' '%s'" % (src.path, dst.path),
762            "else",
763            "  echo -n >'%s'" % dst.path,
764            "fi",
765        ]
766        dsts.append(dst)
767
768    if ctx.label.package == "":
769        script_name = ctx.label.name + ".CGoFilterSrcs.params"
770    else:
771        script_name = ctx.label.package + "/" + ctx.label.name + ".CGoFilterSrcs.params"
772    f = _emit_generate_params_action(cmds, ctx, script_name)
773    ctx.action(
774        inputs = [f, ctx.executable._filter_tags] + srcs,
775        outputs = dsts,
776        command = f.path,
777        mnemonic = "CgoFilterSrcs",
778    )
779    return struct(
780        files = depset(dsts),
781    )
782
783_cgo_filter_srcs = rule(
784    implementation = _cgo_filter_srcs_impl,
785    attrs = {
786        "srcs": attr.label_list(
787            allow_files = cgo_filetype,
788        ),
789        "_filter_tags": attr.label(
790            default = Label("//go/tools/filter_tags"),
791            cfg = "host",
792            executable = True,
793            single_file = True,
794        ),
795    },
796    fragments = ["cpp"],
797)
798
799def _cgo_codegen_impl(ctx):
800    go_srcs = ctx.files.srcs
801    srcs = go_srcs + ctx.files.c_hdrs
802    linkopts = ctx.attr.linkopts
803    copts = ctx.fragments.cpp.c_options + ctx.attr.copts
804    deps = depset([], order = "topological")
805    for d in ctx.attr.deps:
806        srcs += list(d.cc.transitive_headers)
807        deps += d.cc.libs
808        copts += ["-D" + define for define in d.cc.defines]
809        for inc in d.cc.include_directories:
810            copts += ["-I", _exec_path(inc)]
811        for hdr in ctx.files.c_hdrs:
812            copts += ["-iquote", hdr.dirname]
813        for inc in d.cc.quote_include_directories:
814            copts += ["-iquote", _exec_path(inc)]
815        for inc in d.cc.system_include_directories:
816            copts += ["-isystem", _exec_path(inc)]
817        for lib in d.cc.libs:
818            if lib.basename.startswith("lib") and lib.basename.endswith(".so"):
819                linkopts += ["-L", lib.dirname, "-l", lib.basename[3:-3]]
820            else:
821                linkopts += [lib.path]
822        linkopts += d.cc.link_flags
823
824    p = _pkg_dir(ctx.label.workspace_root, ctx.label.package) + "/"
825    if p == "./":
826        p = ""  # workaround when cgo_library in repository root
827    out_dir = (ctx.configuration.genfiles_dir.path + "/" +
828               p + ctx.attr.outdir)
829    cc = ctx.fragments.cpp.compiler_executable
830    cmds = [
831        # We cannot use env for CC because $(CC) on OSX is relative
832        # and '../' does not work fine due to symlinks.
833        "export CC=$(cd $(dirname {cc}); pwd)/$(basename {cc})".format(cc = cc),
834        "export CXX=$CC",
835        'objdir="%s/gen"' % out_dir,
836        "execroot=$(pwd)",
837        'mkdir -p "$objdir"',
838        "unfiltered_go_files=(%s)" % " ".join(["'%s'" % f.path for f in go_srcs]),
839        "filtered_go_files=()",
840        'for file in "${unfiltered_go_files[@]}"; do',
841        '  stem=$(basename "$file" .go)',
842        '  if %s -cgo -quiet "$file"; then' % ctx.executable._filter_tags.path,
843        '    filtered_go_files+=("$file")',
844        "  else",
845        '    grep --max-count 1 "^package " "$file" >"$objdir/$stem.go"',
846        '    echo -n >"$objdir/$stem.c"',
847        "  fi",
848        "done",
849        "if [ ${#filtered_go_files[@]} -eq 0 ]; then",
850        "  echo no buildable Go source files in %s >&1" % str(ctx.label),
851        "  exit 1",
852        "fi",
853        '"$GOROOT/bin/go" tool cgo -objdir "$objdir" -- %s "${filtered_go_files[@]}"' %
854        " ".join(['"%s"' % copt for copt in copts]),
855        # Rename the outputs using glob so we don't have to understand cgo's mangling
856        # TODO(#350): might be fixed by this?.
857        'for file in "${filtered_go_files[@]}"; do',
858        '  stem=$(basename "$file" .go)',
859        '  mv "$objdir/"*"$stem.cgo1.go" "$objdir/$stem.go"',
860        '  mv "$objdir/"*"$stem.cgo2.c" "$objdir/$stem.c"',
861        "done",
862        "rm -f $objdir/_cgo_.o $objdir/_cgo_flags",
863    ]
864
865    f = _emit_generate_params_action(cmds, ctx, out_dir + ".CGoCodeGenFile.params")
866
867    inputs = (srcs + ctx.files.toolchain + ctx.files._crosstool +
868              [f, ctx.executable._filter_tags])
869    ctx.action(
870        inputs = inputs,
871        outputs = ctx.outputs.outs,
872        mnemonic = "CGoCodeGen",
873        progress_message = "CGoCodeGen %s" % ctx.label,
874        command = f.path,
875        env = go_environment_vars(ctx) + {
876            "CGO_LDFLAGS": " ".join(linkopts),
877        },
878    )
879    return struct(
880        label = ctx.label,
881        files = depset(ctx.outputs.outs),
882        cgo_deps = deps,
883    )
884
885_cgo_codegen_rule = rule(
886    _cgo_codegen_impl,
887    attrs = go_env_attrs + _crosstool_attrs + {
888        "srcs": attr.label_list(
889            allow_files = go_filetype,
890            non_empty = True,
891        ),
892        "c_hdrs": attr.label_list(
893            allow_files = cc_hdr_filetype,
894        ),
895        "deps": attr.label_list(
896            allow_files = False,
897            providers = ["cc"],
898        ),
899        "copts": attr.string_list(),
900        "linkopts": attr.string_list(),
901        "outdir": attr.string(mandatory = True),
902        "outs": attr.output_list(
903            mandatory = True,
904            non_empty = True,
905        ),
906    },
907    fragments = ["cpp"],
908    output_to_genfiles = True,
909)
910
911def _cgo_codegen(
912        name,
913        srcs,
914        c_hdrs = [],
915        deps = [],
916        copts = [],
917        linkopts = [],
918        go_tool = None,
919        toolchain = None):
920    """Generates glue codes for interop between C and Go
921
922    Args:
923      name: A unique name of the rule
924      srcs: list of Go source files.
925        Each of them must contain `import "C"`.
926      c_hdrs: C/C++ header files necessary to determine kinds of
927        C/C++ identifiers in srcs.
928      deps: A list of cc_library rules.
929        The generated codes are expected to be linked with these deps.
930      linkopts: A list of linker options,
931        These flags are passed to the linker when the generated codes
932        are linked into the target binary.
933    """
934    outdir = name + ".dir"
935    outgen = outdir + "/gen"
936
937    go_thunks = []
938    c_thunks = []
939    for s in srcs:
940        if not s.endswith(".go"):
941            fail("not a .go file: %s" % s)
942        basename = s[:-3]
943        if basename.rfind("/") >= 0:
944            basename = basename[basename.rfind("/") + 1:]
945        go_thunks.append(outgen + "/" + basename + ".go")
946        c_thunks.append(outgen + "/" + basename + ".c")
947
948    outs = struct(
949        name = name,
950        outdir = outgen,
951        go_thunks = go_thunks,
952        c_thunks = c_thunks,
953        c_exports = [
954            outgen + "/_cgo_export.c",
955            outgen + "/_cgo_export.h",
956        ],
957        c_dummy = outgen + "/_cgo_main.c",
958        gotypes = outgen + "/_cgo_gotypes.go",
959    )
960
961    _cgo_codegen_rule(
962        name = name,
963        srcs = srcs,
964        c_hdrs = c_hdrs,
965        deps = deps,
966        copts = copts,
967        linkopts = linkopts,
968        go_tool = go_tool,
969        toolchain = toolchain,
970        outdir = outdir,
971        outs = outs.go_thunks + outs.c_thunks + outs.c_exports + [
972            outs.c_dummy,
973            outs.gotypes,
974        ],
975        visibility = ["//visibility:private"],
976    )
977    return outs
978
979def _cgo_import_impl(ctx):
980    cmds = [
981        (ctx.file.go_tool.path + " tool cgo" +
982         " -dynout " + ctx.outputs.out.path +
983         " -dynimport " + ctx.file.cgo_o.path +
984         " -dynpackage $(%s %s)" % (
985             ctx.executable._extract_package.path,
986             ctx.file.sample_go_src.path,
987         )),
988    ]
989    f = _emit_generate_params_action(cmds, ctx, ctx.outputs.out.path + ".CGoImportGenFile.params")
990    ctx.action(
991        inputs = (ctx.files.toolchain +
992                  [
993                      f,
994                      ctx.file.go_tool,
995                      ctx.executable._extract_package,
996                      ctx.file.cgo_o,
997                      ctx.file.sample_go_src,
998                  ]),
999        outputs = [ctx.outputs.out],
1000        command = f.path,
1001        mnemonic = "CGoImportGen",
1002        env = go_environment_vars(ctx),
1003    )
1004    return struct(
1005        files = depset([ctx.outputs.out]),
1006    )
1007
1008_cgo_import = rule(
1009    _cgo_import_impl,
1010    attrs = go_env_attrs + {
1011        "cgo_o": attr.label(
1012            allow_files = True,
1013            single_file = True,
1014        ),
1015        "sample_go_src": attr.label(
1016            allow_files = True,
1017            single_file = True,
1018        ),
1019        "out": attr.output(
1020            mandatory = True,
1021        ),
1022        "_extract_package": attr.label(
1023            default = Label("//go/tools/extract_package"),
1024            executable = True,
1025            cfg = "host",
1026        ),
1027    },
1028    fragments = ["cpp"],
1029)
1030
1031def _cgo_genrule_impl(ctx):
1032    return struct(
1033        label = ctx.label,
1034        go_sources = ctx.files.srcs,
1035        asm_sources = [],
1036        asm_headers = [],
1037        cgo_object = ctx.attr.cgo_object,
1038        direct_deps = ctx.attr.deps,
1039        gc_goopts = [],
1040    )
1041
1042_cgo_genrule = rule(
1043    _cgo_genrule_impl,
1044    attrs = {
1045        "srcs": attr.label_list(allow_files = FileType([".go"])),
1046        "cgo_object": attr.label(
1047            providers = [
1048                "cgo_obj",
1049                "cgo_deps",
1050            ],
1051        ),
1052        "deps": attr.label_list(
1053            providers = [
1054                "direct_deps",
1055                "transitive_go_library_paths",
1056                "transitive_go_libraries",
1057                "transitive_cgo_deps",
1058            ],
1059        ),
1060    },
1061    fragments = ["cpp"],
1062)
1063
1064"""Generates symbol-import directives for cgo
1065
1066Args:
1067  cgo_o: The loadable object to extract dynamic symbols from.
1068  sample_go_src: A go source which is compiled together with the generated file.
1069    The generated file will have the same Go package name as this file.
1070  out: Destination of the generated codes.
1071"""
1072
1073def _cgo_object_impl(ctx):
1074    arguments = _c_linker_options(ctx, blocklist = [
1075        # never link any dependency libraries
1076        "-l",
1077        "-L",
1078        # manage flags to ld(1) by ourselves
1079        "-Wl,",
1080    ])
1081    arguments += [
1082        "-o",
1083        ctx.outputs.out.path,
1084        "-nostdlib",
1085        "-Wl,-r",
1086    ]
1087    if _is_darwin_cpu(ctx):
1088        arguments += ["-shared", "-Wl,-all_load"]
1089    else:
1090        arguments += ["-Wl,-whole-archive"]
1091
1092    lo = ctx.files.src[-1]
1093    arguments += [lo.path]
1094
1095    ctx.action(
1096        inputs = [lo] + ctx.files._crosstool,
1097        outputs = [ctx.outputs.out],
1098        mnemonic = "CGoObject",
1099        progress_message = "Linking %s" % ctx.outputs.out.short_path,
1100        executable = ctx.fragments.cpp.compiler_executable,
1101        arguments = arguments,
1102    )
1103    runfiles = ctx.runfiles(collect_data = True)
1104    runfiles = runfiles.merge(ctx.attr.src.data_runfiles)
1105    return struct(
1106        files = depset([ctx.outputs.out]),
1107        cgo_obj = ctx.outputs.out,
1108        cgo_deps = ctx.attr.cgogen.cgo_deps,
1109        runfiles = runfiles,
1110    )
1111
1112_cgo_object = rule(
1113    _cgo_object_impl,
1114    attrs = _crosstool_attrs + {
1115        "src": attr.label(
1116            mandatory = True,
1117            providers = ["cc"],
1118        ),
1119        "cgogen": attr.label(
1120            mandatory = True,
1121            providers = ["cgo_deps"],
1122        ),
1123        "out": attr.output(
1124            mandatory = True,
1125        ),
1126    },
1127    fragments = ["cpp"],
1128)
1129
1130"""Generates _all.o to be archived together with Go objects.
1131
1132Args:
1133  src: source static library which contains objects
1134  cgogen: _cgo_codegen rule which knows the dependency cc_library() rules
1135    to be linked together with src when we generate the final go binary.
1136"""
1137
1138def _setup_cgo_library(name, srcs, cdeps, copts, clinkopts, go_tool, toolchain):
1139    go_srcs = [s for s in srcs if s.endswith(".go")]
1140    c_hdrs = [s for s in srcs if any([s.endswith(ext) for ext in hdr_exts])]
1141    c_srcs = [s for s in srcs if not s in (go_srcs + c_hdrs)]
1142
1143    # Split cgo files into .go parts and .c parts (plus some other files).
1144    cgogen = _cgo_codegen(
1145        name = name + ".cgo",
1146        srcs = go_srcs,
1147        c_hdrs = c_hdrs,
1148        deps = cdeps,
1149        copts = copts,
1150        linkopts = clinkopts,
1151        go_tool = go_tool,
1152        toolchain = toolchain,
1153    )
1154
1155    # Filter c_srcs with build constraints.
1156    c_filtered_srcs = []
1157    if len(c_srcs) > 0:
1158        c_filtered_srcs_name = name + "_filter_cgo_srcs"
1159        _cgo_filter_srcs(
1160            name = c_filtered_srcs_name,
1161            srcs = c_srcs,
1162        )
1163        c_filtered_srcs.append(":" + c_filtered_srcs_name)
1164
1165    pkg_dir = _pkg_dir(
1166        "external/" + REPOSITORY_NAME[1:] if len(REPOSITORY_NAME) > 1 else "",
1167        PACKAGE_NAME,
1168    )
1169
1170    # Platform-specific settings
1171    native.config_setting(
1172        name = name + "_windows_setting",
1173        values = {
1174            "cpu": "x64_windows_msvc",
1175        },
1176    )
1177    platform_copts = select({
1178        ":" + name + "_windows_setting": ["-mthreads"],
1179        "//conditions:default": ["-pthread"],
1180    })
1181    platform_linkopts = select({
1182        ":" + name + "_windows_setting": ["-mthreads"],
1183        "//conditions:default": ["-pthread"],
1184    })
1185
1186    # Bundles objects into an archive so that _cgo_.o and _all.o can share them.
1187    native.cc_library(
1188        name = cgogen.outdir + "/_cgo_lib",
1189        srcs = cgogen.c_thunks + cgogen.c_exports + c_filtered_srcs + c_hdrs,
1190        deps = cdeps,
1191        copts = copts + platform_copts + [
1192            "-I",
1193            pkg_dir,
1194            "-I",
1195            "$(GENDIR)/" + pkg_dir + "/" + cgogen.outdir,
1196            # The generated thunks often contain unused variables.
1197            "-Wno-unused-variable",
1198        ],
1199        linkopts = clinkopts + platform_linkopts,
1200        linkstatic = 1,
1201        # _cgo_.o and _all.o keep all objects in this archive.
1202        # But it should not be very annoying in the final binary target
1203        # because _cgo_object rule does not propagate alwayslink=1
1204        alwayslink = 1,
1205        visibility = ["//visibility:private"],
1206    )
1207
1208    # Loadable object which cgo reads when it generates _cgo_import.go
1209    native.cc_binary(
1210        name = cgogen.outdir + "/_cgo_.o",
1211        srcs = [cgogen.c_dummy],
1212        deps = cdeps + [cgogen.outdir + "/_cgo_lib"],
1213        copts = copts,
1214        linkopts = clinkopts,
1215        visibility = ["//visibility:private"],
1216    )
1217    _cgo_import(
1218        name = "%s.cgo.importgen" % name,
1219        cgo_o = cgogen.outdir + "/_cgo_.o",
1220        out = cgogen.outdir + "/_cgo_import.go",
1221        sample_go_src = go_srcs[0],
1222        go_tool = go_tool,
1223        toolchain = toolchain,
1224        visibility = ["//visibility:private"],
1225    )
1226
1227    _cgo_object(
1228        name = cgogen.outdir + "/_cgo_object",
1229        src = cgogen.outdir + "/_cgo_lib",
1230        out = cgogen.outdir + "/_all.o",
1231        cgogen = cgogen.name,
1232        visibility = ["//visibility:private"],
1233    )
1234    return cgogen
1235
1236def cgo_genrule(
1237        name,
1238        srcs,
1239        copts = [],
1240        clinkopts = [],
1241        cdeps = [],
1242        **kwargs):
1243    cgogen = _setup_cgo_library(
1244        name = name,
1245        srcs = srcs,
1246        cdeps = cdeps,
1247        copts = copts,
1248        clinkopts = clinkopts,
1249        toolchain = None,
1250        go_tool = None,
1251    )
1252    _cgo_genrule(
1253        name = name,
1254        srcs = cgogen.go_thunks + [
1255            cgogen.gotypes,
1256            cgogen.outdir + "/_cgo_import.go",
1257        ],
1258        cgo_object = cgogen.outdir + "/_cgo_object",
1259        **kwargs
1260    )
1261
1262def cgo_library(
1263        name,
1264        srcs,
1265        toolchain = None,
1266        go_tool = None,
1267        copts = [],
1268        clinkopts = [],
1269        cdeps = [],
1270        **kwargs):
1271    """Builds a cgo-enabled go library.
1272
1273    Args:
1274      name: A unique name for this rule.
1275      srcs: List of Go, C and C++ files that are processed to build a Go library.
1276        Those Go files must contain `import "C"`.
1277        C and C++ files can be anything allowed in `srcs` attribute of
1278        `cc_library`.
1279      copts: Add these flags to the C++ compiler.
1280      clinkopts: Add these flags to the C++ linker.
1281      cdeps: List of C/C++ libraries to be linked into the binary target.
1282        They must be `cc_library` rules.
1283      deps: List of other libraries to be linked to this library target.
1284      data: List of files needed by this rule at runtime.
1285
1286    NOTE:
1287      `srcs` cannot contain pure-Go files, which do not have `import "C"`.
1288      So you need to define another `go_library` when you build a go package with
1289      both cgo-enabled and pure-Go sources.
1290
1291      ```
1292      cgo_library(
1293          name = "cgo_enabled",
1294          srcs = ["cgo-enabled.go", "foo.cc", "bar.S", "baz.a"],
1295      )
1296
1297      go_library(
1298          name = "go_default_library",
1299          srcs = ["pure-go.go"],
1300          library = ":cgo_enabled",
1301      )
1302      ```
1303    """
1304    cgogen = _setup_cgo_library(
1305        name = name,
1306        srcs = srcs,
1307        cdeps = cdeps,
1308        copts = copts,
1309        clinkopts = clinkopts,
1310        go_tool = go_tool,
1311        toolchain = toolchain,
1312    )
1313
1314    go_library(
1315        name = name,
1316        srcs = cgogen.go_thunks + [
1317            cgogen.gotypes,
1318            cgogen.outdir + "/_cgo_import.go",
1319        ],
1320        cgo_object = cgogen.outdir + "/_cgo_object",
1321        go_tool = go_tool,
1322        toolchain = toolchain,
1323        **kwargs
1324    )
1325