blob: f4fbf289e7ff60e3d54488a41609ea8dd04423f8 [file] [log] [blame]
# 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("@io_bazel_rules_go//go/private:common.bzl", "dict_of", "split_srcs", "join_srcs", "pkg_dir")
load("@io_bazel_rules_go//go/private:providers.bzl", "GoLibrary")
def _cgo_select_go_files_impl(ctx):
return struct(files = ctx.attr.dep.go_files)
_cgo_select_go_files = rule(_cgo_select_go_files_impl, attrs = {"dep": attr.label()})
def _cgo_select_main_c_impl(ctx):
return struct(files = ctx.attr.dep.main_c)
_cgo_select_main_c = rule(_cgo_select_main_c_impl, attrs = {"dep": attr.label()})
def _mangle(ctx, src):
src_stem, _, src_ext = src.path.rpartition('.')
mangled_stem = ctx.attr.out_dir + "/" + src_stem.replace('/', '_')
return mangled_stem, src_ext
def _c_filter_options(options, blacklist):
return [opt for opt in options
if not any([opt.startswith(prefix) for prefix in blacklist])]
def _cgo_codegen_impl(ctx):
go_toolchain = ctx.toolchains["@io_bazel_rules_go//go:toolchain"]
if not go_toolchain.external_linker:
fail("Go toolchain does not support cgo")
linkopts = ctx.attr.linkopts[:]
copts = go_toolchain.external_linker.c_options + ctx.attr.copts
deps = depset([], order="topological")
cgo_export_h = ctx.new_file(ctx.attr.out_dir + "/_cgo_export.h")
cgo_export_c = ctx.new_file(ctx.attr.out_dir + "/_cgo_export.c")
cgo_main = ctx.new_file(ctx.attr.out_dir + "/_cgo_main.c")
cgo_types = ctx.new_file(ctx.attr.out_dir + "/_cgo_gotypes.go")
out_dir = cgo_main.dirname
cc = go_toolchain.external_linker.compiler_executable
args = [go_toolchain.tools.go.path, "-cc", str(cc), "-objdir", out_dir]
c_outs = depset([cgo_export_h, cgo_export_c])
go_outs = depset([cgo_types])
source = split_srcs(ctx.files.srcs)
for src in source.headers:
copts += ['-iquote', src.dirname]
for src in source.go:
mangled_stem, src_ext = _mangle(ctx, src)
gen_file = ctx.new_file(mangled_stem + ".cgo1."+src_ext)
gen_c_file = ctx.new_file(mangled_stem + ".cgo2.c")
go_outs += [gen_file]
c_outs += [gen_c_file]
args += ["-src", gen_file.path + "=" + src.path]
for src in source.asm:
mangled_stem, src_ext = _mangle(ctx, src)
gen_file = ctx.new_file(mangled_stem + ".cgo1."+src_ext)
go_outs += [gen_file]
args += ["-src", gen_file.path + "=" + src.path]
for src in source.c:
mangled_stem, src_ext = _mangle(ctx, src)
gen_file = ctx.new_file(mangled_stem + ".cgo1."+src_ext)
c_outs += [gen_file]
args += ["-src", gen_file.path + "=" + src.path]
inputs = ctx.files.srcs + go_toolchain.data.tools + go_toolchain.data.crosstool
for d in ctx.attr.deps:
inputs += list(d.cc.transitive_headers)
deps += d.cc.libs
copts += ['-D' + define for define in d.cc.defines]
for inc in d.cc.include_directories:
copts += ['-I', inc]
for inc in d.cc.quote_include_directories:
copts += ['-iquote', inc]
for inc in d.cc.system_include_directories:
copts += ['-isystem', inc]
for lib in d.cc.libs:
if lib.basename.startswith('lib') and lib.basename.endswith('.so'):
linkopts += ['-L', lib.dirname, '-l', lib.basename[3:-3]]
else:
linkopts += [lib.path]
linkopts += d.cc.link_flags
# The first -- below is to stop the cgo from processing args, the
# second is an actual arg to forward to the underlying go tool
args += ["--", "--"] + copts
ctx.action(
inputs = inputs,
outputs = list(c_outs + go_outs + [cgo_main]),
mnemonic = "CGoCodeGen",
progress_message = "CGoCodeGen %s" % ctx.label,
executable = go_toolchain.tools.cgo,
arguments = args,
env = go_toolchain.env + {
"CGO_LDFLAGS": " ".join(linkopts),
},
)
return struct(
label = ctx.label,
files = c_outs + source.headers,
go_files = go_outs,
main_c = depset([cgo_main]),
cgo_deps = deps,
)
_cgo_codegen_rule = rule(
_cgo_codegen_impl,
attrs = {
"srcs": attr.label_list(allow_files = True),
"deps": attr.label_list(
allow_files = False,
providers = ["cc"],
),
"copts": attr.string_list(),
"linkopts": attr.string_list(),
"out_dir": attr.string(mandatory = True),
},
toolchains = ["@io_bazel_rules_go//go:toolchain"],
)
def _cgo_import_impl(ctx):
#TODO: move the dynpackage part into the cgo wrapper so we can stop using shell
go_toolchain = ctx.toolchains["@io_bazel_rules_go//go:toolchain"]
command = (
go_toolchain.tools.go.path + " tool cgo" +
" -dynout " + ctx.outputs.out.path +
" -dynimport " + ctx.file.cgo_o.path +
" -dynpackage $(%s %s)" % (go_toolchain.tools.extract_package.path,
ctx.files.sample_go_srcs[0].path)
)
ctx.action(
inputs = (go_toolchain.data.tools +
[go_toolchain.tools.go, go_toolchain.tools.extract_package,
ctx.file.cgo_o, ctx.files.sample_go_srcs[0]]),
outputs = [ctx.outputs.out],
command = command,
mnemonic = "CGoImportGen",
env = go_toolchain.env,
)
return struct(
files = depset([ctx.outputs.out]),
)
_cgo_import = rule(
_cgo_import_impl,
attrs = {
"cgo_o": attr.label(
allow_files = True,
single_file = True,
),
"sample_go_srcs": attr.label_list(allow_files = True),
"out": attr.output(
mandatory = True,
),
},
toolchains = ["@io_bazel_rules_go//go:toolchain"],
)
"""Generates symbol-import directives for cgo
Args:
cgo_o: The loadable object to extract dynamic symbols from.
sample_go_src: A go source which is compiled together with the generated file.
The generated file will have the same Go package name as this file.
out: Destination of the generated codes.
"""
def _cgo_object_impl(ctx):
go_toolchain = ctx.toolchains["@io_bazel_rules_go//go:toolchain"]
if not go_toolchain.external_linker:
fail("Go toolchain does not support cgo")
options = go_toolchain.external_linker.options
arguments = _c_filter_options(options, blacklist=[
# never link any dependency libraries
"-l", "-L",
# manage flags to ld(1) by ourselves
"-Wl,"])
arguments += [
"-o", ctx.outputs.out.path,
"-nostdlib",
"-Wl,-r",
] + go_toolchain.flags.link_cgo
lo = ctx.files.src[-1]
arguments += [lo.path]
ctx.action(
inputs = [lo] + go_toolchain.data.crosstool,
outputs = [ctx.outputs.out],
mnemonic = "CGoObject",
progress_message = "Linking %s" % ctx.outputs.out.short_path,
executable = go_toolchain.external_linker.compiler_executable,
arguments = arguments,
)
runfiles = ctx.runfiles(collect_data = True)
runfiles = runfiles.merge(ctx.attr.src.data_runfiles)
return struct(
files = depset([ctx.outputs.out]),
cgo_obj = ctx.outputs.out,
cgo_deps = ctx.attr.cgogen.cgo_deps,
runfiles = runfiles,
)
_cgo_object = rule(
_cgo_object_impl,
attrs = {
"src": attr.label(
mandatory = True,
providers = ["cc"],
),
"cgogen": attr.label(
mandatory = True,
providers = ["cgo_deps"],
),
"out": attr.output(
mandatory = True,
),
},
toolchains = ["@io_bazel_rules_go//go:toolchain"],
)
"""Generates _all.o to be archived together with Go objects.
Args:
src: source static library which contains objects
cgogen: _cgo_codegen rule which knows the dependency cc_library() rules
to be linked together with src when we generate the final go binary.
"""
def setup_cgo_library(name, srcs, cdeps, copts, clinkopts):
cgo_codegen_dir = name + ".cgo.dir"
# Apply build constraints to source files (both Go and C) but not to header
# files. Separate filtered Go and C sources.
# Run cgo on the filtered Go files. This will split them into pure Go files
# and pure C files, plus a few other glue files.
base_dir = pkg_dir(
"external/" + REPOSITORY_NAME[1:] if len(REPOSITORY_NAME) > 1 else "",
PACKAGE_NAME)
copts = copts + ["-I", base_dir]
cgo_codegen_name = name + ".cgo_codegen"
_cgo_codegen_rule(
name = cgo_codegen_name,
srcs = srcs,
deps = cdeps,
copts = copts,
linkopts = clinkopts,
out_dir = cgo_codegen_dir,
visibility = ["//visibility:private"],
)
select_go_files = name + ".select_go_files"
_cgo_select_go_files(
name = select_go_files,
dep = cgo_codegen_name,
visibility = ["//visibility:private"],
)
select_main_c = name + ".select_main_c"
_cgo_select_main_c(
name = select_main_c,
dep = cgo_codegen_name,
visibility = ["//visibility:private"],
)
# Compile C sources and generated files into a library. This will be linked
# into binaries that depend on this cgo_library. It will also be used
# in _cgo_.o.
platform_copts = select({
"@io_bazel_rules_go//go/platform:windows_amd64": ["-mthreads"],
"//conditions:default": ["-pthread"],
})
platform_linkopts = platform_copts
cgo_lib_name = name + ".cgo_c_lib"
native.cc_library(
name = cgo_lib_name,
srcs = [cgo_codegen_name],
deps = cdeps,
copts = copts + platform_copts + [
"-I", "$(BINDIR)/" + base_dir + "/" + cgo_codegen_dir,
# The generated thunks often contain unused variables.
"-Wno-unused-variable",
],
linkopts = clinkopts + platform_linkopts,
linkstatic = 1,
# _cgo_.o and _all.o keep all objects in this archive.
# But it should not be very annoying in the final binary target
# because _cgo_object rule does not propagate alwayslink=1
alwayslink = 1,
visibility = ["//visibility:private"],
)
# Create a loadable object with no undefined references. cgo reads this
# when it generates _cgo_import.go.
cgo_o_name = name + "._cgo_.o"
native.cc_binary(
name = cgo_o_name,
srcs = [select_main_c],
deps = cdeps + [cgo_lib_name],
copts = copts,
linkopts = clinkopts,
visibility = ["//visibility:private"],
)
# Create a Go file which imports symbols from the C library.
cgo_import_name = name + ".cgo_import"
_cgo_import(
name = cgo_import_name,
cgo_o = cgo_o_name,
sample_go_srcs = [select_go_files],
out = cgo_codegen_dir + "/_cgo_import.go",
visibility = ["//visibility:private"],
)
# Link the library into a relocatable .o file that can be linked into the
# final binary.
all_o_name = name + "._all.o"
_cgo_object(
name = all_o_name,
src = cgo_lib_name,
out = cgo_codegen_dir + "/_all.o",
cgogen = cgo_codegen_name,
visibility = ["//visibility:private"],
)
return struct(
name = name,
go_srcs = [
select_go_files,
cgo_import_name,
],
cgo_object = all_o_name,
)