Vendor dependencies

Let's see how I like this workflow.
This commit is contained in:
John Doty 2022-12-19 08:27:18 -08:00
parent 34d1830413
commit 9c435dc440
7500 changed files with 1665121 additions and 99 deletions

View file

@ -0,0 +1,183 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//:paths.bzl", "paths")
load(
"@prelude//cxx:compile.bzl",
"CxxSrcWithFlags", # @unused Used as a type
)
load("@prelude//cxx:cxx_library.bzl", "cxx_compile_srcs")
load("@prelude//cxx:cxx_toolchain_types.bzl", "CxxToolchainInfo")
load(
"@prelude//cxx:cxx_types.bzl",
"CxxRuleConstructorParams", # @unused Used as a type
)
load("@prelude//cxx:headers.bzl", "cxx_get_regular_cxx_headers_layout", "prepare_headers")
load(
"@prelude//cxx:preprocessor.bzl",
"CPreprocessor",
"cxx_inherited_preprocessor_infos",
"cxx_merge_cpreprocessors",
"cxx_private_preprocessor_info",
)
load(
"@prelude//linking:link_info.bzl",
"Linkage",
"MergedLinkInfo",
"merge_link_infos",
)
load(
"@prelude//linking:shared_libraries.bzl",
"SharedLibraryInfo",
"merge_shared_libraries",
)
load(
"@prelude//utils:utils.bzl",
"expect",
"map_idx",
)
load(":compile.bzl", "GoPkgCompileInfo", "compile", "get_filtered_srcs", "get_inherited_compile_pkgs")
load(":link.bzl", "GoPkgLinkInfo", "get_inherited_link_pkgs")
load(":packages.bzl", "go_attr_pkg_name", "merge_pkgs")
load(":toolchain.bzl", "GoToolchainInfo", "get_toolchain_cmd_args")
def _cgo(
ctx: "context",
srcs: ["artifact"],
own_pre: [CPreprocessor.type],
inherited_pre: ["CPreprocessorInfo"]) -> (["artifact"], ["artifact"], ["artifact"]):
"""
Run `cgo` on `.go` sources to genreate Go, C, and C-Header sources.
"""
pre = cxx_merge_cpreprocessors(ctx, own_pre, inherited_pre)
pre_args = pre.set.project_as_args("args")
gen_dir = "cgo_gen"
go_srcs = []
c_headers = []
c_srcs = []
go_srcs.append(ctx.actions.declare_output(paths.join(gen_dir, "_cgo_gotypes.go")))
c_srcs.append(ctx.actions.declare_output(paths.join(gen_dir, "_cgo_export.c")))
c_headers.append(ctx.actions.declare_output(paths.join(gen_dir, "_cgo_export.h")))
for src in srcs:
go_srcs.append(ctx.actions.declare_output(paths.join(gen_dir, paths.replace_extension(src.basename, ".cgo1.go"))))
c_srcs.append(ctx.actions.declare_output(paths.join(gen_dir, paths.replace_extension(src.basename, ".cgo2.c"))))
# Return a `cmd_args` to use as the generated sources.
go_toolchain = ctx.attrs._go_toolchain[GoToolchainInfo]
expect(go_toolchain.cgo != None)
expect(CxxToolchainInfo in ctx.attrs._cxx_toolchain)
cxx_toolchain = ctx.attrs._cxx_toolchain[CxxToolchainInfo]
cmd = get_toolchain_cmd_args(go_toolchain, go_root = False)
cmd.add(go_toolchain.cgo_wrapper[RunInfo])
cmd.add(cmd_args(go_toolchain.cgo, format = "--cgo={}"))
# TODO(agallagher): cgo outputs a dir with generated sources, but I'm not
# sure how to pass in an output dir *and* enumerate the sources we know will
# generated w/o v2 complaining that the output dir conflicts with the nested
# artifacts.
cmd.add(cmd_args(go_srcs[0].as_output(), format = "--output={}/.."))
cmd.add(cmd_args(cxx_toolchain.c_compiler_info.preprocessor, format = "--cpp={}"))
cmd.add(cmd_args(pre_args, format = "--cpp={}"))
cmd.add(srcs)
for src in go_srcs + c_headers + c_srcs:
cmd.hidden(src.as_output())
ctx.actions.run(cmd, category = "cgo")
return go_srcs, c_headers, c_srcs
def cgo_library_impl(ctx: "context") -> ["provider"]:
pkg_name = go_attr_pkg_name(ctx)
# Gather preprocessor inputs.
(own_pre, _) = cxx_private_preprocessor_info(
ctx,
cxx_get_regular_cxx_headers_layout(ctx),
)
inherited_pre = cxx_inherited_preprocessor_infos(ctx.attrs.deps)
# Seprate sources into C++ and CGO sources.
cgo_srcs = []
cxx_srcs = []
for src in ctx.attrs.srcs:
if src.extension == ".go":
cgo_srcs.append(src)
elif src.extension in (".c", ".cpp"):
cxx_srcs.append(src)
else:
fail("unexpected extension: {}".format(src))
# Generate CGO and C sources.
go_srcs, c_headers, c_srcs = _cgo(ctx, cgo_srcs, [own_pre], inherited_pre)
cxx_srcs.extend(c_srcs)
# Wrap the generated CGO C headers in a CPreprocessor object for compiling.
cgo_headers_pre = CPreprocessor(args = [
"-I",
prepare_headers(
ctx,
{h.basename: h for h in c_headers},
"cgo-private-headers",
).include_path,
])
link_style = ctx.attrs.link_style
if link_style == None:
link_style = "static"
# Copmile C++ sources into object files.
c_compile_cmds = cxx_compile_srcs(
ctx,
CxxRuleConstructorParams(
rule_type = "cgo_library",
headers_layout = cxx_get_regular_cxx_headers_layout(ctx),
srcs = [CxxSrcWithFlags(file = src) for src in cxx_srcs],
),
# Create private header tree and propagate via args.
[own_pre, cgo_headers_pre],
inherited_pre,
[],
Linkage(link_style),
)
compiled_objects = c_compile_cmds.objects
if link_style != "static":
compiled_objects = c_compile_cmds.pic_objects
# Merge all sources together to pass to the Go compile step.
all_srcs = cmd_args(go_srcs + compiled_objects)
if ctx.attrs.go_srcs:
all_srcs.add(get_filtered_srcs(ctx, ctx.attrs.go_srcs))
# Build Go library.
lib = compile(
ctx,
pkg_name,
all_srcs,
deps = ctx.attrs.deps + ctx.attrs.exported_deps,
)
pkgs = {pkg_name: lib}
return [
DefaultInfo(default_outputs = [lib]),
GoPkgCompileInfo(pkgs = merge_pkgs([
pkgs,
get_inherited_compile_pkgs(ctx.attrs.exported_deps),
])),
GoPkgLinkInfo(pkgs = merge_pkgs([
pkgs,
get_inherited_link_pkgs(ctx.attrs.deps + ctx.attrs.exported_deps),
])),
merge_link_infos(ctx, filter(None, [d.get(MergedLinkInfo) for d in ctx.attrs.deps])),
merge_shared_libraries(
ctx.actions,
deps = filter(None, map_idx(SharedLibraryInfo, ctx.attrs.deps)),
),
]

