Files
allwpilib/shared/bazel/rules/cc_rules.bzl
Austin Schuh 60098b0685 [bazel] Build a single maven artifact bundle (#8049)
Signed-off-by: Austin Schuh <austin.linux@gmail.com>
Co-authored-by: PJ Reiniger <pj.reiniger@gmail.com>
Co-authored-by: David Vo <auscompgeek@users.noreply.github.com>
2025-07-22 14:26:20 -06:00

403 lines
15 KiB
Python

load("@build_bazel_apple_support//rules:universal_binary.bzl", "universal_binary")
load("@rules_cc//cc:action_names.bzl", "CPP_LINK_STATIC_LIBRARY_ACTION_NAME")
load("@rules_cc//cc:cc_shared_library.bzl", "cc_shared_library")
load("@rules_cc//cc:defs.bzl", "CcInfo", "cc_library")
load("@rules_cc//cc:find_cc_toolchain.bzl", "CC_TOOLCHAIN_ATTRS", "find_cpp_toolchain", "use_cc_toolchain")
load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
load("@rules_pkg//:mappings.bzl", "pkg_files")
load("@rules_pkg//:pkg.bzl", "pkg_zip")
def _folder_prefix(name):
if "/" in name:
last_slash = name.rfind("/")
return (name[0:last_slash], name[last_slash + 1:])
else:
return ("", name)
def third_party_cc_lib_helper(
name,
include_root,
src_root = None,
src_excludes = [],
visibility = None):
"""
Helper for src / headers pairs that aren't directly compiled, but rather pulled into a bigger library.
Due to allwpilibs directory structure of includes and sources living next to eachother, it often is required
to make a header shim to deal with the include path, and a filegroup of the sources. This pattern is extermely
common for the thirdparty libraries that live beneath certain libraries.
This will produce a library shim with the include path stripped, a filegroup of sources, and packages that can be
used to downstream to zip headers / sources with their "parent" library.
Params
include_root: The package relative path to the header files. This will be used to glob the files and strip the include prefix
src_root: Optional. The package relative path to the source files.
src_excludes: Optional. Used to exclude files from the src_root glob
visibilty: The visibility of header shim / source files / package files
"""
cc_library(
name = name + "-headers",
hdrs = native.glob([
include_root + "/**",
]),
includes = [include_root],
strip_include_prefix = include_root,
visibility = visibility,
)
pkg_files(
name = name + "-hdrs-pkg",
srcs = native.glob([include_root + "/**"]),
strip_prefix = include_root,
)
if src_root:
native.filegroup(
name = name + "-srcs",
srcs = native.glob([src_root + "/**"], exclude = src_excludes),
visibility = visibility,
)
pkg_files(
name = name + "-srcs-pkg",
srcs = native.glob([src_root + "/**"]),
strip_prefix = src_root,
)
def wpilib_cc_library(
name,
srcs = [],
hdrs = [],
deps = [],
copts = [],
third_party_libraries = [],
third_party_header_only_libraries = [],
extra_src_pkg_files = [],
extra_hdr_pkg_files = [],
include_license_files = False,
srcs_pkg_root = "src/main/native/cpp",
hdrs_pkg_root = "src/main/native/include",
strip_include_prefix = None,
linkopts = None,
**kwargs):
"""
This function is used to ease the creation of a cc_library with publishing given the standard allwpilib directory structure.
This will create a cc_library as well as automatically create header, source, and library artifacts that can be used for publishing. This
also provides some syntactic sugar for third party library shims declared by third_party_cc_lib_helper.
Important outputs:
":name" - The cc_library
name + "-srcs-zip" - A zip file containing all the exported sources
name + "-hdrs-zip" - A zip file containing all the exported headers
name + "-zip" - A zip file that contains the compiled library
Params:
srcs: The sources used to compile the library. Note: This may be platform dependent and not include all the sources of the library for packaging
hdrs: The headers used to compile the library. Note: This may be platform dependent and not include all the sources of the library for packaging
third_party_libraries: These are helper dependencies, created by third_party_cc_lib_helper. Header shims will be added as deps and src filegroups will be added to srcs
third_party_header_only_libraries: Similar to third_party_libraries, but for shims that contain no sources
extra_src_pkg_files: Extra pkg_files to add to the source bundle. This is useful in the event that a library is complicated and requires
extra, customized sources to be added to the published zip file
extra_hdr_pkg_files: Extra pkg_files to add to the headers bundle. This is useful in the event that a library is complicated and requires
extra, customized headers to be added to the published zip file
include_license_files: If the header / source / library zip files should automatically includes the license files. This is used to maintain
consistency with the gradle publishing, as not all of them export the license files.
"""
maybe_license_pkg = ["//:license_pkg_files"] if include_license_files else []
cc_library(
name = name + "-headers",
hdrs = hdrs,
deps = [lib + "-headers" for lib in third_party_libraries + third_party_header_only_libraries],
strip_include_prefix = strip_include_prefix,
**kwargs
)
cc_library(
name = name,
hdrs = hdrs,
copts = copts,
srcs = srcs + [lib + "-srcs" for lib in third_party_libraries],
deps = deps + [lib + "-headers" for lib in third_party_libraries + third_party_header_only_libraries],
strip_include_prefix = strip_include_prefix,
**kwargs
)
if srcs_pkg_root:
pkg_files(
name = name + "-srcs-pkg",
srcs = native.glob([srcs_pkg_root + "/**"]),
strip_prefix = srcs_pkg_root,
)
pkg_zip(
name = name + "-srcs-zip",
srcs = maybe_license_pkg + extra_src_pkg_files + [name + "-srcs-pkg"] + [lib + "-srcs-pkg" for lib in third_party_libraries],
tags = ["no-remote"],
)
if hdrs_pkg_root:
pkg_files(
name = name + "-hdrs-pkg",
srcs = native.glob([hdrs_pkg_root + "/**"]),
strip_prefix = hdrs_pkg_root,
)
pkg_zip(
name = name + "-hdrs-zip",
srcs = extra_hdr_pkg_files + maybe_license_pkg + [name + "-hdrs-pkg"] + [lib + "-hdrs-pkg" for lib in third_party_libraries + third_party_header_only_libraries],
tags = ["no-remote"],
)
def wpilib_cc_shared_library(
name,
auto_export_windows_symbols = True,
**kwargs):
folder, lib = _folder_prefix(name)
features = []
if auto_export_windows_symbols:
features.append("windows_export_all_symbols")
cc_shared_library(
name = name,
features = features,
**kwargs
)
universal_name = "universal/lib" + lib + ".lib"
universal_binary(
name = universal_name,
binary = name,
target_compatible_with = [
"@platforms//os:osx",
],
)
pkg_files(
name = folder + "/lib" + lib + "-shared-files",
srcs = select({
"@rules_bzlmodrio_toolchains//conditions:osx": [universal_name],
"//conditions:default": [
":" + name,
],
}),
strip_prefix = select({
"@rules_bzlmodrio_toolchains//conditions:osx": "universal",
"//conditions:default": folder,
}),
)
CcStaticLibraryInfo = provider(
"Information about a cc static library.",
fields = {
"linker_input": "the resulting linker input artifact for the static library",
"used_objects": "the object files already accounted for",
},
)
def _accumulate_used_objects(ctx):
transitive_used_objects = []
for dep in ctx.attr.static_deps:
transitive_used_objects.append(dep[CcStaticLibraryInfo].used_objects)
return transitive_used_objects
def _filter_inputs(
ctx,
feature_configuration,
cc_toolchain,
deps,
used_objects):
dependency_linker_inputs_sets = []
for dep in deps:
dependency_linker_inputs_sets.append(dep[CcInfo].linking_context.linker_inputs)
dependency_linker_inputs = depset(transitive = dependency_linker_inputs_sets, order = "topological").to_list()
used_objects_depset = depset(transitive = used_objects, order = "topological").to_list()
linker_inputs = []
for linker_input in dependency_linker_inputs:
for lib in linker_input.libraries:
if lib.pic_objects:
for o in lib.pic_objects:
if o not in used_objects_depset:
linker_inputs.append(o)
elif lib.objects:
for o in lib.objects:
if o not in used_objects_depset:
linker_inputs.append(o)
return sorted(linker_inputs)
def _cc_static_library_impl(ctx):
"""
This is a modified version of built in cc_static_library implementation
https://github.com/bazelbuild/bazel/blob/8.2.1/src/main/starlark/builtins_bzl/common/cc/experimental_cc_static_library.bzl
The built in version amalgamates all of the transative dependency objects into a single shared library. However, we do not want our
static libraries to only have the symbols related to the objects for this library, and not anything transative. In order to do this,
we add the option to specify transative static_libraries. The rule then filters out the objects that are defines in the other static
libraries.
"""
deps = ctx.attr.deps
cc_toolchain = find_cpp_toolchain(ctx)
feature_configuration = cc_common.configure_features(
ctx = ctx,
cc_toolchain = cc_toolchain,
requested_features = ctx.features + ["force_no_whole_archive"],
unsupported_features = ctx.disabled_features,
)
# Find all the objects which are already in another static library.
used_objects = _accumulate_used_objects(ctx)
# Now, find the ones we depend on which aren't.
libs = _filter_inputs(
ctx,
feature_configuration,
cc_toolchain,
deps,
used_objects,
)
used_objects_depset = depset(direct = libs, transitive = used_objects, order = "topological")
# Generate the output library name if one isn't provided.
output_file = ctx.actions.declare_file(ctx.attr.static_lib_name)
# And, now do it.
linker_input = cc_common.create_linker_input(
owner = ctx.label,
libraries = depset(direct = [
cc_common.create_library_to_link(
actions = ctx.actions,
feature_configuration = feature_configuration,
cc_toolchain = cc_toolchain,
static_library = output_file,
),
]),
)
compilation_context = cc_common.create_compilation_context()
linking_context = cc_common.create_linking_context(linker_inputs = depset(direct = [linker_input], order = "topological"))
archiver_path = cc_common.get_tool_for_action(
feature_configuration = feature_configuration,
action_name = CPP_LINK_STATIC_LIBRARY_ACTION_NAME,
)
archiver_variables = cc_common.create_link_variables(
feature_configuration = feature_configuration,
cc_toolchain = cc_toolchain,
output_file = output_file.path,
is_using_linker = False,
)
command_line = cc_common.get_memory_inefficient_command_line(
feature_configuration = feature_configuration,
action_name = CPP_LINK_STATIC_LIBRARY_ACTION_NAME,
variables = archiver_variables,
)
args = ctx.actions.args()
args.add_all(command_line)
args.add_all(libs)
if cc_common.is_enabled(
feature_configuration = feature_configuration,
feature_name = "archive_param_file",
):
# TODO: The flag file arg should come from the toolchain instead.
args.use_param_file("@%s", use_always = True)
env = cc_common.get_environment_variables(
feature_configuration = feature_configuration,
action_name = CPP_LINK_STATIC_LIBRARY_ACTION_NAME,
variables = archiver_variables,
)
ctx.actions.run(
executable = archiver_path,
arguments = [args],
env = env,
inputs = depset(
direct = libs,
transitive = [
cc_toolchain.all_files,
],
),
outputs = [output_file],
)
cc_info = cc_common.merge_cc_infos(cc_infos = [
CcInfo(compilation_context = compilation_context, linking_context = linking_context),
] + [dep[CcInfo] for dep in ctx.attr.deps])
# TODO(austin): Do we want this to be able to link into a binary? Probably... Need to figure out what the right result needs to be for that.
return [
cc_info,
DefaultInfo(
files = depset([output_file]),
),
CcStaticLibraryInfo(
used_objects = used_objects_depset,
linker_input = linker_input,
),
]
_wpilib_cc_static_library = rule(
implementation = _cc_static_library_impl,
attrs = {
"deps": attr.label_list(
providers = [CcInfo],
doc = """
List of all the dependencies to accumulate objects from to link into this static library.
""",
),
"static_deps": attr.label_list(
providers = [CcStaticLibraryInfo],
doc = """
List of all static libraries to not duplicate .o files from.
""",
),
"static_lib_name": attr.string(doc = """
By default cc_static_library will use a name for the static library output file based on
the target's name and the platform. This includes an extension and sometimes a prefix.
Sometimes you may not want the default name, in which case you can use this
attribute to choose a custom name."""),
} | CC_TOOLCHAIN_ATTRS,
toolchains = use_cc_toolchain(),
fragments = ["cpp"],
)
def wpilib_cc_static_library(
name,
static_lib_name = None,
**kwargs):
if not static_lib_name:
folder, lib = _folder_prefix(name)
static_lib_name = select({
"@bazel_tools//src/conditions:windows": folder + "/" + lib + ".lib",
"//conditions:default": folder + "/lib" + lib + ".a",
})
_wpilib_cc_static_library(
name = name,
static_lib_name = static_lib_name,
**kwargs
)
pkg_files(
name = name + "-static.pkg",
srcs = [":" + name],
tags = ["manual"],
)
pkg_zip(
name = name + "-static-zip",
srcs = ["//:license_pkg_files", name + "-static.pkg"],
tags = ["no-remote", "manual"],
)