View file

@ -0,0 +1,134 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//:paths.bzl", "paths")
load(":packages.bzl", "merge_pkgs")
load(":toolchain.bzl", "GoToolchainInfo", "get_toolchain_cmd_args")
# Provider wrapping packages used for compiling.
GoPkgCompileInfo = provider(fields = [
"pkgs", # {str.type: "artifact"}
])
# Provider for test targets that test a library. Contains information for
# compiling the test and library code together as expected by go.
GoTestInfo = provider(fields = [
"deps", # ["dependency"]
"srcs", # ["source"]
"pkg_name", # str.type
])
def get_inherited_compile_pkgs(deps: ["dependency"]) -> {str.type: "artifact"}:
return merge_pkgs([d[GoPkgCompileInfo].pkgs for d in deps if GoPkgCompileInfo in d])
def get_filtered_srcs(ctx: "context", srcs: ["artifact"], tests: bool.type = False) -> "cmd_args":
"""
Filter the input sources based on build pragma
"""
go_toolchain = ctx.attrs._go_toolchain[GoToolchainInfo]
# Delegate to `go list` to filter out srcs with incompatible `// +build`
# pragmas.
filtered_srcs = ctx.actions.declare_output("__filtered_srcs__.txt")
srcs_dir = ctx.actions.symlinked_dir(
"__srcs__",
{src.short_path: src for src in srcs},
)
filter_cmd = get_toolchain_cmd_args(go_toolchain, go_root = False)
filter_cmd.add(go_toolchain.filter_srcs[RunInfo])
filter_cmd.add(cmd_args(go_toolchain.go, format = "--go={}"))
if tests:
filter_cmd.add("--tests")
filter_cmd.add(cmd_args(",".join(go_toolchain.tags), format = "--tags={}"))
filter_cmd.add(cmd_args(filtered_srcs.as_output(), format = "--output={}"))
filter_cmd.add(srcs_dir)
ctx.actions.run(filter_cmd, category = "go_filter_srcs")
# Add filtered srcs to compile command.
return cmd_args(filtered_srcs, format = "@{}").hidden(srcs)
def _get_import_map(pkgs: [str.type]) -> {str.type: str.type}:
"""
Return the import remappings for vendor paths.
"""
vendor_prefixes = []
vendor_prefixes.append("third-party-source/go")
# TODO: add in implicit vendor prefixes inferred from project name.
vendor_prefixes = reversed(sorted(vendor_prefixes))
mappings = {}
for pkg in pkgs:
for prefix in vendor_prefixes:
if paths.starts_with(pkg, prefix):
mappings[paths.relativize(pkg, prefix)] = pkg
break
return mappings
def _assemble_cmd(
ctx: "context",
flags: [str.type] = []) -> "cmd_args":
go_toolchain = ctx.attrs._go_toolchain[GoToolchainInfo]
cmd = cmd_args()
cmd.add(go_toolchain.assembler)
cmd.add(flags)
return cmd
def _compile_cmd(
ctx: "context",
pkg_name: str.type,
pkgs: {str.type: "artifact"} = {},
deps: ["dependency"] = [],
flags: [str.type] = []) -> "cmd_args":
go_toolchain = ctx.attrs._go_toolchain[GoToolchainInfo]
cmd = cmd_args()
cmd.add(go_toolchain.compiler)
cmd.add("-p", pkg_name)
cmd.add("-pack")
cmd.add("-nolocalimports")
cmd.add(flags)
cmd.add("-buildid=")
# Add Go pkgs inherited from deps to compiler search path.
all_pkgs = merge_pkgs([pkgs, get_inherited_compile_pkgs(deps)])
if all_pkgs:
pkg_dir = ctx.actions.symlinked_dir(
"__{}_compile_pkgs__".format(paths.basename(pkg_name)),
{name + path.extension: path for name, path in all_pkgs.items()},
)
cmd.add("-I", pkg_dir)
for mapping in _get_import_map(all_pkgs.keys()).items():
cmd.add("-importmap", "{}={}".format(*mapping))
return cmd
def compile(
ctx: "context",
pkg_name: str.type,
srcs: "cmd_args",
pkgs: {str.type: "artifact"} = {},
deps: ["dependency"] = [],
compile_flags: [str.type] = [],
assemble_flags: [str.type] = []) -> "artifact":
go_toolchain = ctx.attrs._go_toolchain[GoToolchainInfo]
output = ctx.actions.declare_output(paths.basename(pkg_name) + ".a")
cmd = get_toolchain_cmd_args(go_toolchain)
cmd.add(go_toolchain.compile_wrapper[RunInfo])
cmd.add(cmd_args(output.as_output(), format = "--output={}"))
cmd.add(cmd_args(_compile_cmd(ctx, pkg_name, pkgs, deps, compile_flags), format = "--compiler={}"))
cmd.add(cmd_args(_assemble_cmd(ctx, assemble_flags), format = "--assembler={}"))
cmd.add(cmd_args(go_toolchain.packer, format = "--packer={}"))
cmd.add(srcs)
ctx.actions.run(cmd, category = "go_compile", identifier = paths.basename(pkg_name))
return output

View file

@ -0,0 +1,46 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load(":toolchain.bzl", "GoToolchainInfo")
GoCoverageMode = enum(
"set",
"count",
"atomic",
)
# The result of runnin `go tool cover` on the input sources.
GoCoverResult = record(
# All sources after annotating non-`_test.go` sources. This will be a
# combination of the original `*_test.go` sources and the annotated non-
# `*_test.go` sources.
srcs = field("cmd_args"),
# Coverage variables we used when annotating non-test sources.
variables = field("cmd_args"),
)
def cover_srcs(ctx: "context", pkg_name: str.type, mode: GoCoverageMode.type, srcs: "cmd_args") -> GoCoverResult.type:
out_covered_src_dir = ctx.actions.declare_output("__covered_srcs__")
out_srcs_argsfile = ctx.actions.declare_output("covered_srcs.txt")
out_coverage_vars_argsfile = ctx.actions.declare_output("coverage_vars.txt")
go_toolchain = ctx.attrs._go_toolchain[GoToolchainInfo]
cmd = cmd_args()
cmd.add(go_toolchain.cover_srcs[RunInfo])
cmd.add("--cover", go_toolchain.cover)
cmd.add("--coverage-mode", mode.value)
cmd.add("--coverage-var-argsfile", out_coverage_vars_argsfile.as_output())
cmd.add("--covered-srcs-dir", out_covered_src_dir.as_output())
cmd.add("--out-srcs-argsfile", out_srcs_argsfile.as_output())
cmd.add("--pkg-name", pkg_name)
cmd.add(srcs)
ctx.actions.run(cmd, category = "go_cover")
return GoCoverResult(
srcs = cmd_args(out_srcs_argsfile, format = "@{}").hidden(out_covered_src_dir).hidden(srcs),
variables = cmd_args(out_coverage_vars_argsfile, format = "@{}"),
)

View file

@ -0,0 +1,44 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load(
"@prelude//utils:utils.bzl",
"expect",
)
load(":compile.bzl", "compile", "get_filtered_srcs")
load(":link.bzl", "link")
def go_binary_impl(ctx: "context") -> ["provider"]:
lib = compile(ctx, "main", get_filtered_srcs(ctx, ctx.attrs.srcs), deps = ctx.attrs.deps)
(bin, runtime_files) = link(ctx, lib, deps = ctx.attrs.deps, link_mode = ctx.attrs.link_mode)
hidden = []
for resource in ctx.attrs.resources:
if type(resource) == "artifact":
hidden.append(resource)
else:
# Otherwise, this is a dependency, so extract the resource and other
# resources from the `DefaultInfo` provider.
info = resource[DefaultInfo]
expect(
len(info.default_outputs) == 1,
"expected exactly one default output from {} ({})"
.format(resource, info.default_outputs),
)
[resource] = info.default_outputs
other = info.other_outputs
hidden.append(resource)
hidden.extend(other)
return [
DefaultInfo(
default_outputs = [bin],
other_outputs = hidden + runtime_files,
),
RunInfo(args = cmd_args(bin).hidden(hidden + runtime_files)),
]

View file

@ -0,0 +1,61 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load(
"@prelude//linking:link_info.bzl",
"MergedLinkInfo",
"merge_link_infos",
)
load(
"@prelude//linking:shared_libraries.bzl",
"SharedLibraryInfo",
"merge_shared_libraries",
)
load(
"@prelude//utils:utils.bzl",
"map_idx",
)
load(":compile.bzl", "GoPkgCompileInfo", "GoTestInfo", "compile", "get_filtered_srcs", "get_inherited_compile_pkgs")
load(":link.bzl", "GoPkgLinkInfo", "get_inherited_link_pkgs")
load(":packages.bzl", "go_attr_pkg_name", "merge_pkgs")
def go_library_impl(ctx: "context") -> ["provider"]:
pkgs = {}
default_outputs = []
pkg_name = None
if ctx.attrs.srcs:
pkg_name = go_attr_pkg_name(ctx)
lib = compile(
ctx,
pkg_name,
get_filtered_srcs(ctx, ctx.attrs.srcs),
deps = ctx.attrs.deps + ctx.attrs.exported_deps,
)
default_outputs.append(lib)
pkgs[pkg_name] = lib
return [
DefaultInfo(default_outputs = default_outputs),
GoPkgCompileInfo(pkgs = merge_pkgs([
pkgs,
get_inherited_compile_pkgs(ctx.attrs.exported_deps),
])),
GoPkgLinkInfo(pkgs = merge_pkgs([
pkgs,
get_inherited_link_pkgs(ctx.attrs.deps + ctx.attrs.exported_deps),
])),
GoTestInfo(
deps = ctx.attrs.deps,
srcs = ctx.attrs.srcs,
pkg_name = pkg_name,
),
merge_link_infos(ctx, filter(None, [d.get(MergedLinkInfo) for d in ctx.attrs.deps])),
merge_shared_libraries(
ctx.actions,
deps = filter(None, map_idx(SharedLibraryInfo, ctx.attrs.deps)),
),
]

View file

@ -0,0 +1,91 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//test/inject_test_run_info.bzl", "inject_test_run_info")
load(":compile.bzl", "GoTestInfo", "compile", "get_filtered_srcs")
load(":coverage.bzl", "GoCoverageMode", "cover_srcs")
load(":link.bzl", "link")
load(":packages.bzl", "go_attr_pkg_name")
def _gen_test_main(
ctx: "context",
pkg_name: str.type,
coverage_mode: [GoCoverageMode.type, None],
coverage_vars: ["cmd_args", None],
srcs: "cmd_args") -> "artifact":
"""
Generate a `main.go` which calls tests from the given sources.
"""
output = ctx.actions.declare_output("main.go")
cmd = cmd_args()
cmd.add(ctx.attrs._testmaingen[RunInfo])
if ctx.attrs.coverage_mode:
cmd.add(cmd_args(ctx.attrs.coverage_mode, format = "--cover-mode={}"))
cmd.add(cmd_args(output.as_output(), format = "--output={}"))
cmd.add(cmd_args(pkg_name, format = "--import-path={}"))
if coverage_mode != None:
cmd.add("--cover-mode", coverage_mode.value)
if coverage_vars != None:
cmd.add(coverage_vars)
cmd.add(srcs)
ctx.actions.run(cmd, category = "go_test_main_gen")
return output
def go_test_impl(ctx: "context") -> ["provider"]:
deps = ctx.attrs.deps
srcs = ctx.attrs.srcs
pkg_name = go_attr_pkg_name(ctx) + "_test"
# Copy the srcs, deps and pkg_name from the target library when set. The
# library code gets compiled together with the tests.
if ctx.attrs.library:
lib = ctx.attrs.library[GoTestInfo]
srcs += lib.srcs
deps += lib.deps
# TODO: should we assert that pkg_name != None here?
pkg_name = lib.pkg_name
srcs = get_filtered_srcs(ctx, srcs, tests = True)
# If coverage is enabled for this test, we need to preprocess the sources
# with the Go cover tool.
coverage_mode = None
coverage_vars = None
if ctx.attrs.coverage_mode != None:
coverage_mode = GoCoverageMode(ctx.attrs.coverage_mode)
cov_res = cover_srcs(ctx, pkg_name, coverage_mode, srcs)
srcs = cov_res.srcs
coverage_vars = cov_res.variables
# Compile all tests into a package.
tests = compile(ctx, pkg_name, srcs, deps = deps)
# Generate a main function which runs the tests and build that into another
# package.
gen_main = _gen_test_main(ctx, pkg_name, coverage_mode, coverage_vars, srcs)
main = compile(ctx, "main", cmd_args(gen_main), pkgs = {pkg_name: tests})
# Link the above into a Go binary.
(bin, runtime_files) = link(ctx, main, pkgs = {pkg_name: tests}, deps = deps)
run_cmd = cmd_args(bin).hidden(runtime_files)
# As per v1, copy in resources next to binary.
for resource in ctx.attrs.resources:
run_cmd.hidden(ctx.actions.copy_file(resource.short_path, resource))
return inject_test_run_info(
ctx,
ExternalRunnerTestInfo(
type = "go",
command = [run_cmd],
env = ctx.attrs.env,
labels = ctx.attrs.labels,
contacts = ctx.attrs.contacts,
),
) + [DefaultInfo(default_outputs = [bin], other_outputs = [gen_main] + runtime_files)]

View file

@ -0,0 +1,135 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//cxx:cxx_library_utility.bzl", "cxx_inherited_link_info")
load(
"@prelude//cxx:cxx_link_utility.bzl",
"executable_shared_lib_arguments",
)
load(
"@prelude//linking:link_info.bzl",
"LinkStyle",
"get_link_args",
"unpack_link_args",
)
load(
"@prelude//linking:shared_libraries.bzl",
"SharedLibraryInfo",
"merge_shared_libraries",
"traverse_shared_library_info",
)
load(
"@prelude//utils:utils.bzl",
"map_idx",
)
load(":packages.bzl", "merge_pkgs")
load(":toolchain.bzl", "GoToolchainInfo", "get_toolchain_cmd_args")
# Provider wrapping packages used for linking.
GoPkgLinkInfo = provider(fields = [
"pkgs", # {str.type: "artifact"}
])
def get_inherited_link_pkgs(deps: ["dependency"]) -> {str.type: "artifact"}:
return merge_pkgs([d[GoPkgLinkInfo].pkgs for d in deps if GoPkgLinkInfo in d])
def _process_shared_dependencies(ctx: "context", artifact: "artifact", deps: ["dependency"]):
"""
Provides files and linker args needed to for binaries with shared library linkage.
- the runtime files needed to run binary linked with shared libraries
- linker arguments for shared libraries
"""
if ctx.attrs.link_style != "shared":
return ([], [])
shlib_info = merge_shared_libraries(
ctx.actions,
deps = filter(None, map_idx(SharedLibraryInfo, deps)),
)
shared_libs = {}
for name, shared_lib in traverse_shared_library_info(shlib_info).items():
shared_libs[name] = shared_lib.lib
extra_link_args, runtime_files, _ = executable_shared_lib_arguments(
ctx.actions,
ctx.attrs._go_toolchain[GoToolchainInfo].cxx_toolchain_for_linking,
artifact,
shared_libs,
)
return (runtime_files, extra_link_args)
def link(ctx: "context", main: "artifact", pkgs: {str.type: "artifact"} = {}, deps: ["dependency"] = [], link_mode = None):
go_toolchain = ctx.attrs._go_toolchain[GoToolchainInfo]
output = ctx.actions.declare_output(ctx.label.name)
cmd = get_toolchain_cmd_args(go_toolchain)
cmd.add(go_toolchain.linker)
cmd.add("-o", output.as_output())
cmd.add("-buildmode", "exe") # TODO(agallagher): support other modes
cmd.add("-buildid=") # Setting to a static buildid helps make the binary reproducible.
# Add inherited Go pkgs to library search path.
all_pkgs = merge_pkgs([pkgs, get_inherited_link_pkgs(deps)])
pkgs_dir = ctx.actions.symlinked_dir(
"__link_pkgs__",
{name + path.extension: path for name, path in all_pkgs.items()},
)
cmd.add("-L", pkgs_dir)
link_style = ctx.attrs.link_style
if link_style == None:
link_style = "static"
runtime_files, extra_link_args = _process_shared_dependencies(ctx, main, deps)
# Gather external link args from deps.
ext_links = get_link_args(
cxx_inherited_link_info(ctx, deps),
LinkStyle(link_style),
)
ext_link_args = cmd_args(unpack_link_args(ext_links))
ext_link_args.add(cmd_args(extra_link_args, quote = "shell"))
if not link_mode:
link_mode = "external"
cmd.add("-linkmode", link_mode)
if link_mode == "external":
# Delegate to C++ linker...
# TODO: It feels a bit inefficient to generate a wrapper file for every
# link. Is there some way to etract the first arg of `RunInfo`? Or maybe
# we can generate te platform-specific stuff once and re-use?
cxx_toolchain = go_toolchain.cxx_toolchain_for_linking
cxx_link_cmd = cmd_args(
[
cxx_toolchain.linker_info.linker,
cxx_toolchain.linker_info.linker_flags,
go_toolchain.external_linker_flags,
ext_link_args,
"\"$@\"",
],
delimiter = " ",
)
linker_wrapper, _ = ctx.actions.write(
"__{}_cxx_link_wrapper__.sh".format(ctx.label.name),
["#!/bin/sh", cxx_link_cmd],
allow_args = True,
is_executable = True,
)
cmd.add("-extld", linker_wrapper).hidden(cxx_link_cmd)
if ctx.attrs.linker_flags:
cmd.add(ctx.attrs.linker_flags)
cmd.add(main)
ctx.actions.run(cmd, category = "go_link")
return (output, runtime_files)

View file

@ -0,0 +1,30 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//utils:utils.bzl", "value_or")
def go_attr_pkg_name(ctx: "context") -> str.type:
"""
Return the Go package name for the given context corresponing to a rule.
"""
return value_or(ctx.attrs.package_name, ctx.label.package)
def merge_pkgs(pkgss: [{str.type: "artifact"}]) -> {str.type: "artifact"}:
"""
Merge mappings of packages into a single mapping, throwing an error on
conflicts.
"""
all_pkgs = {}
for pkgs in pkgss:
for name, path in pkgs.items():
if name in pkgs and pkgs[name] != path:
fail("conflict for package {!r}: {} and {}".format(name, path, all_pkgs[name]))
all_pkgs[name] = path
return all_pkgs

View file

@ -0,0 +1,43 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
GoToolchainInfo = provider(fields = [
"assembler",
"cgo",
"cgo_wrapper",
"compile_wrapper",
"compiler",
"cover",
"cover_srcs",
"cxx_toolchain_for_linking",
"env_go_arch",
"env_go_os",
"env_go_root",
"external_linker_flags",
"filter_srcs",
"go",
"linker",
"packer",
"tags",
])
def get_toolchain_cmd_args(toolchain: "GoToolchainInfo", go_root = True) -> "cmd_args":
cmd = cmd_args("env")
if toolchain.env_go_arch != None:
cmd.add("GOARCH={}".format(toolchain.env_go_arch))
if toolchain.env_go_os != None:
cmd.add("GOOS={}".format(toolchain.env_go_os))
if go_root and toolchain.env_go_root != None:
cmd.add(cmd_args(toolchain.env_go_root, format = "GOROOT={}"))
# CGO is enabled by default for native compilation, but we need to set it
# explicitly for cross-builds:
# https://go-review.googlesource.com/c/go/+/12603/2/src/cmd/cgo/doc.go
if toolchain.cgo != None:
cmd.add("CGO_ENABLED=1")
return cmd

View file

@ -0,0 +1,35 @@
prelude = native
prelude.python_bootstrap_binary(
name = "compile_wrapper",
main = "compile_wrapper.py",
visibility = ["PUBLIC"],
)
prelude.python_bootstrap_binary(
name = "cover_srcs",
main = "cover_srcs.py",
visibility = ["PUBLIC"],
)
prelude.python_bootstrap_binary(
name = "filter_srcs",
main = "filter_srcs.py",
visibility = ["PUBLIC"],
)
prelude.python_bootstrap_binary(
name = "cgo_wrapper",
main = "cgo_wrapper.py",
visibility = ["PUBLIC"],
)
prelude.go_binary(
name = "testmaingen",
srcs = [
"testmaingen.go",
],
visibility = [
"PUBLIC",
],
)

View file

@ -0,0 +1,44 @@
#!/usr/bin/env python3
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
# pyre-unsafe
import argparse
import os
import subprocess
import sys
from pathlib import Path
def main(argv):
parser = argparse.ArgumentParser(fromfile_prefix_chars="@")
parser.add_argument("--cgo", action="append", default=[])
parser.add_argument("--output", required=True, type=Path)
parser.add_argument("--cpp", action="append", default=[])
parser.add_argument("srcs", type=Path, nargs="*")
args = parser.parse_args(argv[1:])
output = args.output.resolve(strict=False)
os.makedirs(output, exist_ok=True)
os.environ["CC"] = args.cpp[0]
cmd = []
cmd.extend(args.cgo)
# cmd.append("-importpath={}")
# cmd.append("-srcdir={}")
cmd.append(f"-objdir={output}")
# cmd.append(cgoCompilerFlags)
cmd.append("--")
# cmd.append(cxxCompilerFlags)
cmd.extend(args.cpp[1:])
cmd.extend(args.srcs)
subprocess.check_call(cmd)
sys.exit(main(sys.argv))

View file

@ -0,0 +1,94 @@
#!/usr/bin/env python3
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
"""
Compile the given Go sources into a Go package.
Example:
$ ./compile_wrapper.py \
--compiler compile \
--assember assemble \
--output srcs.txt src/dir/
"""
# pyre-unsafe
import argparse
import os
import subprocess
import sys
from pathlib import Path
from typing import List
def _compile(compile_prefix: List[str], output: Path, srcs: List[Path]):
cmd = []
cmd.extend(compile_prefix)
cmd.append("-trimpath={}".format(os.getcwd()))
cmd.append("-o")
cmd.append(output)
cmd.extend(srcs)
subprocess.check_call(cmd)
def _pack(pack_prefix: List[str], output: Path, items: List[Path]):
cmd = []
cmd.extend(pack_prefix)
cmd.append("r")
cmd.append(output)
cmd.extend(items)
subprocess.check_call(cmd)
def main(argv):
parser = argparse.ArgumentParser(fromfile_prefix_chars="@")
parser.add_argument("--compiler", action="append", default=[])
parser.add_argument("--assembler", action="append", default=[])
parser.add_argument("--packer", action="append", default=[])
parser.add_argument("--output", required=True, type=Path)
parser.add_argument("srcs", type=Path, nargs="*")
args = parser.parse_args(argv[1:])
# If there's no srcs, just leave an empty file.
if not args.srcs:
args.output.touch()
return
go_files = [s for s in args.srcs if s.suffix == ".go"]
s_files = [s for s in args.srcs if s.suffix == ".s"]
o_files = [s for s in args.srcs if s.suffix == ".o"]
if go_files:
compile_prefix = []
compile_prefix.extend(args.compiler)
# If we have assembly files, generate the symabi file to compile against.
if s_files:
symabis = args.output.with_suffix(".symabis")
_compile(args.assembler + ["-gensymabis"], symabis, s_files)
compile_prefix.extend(["-symabis", symabis])
_compile(compile_prefix, args.output, go_files)
else:
args.output.touch()
# If there are assembly files, assemble them to an object and add into the
# output archive.
if s_files:
s_object = args.output.with_suffix(".o")
_compile(args.assembler, s_object, s_files)
o_files.append(s_object)
if o_files:
_pack(args.packer, args.output, o_files)
sys.exit(main(sys.argv))

View file

@ -0,0 +1,81 @@
#!/usr/bin/env python3
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
"""
Run `go cover` on non-`_test.go` input sources.
"""
# pyre-unsafe
import argparse
import hashlib
import subprocess
import sys
from pathlib import Path
def _var(pkg_name, src):
return "Var_" + hashlib.md5(f"{pkg_name}::{src}".encode("utf-8")).hexdigest()
def main(argv):
parser = argparse.ArgumentParser(fromfile_prefix_chars="@")
parser.add_argument("--cover", type=Path, required=True)
parser.add_argument("--pkg-name", type=str, required=True)
parser.add_argument("--coverage-mode", type=str, required=True)
parser.add_argument("--covered-srcs-dir", type=Path, required=True)
parser.add_argument("--out-srcs-argsfile", type=Path, required=True)
parser.add_argument("--coverage-var-argsfile", type=Path, required=True)
parser.add_argument("srcs", nargs="*", type=Path)
args = parser.parse_args(argv[1:])
out_srcs = []
coverage_vars = {}
args.covered_srcs_dir.mkdir(parents=True)
for src in args.srcs:
if src.name.endswith("_test.go"):
out_srcs.append(src)
else:
var = _var(args.pkg_name, src)
covered_src = args.covered_srcs_dir / src
covered_src.parent.mkdir(parents=True, exist_ok=True)
subprocess.check_call(
[
args.cover,
"-mode",
args.coverage_mode,
"-var",
var,
"-o",
covered_src,
src,
]
)
# we need just the source name for the --cover-pkgs argument
coverage_vars[var] = src.name
out_srcs.append(covered_src)
with open(args.out_srcs_argsfile, mode="w") as f:
for src in out_srcs:
print(src, file=f)
with open(args.coverage_var_argsfile, mode="w") as f:
if coverage_vars:
print("--cover-pkgs", file=f)
print(
"{}:{}".format(
args.pkg_name,
",".join([f"{var}={name}" for var, name in coverage_vars.items()]),
),
file=f,
)
sys.exit(main(sys.argv))

View file

@ -0,0 +1,87 @@
#!/usr/bin/env python3
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
"""
Run on a directory of Go source files and print out a list of srcs that should
be compiled.
Example:
$ ./filter_srcs.py --output srcs.txt src/dir/
"""
# pyre-unsafe
import argparse
import json
import os
import subprocess
import sys
from pathlib import Path
def main(argv):
parser = argparse.ArgumentParser()
parser.add_argument("--go", default="go", type=Path)
parser.add_argument("--tests", action="store_true")
parser.add_argument("--tags", default="")
parser.add_argument("--output", type=argparse.FileType("w"), default=sys.stdout)
parser.add_argument("srcdir", type=Path)
args = parser.parse_args(argv[1:])
# Find all source sub-dirs, which we'll need to run `go list` from.
roots = set()
for root, _dirs, _files in os.walk(args.srcdir):
roots.add(root)
# Run `go list` on all source dirs to filter input sources by build pragmas.
for root in roots:
out = subprocess.check_output(
[
"env",
"-i",
"GOARCH={}".format(os.environ.get("GOARCH", "")),
"GOOS={}".format(os.environ.get("GOOS", "")),
"CGO_ENABLED={}".format(os.environ.get("CGO_ENABLED", "0")),
"GO111MODULE=off",
"GOCACHE=/tmp",
args.go.resolve(),
"list",
"-e",
"-json",
"-tags",
args.tags,
".",
],
cwd=root,
).decode("utf-8")
# Parse JSON output and print out sources.
idx = 0
decoder = json.JSONDecoder()
while idx < len(out) - 1:
# The raw_decode method fails if there are any leading spaces, e.g. " {}" fails
# so manually trim the prefix of the string
if out[idx].isspace():
idx += 1
continue
obj, idx = decoder.raw_decode(out, idx)
if args.tests:
types = ["GoFiles", "TestGoFiles", "XTestGoFiles"]
else:
types = ["GoFiles", "SFiles"]
for typ in types:
for src in obj.get(typ, []):
src = Path(obj["Dir"]) / src
src = src.resolve().relative_to(os.getcwd())
print(src, file=args.output)
sys.exit(main(sys.argv))

View file

@ -0,0 +1,531 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under both the MIT license found in the
* LICENSE-MIT file in the root directory of this source tree and the Apache
* License, Version 2.0 found in the LICENSE-APACHE file in the root directory
* of this source tree.
*/
// Generates testmain.go files from a group of test files.
//
// Regular go tests (ones run with `go test`) don't actually define a main
// package. Moreover, Go's reflection does not have the ability to inspect
// packages (e.g. list functions). This script generates a main.go that
// runs some set of tests passed in on the CLI. The code liberally borrows from
// the `go test` implementation at https://github.com/golang/go/blob/master/src/cmd/go/test.go
package main
import (
"bufio"
"bytes"
"errors"
"flag"
"fmt"
"go/ast"
"go/build"
"go/doc"
"go/parser"
"go/scanner"
"go/token"
"log"
"os"
"path/filepath"
"reflect"
"sort"
"strings"
"text/template"
"unicode"
"unicode/utf8"
)
// A map of: pkg -> [var name -> file name]
type coverPkgFlag map[string]map[string]string
func (c coverPkgFlag) String() string {
var buffer bytes.Buffer
for k, vs := range c {
if len(vs) > 0 {
buffer.WriteString(k)
buffer.WriteString(":")
first := true
for f, v := range vs {
buffer.WriteString(f)
buffer.WriteString("=")
buffer.WriteString(v)
if !first {
buffer.WriteString(",")
}
first = false
}
buffer.WriteString(";")
}
}
return buffer.String()
}
func (c coverPkgFlag) Set(value string) error {
for _, path := range strings.Split(value, ";") {
pkgAndFiles := strings.Split(path, ":")
if len(pkgAndFiles) != 2 {
return errors.New("Bad format: expected path:...;...")
}
pkg := pkgAndFiles[0]
for _, varAndFile := range strings.Split(pkgAndFiles[1], ",") {
varAndFile := strings.Split(varAndFile, "=")
if len(varAndFile) != 2 {
return errors.New("Bad format: expected path:var1=file1,var2=file2,...")
}
if c[pkg] == nil {
c[pkg] = make(map[string]string)
}
c[pkg][varAndFile[0]] = varAndFile[1]
}
}
return nil
}
var testCoverMode string
var coverPkgs = make(coverPkgFlag)
var pkgImportPath = flag.String("import-path", "test", "The import path in the test file")
var outputFile = flag.String("output", "", "The path to the output file. Default to stdout.")
var cwd, _ = os.Getwd()
var testCover bool
var testCoverPaths []string
// Resolve argsfiles in args (e.g. `@file.txt`).
func loadArgs(args []string) []string {
newArgs := make([]string, 0, 0)
for _, arg := range args {
if !strings.HasPrefix(arg, "@") {
newArgs = append(newArgs, arg)
} else {
file, _ := os.Open(arg[1:])
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
newArgs = append(newArgs, scanner.Text())
}
}
}
return newArgs
}
func main() {
os.Args = loadArgs(os.Args)
flag.Var(coverPkgs, "cover-pkgs", "List of packages & coverage variables to gather coverage info on, in the form of IMPORT-PATH1:var1=file1,var2=file2,var3=file3;IMPORT-PATH2:...")
flag.StringVar(&testCoverMode, "cover-mode", "", "Cover mode (see `go tool cover`)")
flag.Parse()
testFuncs, err := loadTestFuncsFromFiles(*pkgImportPath, flag.Args())
if err != nil {
log.Fatalln("Could not read test files:", err)
}
for importPath, vars := range coverPkgs {
cover := coverInfo{&Package{importPath}, make(map[string]*CoverVar)}
for v, f := range vars {
cover.Vars[f] = &CoverVar{File: filepath.Join(importPath, f), Var: v}
}
testFuncs.Cover = append(testFuncs.Cover, cover)
testCoverPaths = append(testCoverPaths, importPath)
}
testCover = testCoverMode != ""
out := os.Stdout
if *outputFile != "" {
out, err = os.Create(*outputFile)
if err != nil {
log.Fatalln("Could not write test main:", err)
}
}
if err := testmainTmpl.Execute(out, testFuncs); err != nil {
log.Fatalln("Failed to generate main file:", err)
}
}
func loadTestFuncsFromFiles(packageImportPath string, files []string) (*testFuncs, error) {
t := &testFuncs{
Package: &Package{
ImportPath: packageImportPath,
},
}
for _, filename := range files {
if err := t.load(filename, "_test", &t.ImportTest, &t.NeedTest); err != nil {
return nil, err
}
}
return t, nil
}
//
//
//
// Most of the code below is a direct copy from the 'go test' command, but
// adapted to support multiple versions of the go stdlib. This was last
// updated for the changes in go1.8. The source for the 'go test' generator
// for go1.8 can be found here:
// https://github.com/golang/go/blob/release-branch.go1.8/src/cmd/go/test.go
//
//
//
// This is a fake version of the actual Package type, since we don't really need all
// ~300 fields of it.
type Package struct {
ImportPath string `json:",omitempty"` // import path of package in dir
}
// isTestFunc tells whether fn has the type of a testing function. arg
// specifies the parameter type we look for: B, M or T.
func isTestFunc(fn *ast.FuncDecl, arg string) bool {
if fn.Type.Results != nil && len(fn.Type.Results.List) > 0 ||
fn.Type.Params.List == nil ||
len(fn.Type.Params.List) != 1 ||
len(fn.Type.Params.List[0].Names) > 1 {
return false
}
ptr, ok := fn.Type.Params.List[0].Type.(*ast.StarExpr)
if !ok {
return false
}
// We can't easily check that the type is *testing.M
// because we don't know how testing has been imported,
// but at least check that it's *M or *something.M.
// Same applies for B and T.
if name, ok := ptr.X.(*ast.Ident); ok && name.Name == arg {
return true
}
if sel, ok := ptr.X.(*ast.SelectorExpr); ok && sel.Sel.Name == arg {
return true
}
return false
}
// isTest tells whether name looks like a test (or benchmark, according to prefix).
// It is a Test (say) if there is a character after Test that is not a lower-case letter.
// We don't want TesticularCancer.
func isTest(name, prefix string) bool {
if !strings.HasPrefix(name, prefix) {
return false
}
if len(name) == len(prefix) { // "Test" is ok
return true
}
rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
return !unicode.IsLower(rune)
}
// shortPath returns an absolute or relative name for path, whatever is shorter.
func shortPath(path string) string {
if rel, err := filepath.Rel(cwd, path); err == nil && len(rel) < len(path) {
return rel
}
return path
}
// expandScanner expands a scanner.List error into all the errors in the list.
// The default Error method only shows the first error.
func expandScanner(err error) error {
// Look for parser errors.
if err, ok := err.(scanner.ErrorList); ok {
// Prepare error with \n before each message.
// When printed in something like context: %v
// this will put the leading file positions each on
// its own line. It will also show all the errors
// instead of just the first, as err.Error does.
var buf bytes.Buffer
for _, e := range err {
e.Pos.Filename = shortPath(e.Pos.Filename)
buf.WriteString("\n")
buf.WriteString(e.Error())
}
return errors.New(buf.String())
}
return err
}
// CoverVar holds the name of the generated coverage variables
// targeting the named file.
type CoverVar struct {
File string // local file name
Var string // name of count struct
}
type coverInfo struct {
Package *Package
Vars map[string]*CoverVar
}
type testFuncs struct {
Tests []testFunc
Benchmarks []testFunc
Examples []testFunc
TestMain *testFunc
Package *Package
ImportTest bool
NeedTest bool
ImportXtest bool
NeedXtest bool
Cover []coverInfo
}
func (t *testFuncs) CoverMode() string {
return testCoverMode
}
func (t *testFuncs) CoverEnabled() bool {
return testCover
}
func (t *testFuncs) ReleaseTag(want string) bool {
for _, r := range build.Default.ReleaseTags {
if want == r {
return true
}
}
return false
}
// ImportPath returns the import path of the package being tested, if it is within GOPATH.
// This is printed by the testing package when running benchmarks.
func (t *testFuncs) ImportPath() string {
pkg := t.Package.ImportPath
if strings.HasPrefix(pkg, "_/") {
return ""
}
if pkg == "command-line-arguments" {
return ""
}
return pkg
}
// Covered returns a string describing which packages are being tested for coverage.
// If the covered package is the same as the tested package, it returns the empty string.
// Otherwise it is a comma-separated human-readable list of packages beginning with
// " in", ready for use in the coverage message.
func (t *testFuncs) Covered() string {
if testCoverPaths == nil {
return ""
}
return " in " + strings.Join(testCoverPaths, ", ")
}
type testFunc struct {
Package string // imported package name (_test or _xtest)
Name string // function name
Output string // output, for examples
Unordered bool // output is allowed to be unordered.
}
var testFileSet = token.NewFileSet()
func (t *testFuncs) load(filename, pkg string, doImport, seen *bool) error {
f, err := parser.ParseFile(testFileSet, filename, nil, parser.ParseComments)
if err != nil {
return expandScanner(err)
}
for _, d := range f.Decls {
n, ok := d.(*ast.FuncDecl)
if !ok {
continue
}
if n.Recv != nil {
continue
}
name := n.Name.String()
switch {
case name == "TestMain" && isTestFunc(n, "M"):
if t.TestMain != nil {
return errors.New("multiple definitions of TestMain")
}
t.TestMain = &testFunc{pkg, name, "", false}
*doImport, *seen = true, true
case isTest(name, "Test") && isTestFunc(n, "T"):
t.Tests = append(t.Tests, testFunc{pkg, name, "", false})
*doImport, *seen = true, true
case isTest(name, "Benchmark"):
err := checkTestFunc(n, "B")
if err != nil {
return err
}
t.Benchmarks = append(t.Benchmarks, testFunc{pkg, name, "", false})
*doImport, *seen = true, true
}
}
ex := doc.Examples(f)
sort.Sort(byOrder(ex))
for _, e := range ex {
*doImport = true // import test file whether executed or not
if e.Output == "" && !e.EmptyOutput {
// Don't run examples with no output.
continue
}
// Go 1.7 and beyond has support for unordered test output on examples.
// We can use reflection to see if the Unordered field is there. This
// can be removed when go 1.6 is not supported by buck.
unordered := false
v := reflect.Indirect(reflect.ValueOf(e))
if f := v.FieldByName("Unordered"); f.IsValid() {
unordered = f.Bool()
}
t.Examples = append(t.Examples, testFunc{pkg, "Example" + e.Name, e.Output, unordered})
*seen = true
}
return nil
}
func checkTestFunc(fn *ast.FuncDecl, arg string) error {
if !isTestFunc(fn, arg) {
name := fn.Name.String()
pos := testFileSet.Position(fn.Pos())
return fmt.Errorf("%s: wrong signature for %s, must be: func %s(%s *testing.%s)", pos, name, name, strings.ToLower(arg), arg)
}
return nil
}
type byOrder []*doc.Example
func (x byOrder) Len() int { return len(x) }
func (x byOrder) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x byOrder) Less(i, j int) bool { return x[i].Order < x[j].Order }
var testmainTmpl = template.Must(template.New("main").Parse(`
package main
import (
"fmt"
"os"
"path/filepath"
"regexp"
"testing"
{{if .ReleaseTag "go1.8"}}
internalTestDeps "testing/internal/testdeps"
{{end}}
{{if .ImportTest}}
{{if .NeedTest}}_test{{else}}_{{end}} {{.Package.ImportPath | printf "%q"}}
{{end}}
{{if .ImportXtest}}
{{if .NeedXtest}}_xtest{{else}}_{{end}} {{.Package.ImportPath | printf "%s_test" | printf "%q"}}
{{end}}
{{range $i, $p := .Cover}}
_cover{{$i}} {{$p.Package.ImportPath | printf "%q"}}
{{end}}
)
var tests = []testing.InternalTest{
{{range .Tests}}
{"{{.Name}}", {{.Package}}.{{.Name}}},
{{end}}
}
var benchmarks = []testing.InternalBenchmark{
{{range .Benchmarks}}
{"{{.Name}}", {{.Package}}.{{.Name}}},
{{end}}
}
{{if .ReleaseTag "go1.18"}}
var fuzzTargets = []testing.InternalFuzzTarget{
}
{{end}}
var examples = []testing.InternalExample{
{{range .Examples}}
{"{{.Name}}", {{.Package}}.{{.Name}}, {{.Output | printf "%q"}}{{if $.ReleaseTag "go1.7"}}, {{.Unordered}}{{end}}},
{{end}}
}
var matchPat string
var matchRe *regexp.Regexp
func matchString(pat, str string) (result bool, err error) {
if matchRe == nil || matchPat != pat {
matchPat = pat
matchRe, err = regexp.Compile(matchPat)
if err != nil {
return
}
}
return matchRe.MatchString(str), nil
}
{{if .CoverEnabled}}
// Only updated by init functions, so no need for atomicity.
var (
coverCounters = make(map[string][]uint32)
coverBlocks = make(map[string][]testing.CoverBlock)
)
func init() {
{{range $i, $p := .Cover}}
{{range $file, $cover := $p.Vars}}
coverRegisterFile({{printf "%q" $cover.File}}, _cover{{$i}}.{{$cover.Var}}.Count[:], _cover{{$i}}.{{$cover.Var}}.Pos[:], _cover{{$i}}.{{$cover.Var}}.NumStmt[:])
{{end}}
{{end}}
}
func coverRegisterFile(fileName string, counter []uint32, pos []uint32, numStmts []uint16) {
if 3*len(counter) != len(pos) || len(counter) != len(numStmts) {
panic("coverage: mismatched sizes")
}
if coverCounters[fileName] != nil {
// Already registered.
return
}
coverCounters[fileName] = counter
block := make([]testing.CoverBlock, len(counter))
for i := range counter {
block[i] = testing.CoverBlock{
Line0: pos[3*i+0],
Col0: uint16(pos[3*i+2]),
Line1: pos[3*i+1],
Col1: uint16(pos[3*i+2]>>16),
Stmts: numStmts[i],
}
}
coverBlocks[fileName] = block
}
{{end}}
func main() {
// Buck ensures that resources defined on the test targets live in the same
// directory as the binary. We change the working directory to this
// directory to make sure that tests can read test fixtures relative to the
// current working directory. This matches behavior with "go test" from the
// test author perspective.
execPath, err := os.Executable()
if err != nil {
os.Stderr.WriteString("Unable to get path to test binary executable.")
os.Exit(1)
}
execDir := filepath.Dir(execPath)
err = os.Chdir(execDir)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to change directory to %s.", execDir)
os.Exit(1)
}
{{if .CoverEnabled}}
testing.RegisterCover(testing.Cover{
Mode: {{printf "%q" .CoverMode}},
Counters: coverCounters,
Blocks: coverBlocks,
CoveredPackages: {{printf "%q" .Covered}},
})
{{end}}
{{if .ReleaseTag "go1.18"}}
m := testing.MainStart(internalTestDeps.TestDeps{}, tests, benchmarks, fuzzTargets, examples)
{{else if .ReleaseTag "go1.8"}}
m := testing.MainStart(internalTestDeps.TestDeps{}, tests, benchmarks, examples)
{{else}}
m := testing.MainStart(matchString, tests, benchmarks, examples)
{{end}}
{{with .TestMain}}
{{.Package}}.{{.Name}}(m)
{{else}}
os.Exit(m.Run())
{{end}}
}
`))