blob: 057a8a5a9d570945c7b75ec58e97cbff8a14c54c [file]
"""Linking logic copied from rules_apple"""
load("@bazel_skylib//lib:paths.bzl", "paths")
load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
load("@rules_cc//cc/common:cc_info.bzl", "CcInfo")
load("@rules_cc//cc/private/rules_impl:objc_compilation_support.bzl", "compilation_support") # buildifier: disable=bzl-visibility
load(":cc_toolchain_forwarder.bzl", "CcWrapperInfo", "TestApplePlatformInfo")
# TODO: Delete once we drop 8.x
_USE_OLD_API = not hasattr(cc_common, "objc_expand_and_tokenize")
def _build_avoid_library_set(avoid_dep_linking_contexts):
avoid_library_set = dict()
for linking_context in avoid_dep_linking_contexts:
for linker_input in linking_context.linker_inputs.to_list():
for library_to_link in linker_input.libraries:
library_artifact = compilation_support.get_static_library_for_linking(library_to_link)
if library_artifact:
avoid_library_set[library_artifact.short_path] = True
return avoid_library_set
def _subtract_linking_contexts(owner, linking_contexts, avoid_dep_linking_contexts):
libraries = []
user_link_flags = []
additional_inputs = []
linkstamps = []
avoid_library_set = _build_avoid_library_set(avoid_dep_linking_contexts)
for linking_context in linking_contexts:
for linker_input in linking_context.linker_inputs.to_list():
for library_to_link in linker_input.libraries:
library_artifact = _get_library_for_linking(library_to_link)
if library_artifact.short_path not in avoid_library_set:
libraries.append(library_to_link)
user_link_flags.extend(linker_input.user_link_flags)
additional_inputs.extend(linker_input.additional_inputs)
linkstamps.extend(linker_input.linkstamps)
linker_input = cc_common.create_linker_input(
owner = owner,
libraries = depset(libraries, order = "topological"),
user_link_flags = user_link_flags,
additional_inputs = depset(additional_inputs),
linkstamps = depset(linkstamps),
)
return cc_common.create_linking_context(
linker_inputs = depset([linker_input]),
)
def _get_library_for_linking(library_to_link):
if library_to_link.static_library:
return library_to_link.static_library
elif library_to_link.pic_static_library:
return library_to_link.pic_static_library
elif library_to_link.interface_library:
return library_to_link.interface_library
else:
return library_to_link.dynamic_library
def _get_libraries_for_linking(libraries_to_link):
libraries = []
for library_to_link in libraries_to_link:
libraries.append(_get_library_for_linking(library_to_link))
return libraries
def _libraries_from_linking_context(linking_context):
libraries = []
for linker_input in linking_context.linker_inputs.to_list():
libraries.extend(linker_input.libraries)
return depset(libraries, order = "topological")
def _classify_libraries(libraries_to_link):
always_link_libraries = {
lib: None
for lib in _get_libraries_for_linking(
[lib for lib in libraries_to_link if lib.alwayslink],
)
}
as_needed_libraries = {
lib: None
for lib in _get_libraries_for_linking(
[lib for lib in libraries_to_link if not lib.alwayslink],
)
if lib not in always_link_libraries
}
return always_link_libraries.keys(), as_needed_libraries.keys()
def _linkstamp_map(ctx, linkstamps, output, build_config):
# create linkstamps_map - mapping from linkstamps to object files
linkstamps_map = {}
stamp_output_dir = paths.join(ctx.label.package, "_objs", output.basename)
for linkstamp in linkstamps:
linkstamp_file = linkstamp.file()
stamp_output_path = paths.join(
stamp_output_dir,
linkstamp_file.short_path[:-len(linkstamp_file.extension)].rstrip(".") + ".o",
)
stamp_output_file = ctx.actions.declare_shareable_artifact(
stamp_output_path,
build_config.bin_dir,
)
linkstamps_map[linkstamp_file] = stamp_output_file
return linkstamps_map
def _dedup_link_flags(flags, seen_flags = {}):
new_flags = []
previous_arg = None
for arg in flags:
if previous_arg in ["-framework", "-weak_framework"]:
framework = arg
key = previous_arg[1] + framework
if key not in seen_flags:
new_flags.extend([previous_arg, framework])
seen_flags[key] = True
previous_arg = None
elif arg in ["-framework", "-weak_framework"]:
previous_arg = arg
elif arg.startswith("-Wl,-framework,") or arg.startswith("-Wl,-weak_framework,"):
framework = arg.split(",")[2]
key = arg[5] + framework
if key not in seen_flags:
new_flags.extend([arg.split(",")[1], framework])
seen_flags[key] = True
elif arg.startswith("-Wl,-rpath,"):
rpath = arg.split(",")[2]
key = arg[5] + rpath
if key not in seen_flags:
new_flags.append(arg)
seen_flags[key] = True
elif arg.startswith("-l"):
if arg not in seen_flags:
new_flags.append(arg)
seen_flags[arg] = True
else:
new_flags.append(arg)
same = (
len(flags) == len(new_flags) and
all([flags[i] == new_flags[i] for i in range(0, len(flags))])
)
return (same, new_flags, seen_flags)
def _register_obj_filelist_action(ctx, build_config, obj_files):
obj_list = ctx.actions.declare_shareable_artifact(
paths.join(ctx.label.package, ctx.label.name + "-linker.objlist"),
build_config.bin_dir,
)
args = ctx.actions.args()
args.add_all(obj_files)
args.set_param_file_format("multiline")
ctx.actions.write(obj_list, args)
return obj_list
def _create_deduped_linkopts_list(linker_inputs):
seen_flags = {}
final_linkopts = []
for linker_input in linker_inputs.to_list():
(_, new_flags, seen_flags) = _dedup_link_flags(
linker_input.user_link_flags,
seen_flags,
)
final_linkopts.extend(new_flags)
return final_linkopts
def _emit_builtin_objc_strip_action(ctx):
return (
ctx.fragments.cpp.objc_enable_binary_stripping() and
ctx.fragments.cpp.compilation_mode() == "opt"
)
def _apple_common_platform_from_platform_info(*, apple_platform_info):
if apple_platform_info.target_os == "ios":
if apple_platform_info.target_environment == "device":
return apple_common.platform.ios_device
elif apple_platform_info.target_environment == "simulator":
return apple_common.platform.ios_simulator
elif apple_platform_info.target_os == "macos":
return apple_common.platform.macos
elif apple_platform_info.target_os == "tvos":
if apple_platform_info.target_environment == "device":
return apple_common.platform.tvos_device
elif apple_platform_info.target_environment == "simulator":
return apple_common.platform.tvos_simulator
elif apple_platform_info.target_os == "visionos":
if apple_platform_info.target_environment == "device":
return apple_common.platform.visionos_device
elif apple_platform_info.target_environment == "simulator":
return apple_common.platform.visionos_simulator
elif apple_platform_info.target_os == "watchos":
if apple_platform_info.target_environment == "device":
return apple_common.platform.watchos_device
elif apple_platform_info.target_environment == "simulator":
return apple_common.platform.watchos_simulator
fail("Internal Error: Found unrecognized target os of " + apple_platform_info.target_os)
def _register_binary_strip_action(
ctx,
name,
binary,
feature_configuration,
apple_platform_info,
extra_link_args):
strip_safe = ctx.fragments.objc.strip_executable_safely
# For dylibs, loadable bundles, and kexts, must strip only local symbols.
link_dylib = cc_common.is_enabled(
feature_configuration = feature_configuration,
feature_name = "link_dylib",
)
link_bundle = cc_common.is_enabled(
feature_configuration = feature_configuration,
feature_name = "link_bundle",
)
if ("-dynamiclib" in extra_link_args or link_dylib or
"-bundle" in extra_link_args or link_bundle or "-kext" in extra_link_args):
strip_safe = True
stripped_binary = ctx.actions.declare_shareable_artifact(
paths.join(ctx.label.package, name),
apple_platform_info.target_build_config.bin_dir,
)
args = ctx.actions.args()
args.add("strip")
if strip_safe:
args.add("-x")
args.add("-o", stripped_binary)
args.add(binary)
xcode_config = ctx.attr._xcode_config[apple_common.XcodeVersionConfig]
platform = _apple_common_platform_from_platform_info(apple_platform_info = apple_platform_info)
ctx.actions.run(
mnemonic = "ObjcBinarySymbolStrip",
executable = "/usr/bin/xcrun",
arguments = [args],
inputs = [binary],
outputs = [stripped_binary],
execution_requirements = ctx.attr._xcode_config[apple_common.XcodeVersionConfig].execution_info(),
env = apple_common.apple_host_system_env(xcode_config) |
apple_common.target_apple_env(xcode_config, platform),
)
return stripped_binary
def _create_deduped_linkopts_linking_context(cc_linking_context, seen_flags):
linker_inputs = []
for linker_input in cc_linking_context.linker_inputs.to_list():
(same, new_flags, seen_flags) = _dedup_link_flags(
linker_input.user_link_flags,
seen_flags,
)
if same:
linker_inputs.append(linker_input)
else:
linker_inputs.append(cc_common.create_linker_input(
owner = linker_input.owner,
libraries = depset(linker_input.libraries),
user_link_flags = new_flags,
additional_inputs = depset(linker_input.additional_inputs),
linkstamps = depset(linker_input.linkstamps),
))
return (
cc_common.create_linking_context(
linker_inputs = depset(linker_inputs),
),
seen_flags,
)
def _register_configuration_specific_link_actions_with_cpp_variables(
name,
binary,
common_variables,
feature_configuration,
cc_linking_context,
apple_platform_info,
extra_link_args,
stamp,
user_variable_extensions,
additional_outputs,
extra_link_inputs,
attr_linkopts):
ctx = common_variables.ctx
prefixed_attr_linkopts = [
"-Wl,%s" % linkopt
for linkopt in attr_linkopts
]
seen_flags = {}
(_, user_link_flags, seen_flags) = _dedup_link_flags(
extra_link_args + prefixed_attr_linkopts,
seen_flags,
)
(cc_linking_context, _) = _create_deduped_linkopts_linking_context(
cc_linking_context,
seen_flags,
)
cc_common.link(
name = name,
actions = ctx.actions,
additional_inputs = (
extra_link_inputs +
getattr(ctx.files, "additional_linker_inputs", [])
),
additional_outputs = additional_outputs,
build_config = apple_platform_info.target_build_config,
cc_toolchain = common_variables.toolchain,
feature_configuration = feature_configuration,
language = "objc",
linking_contexts = [cc_linking_context],
main_output = binary,
output_type = "executable",
stamp = stamp,
user_link_flags = user_link_flags,
variables_extension = user_variable_extensions,
)
if _emit_builtin_objc_strip_action(ctx):
return _register_binary_strip_action(
ctx,
name,
binary,
feature_configuration,
apple_platform_info,
extra_link_args,
)
else:
return binary
def _register_configuration_specific_link_actions_with_objc_variables(
name,
binary,
common_variables,
feature_configuration,
cc_linking_context,
apple_platform_info,
extra_link_args,
stamp,
user_variable_extensions,
additional_outputs,
extra_link_inputs,
attr_linkopts):
ctx = common_variables.ctx
libraries_to_link = _libraries_from_linking_context(cc_linking_context).to_list()
always_link_libraries, as_needed_libraries = _classify_libraries(libraries_to_link)
static_runtimes = common_variables.toolchain.static_runtime_lib(
feature_configuration = feature_configuration,
)
linkstamps = [
linkstamp
for linker_input in cc_linking_context.linker_inputs.to_list()
for linkstamp in linker_input.linkstamps
]
linkstamp_map = _linkstamp_map(ctx, linkstamps, binary, apple_platform_info.target_build_config)
input_file_list = _register_obj_filelist_action(
ctx,
apple_platform_info.target_build_config,
as_needed_libraries + static_runtimes.to_list() + linkstamp_map.values(),
)
extensions = user_variable_extensions | {
"filelist": input_file_list.path,
"linked_binary": binary.path,
# artifacts to be passed to the linker with `-force_load`
"force_load_exec_paths": [lib.path for lib in always_link_libraries],
# linkopts from dependency
"dep_linkopts": _create_deduped_linkopts_list(cc_linking_context.linker_inputs),
"attr_linkopts": attr_linkopts, # linkopts arising from rule attributes
}
additional_inputs = [
input
for linker_input in cc_linking_context.linker_inputs.to_list()
for input in linker_input.additional_inputs
]
cc_common.link(
name = name,
actions = ctx.actions,
feature_configuration = feature_configuration,
cc_toolchain = common_variables.toolchain,
language = "objc",
additional_inputs = (
as_needed_libraries + always_link_libraries + [input_file_list] + extra_link_inputs +
additional_inputs +
getattr(ctx.files, "additional_linker_inputs", [])
),
linking_contexts = [cc_common.create_linking_context(linker_inputs = depset(
[cc_common.create_linker_input(
owner = ctx.label,
linkstamps = depset(linkstamps),
)],
))],
output_type = "executable",
build_config = apple_platform_info.target_build_config,
user_link_flags = extra_link_args,
stamp = stamp,
variables_extension = extensions,
additional_outputs = additional_outputs,
main_output = binary,
)
if _emit_builtin_objc_strip_action(ctx):
return _register_binary_strip_action(
ctx,
name,
binary,
feature_configuration,
apple_platform_info,
extra_link_args,
)
else:
return binary
def _build_feature_configuration(common_variables):
ctx = common_variables.ctx
enabled_features = []
enabled_features.extend(ctx.features)
enabled_features.extend(common_variables.extra_enabled_features)
disabled_features = []
disabled_features.extend(ctx.disabled_features)
disabled_features.extend(common_variables.extra_disabled_features)
disabled_features.append("parse_headers")
enabled_features.append("module_maps")
enabled_features.append("compile_all_modules")
enabled_features.append("only_doth_headers_in_module_maps")
enabled_features.append("exclude_private_headers_in_module_maps")
enabled_features.append("module_map_without_extern_module")
disabled_features.append("generate_submodules")
return cc_common.configure_features(
ctx = common_variables.ctx,
cc_toolchain = common_variables.toolchain,
language = "objc",
requested_features = enabled_features,
unsupported_features = disabled_features,
)
def _register_configuration_specific_link_actions(
name,
common_variables,
cc_linking_context,
apple_platform_info,
extra_link_args,
stamp,
user_variable_extensions,
additional_outputs,
extra_link_inputs,
attr_linkopts):
ctx = common_variables.ctx
feature_configuration = _build_feature_configuration(common_variables)
if _emit_builtin_objc_strip_action(ctx):
binary = ctx.actions.declare_shareable_artifact(
paths.join(ctx.label.package, name + "_unstripped"),
apple_platform_info.target_build_config.bin_dir,
)
else:
binary = ctx.actions.declare_shareable_artifact(
paths.join(ctx.label.package, name),
apple_platform_info.target_build_config.bin_dir,
)
if cc_common.is_enabled(
feature_configuration = feature_configuration,
feature_name = "use_cpp_variables_for_objc_executable",
):
return _register_configuration_specific_link_actions_with_cpp_variables(
name,
binary,
common_variables,
feature_configuration,
cc_linking_context,
apple_platform_info,
extra_link_args,
stamp,
user_variable_extensions,
additional_outputs,
extra_link_inputs,
attr_linkopts,
)
else:
return _register_configuration_specific_link_actions_with_objc_variables(
name,
binary,
common_variables,
feature_configuration,
cc_linking_context,
apple_platform_info,
extra_link_args,
stamp,
user_variable_extensions,
additional_outputs,
extra_link_inputs,
attr_linkopts,
)
def link_multi_arch_binary(*, ctx, cc_toolchains, stamp = -1):
"""Copied from rules_apple.
Args:
ctx: rule ctx
cc_toolchains: split toolchain attr
stamp: See upstream docs
Returns:
struct of linking info
"""
# TODO: Delete when we drop bazel 7.x
legacy_linking_function = getattr(apple_common, "link_multi_arch_binary", None)
if legacy_linking_function:
return legacy_linking_function(ctx = ctx, stamp = stamp)
split_deps = ctx.split_attr.deps
if split_deps and split_deps.keys() != cc_toolchains.keys():
fail(("Split transition keys are different between 'deps' [%s] and " +
"'_cc_toolchain_forwarder' [%s]") % (
split_deps.keys(),
cc_toolchains.keys(),
))
outputs = []
cc_infos = []
legacy_debug_outputs = {}
# $(location...) is only used in one test, and tokenize only affects linkopts in one target
additional_linker_inputs = getattr(ctx.attr, "additional_linker_inputs", [])
attr_linkopts = [
ctx.expand_location(opt, targets = additional_linker_inputs)
for opt in getattr(ctx.attr, "linkopts", [])
]
attr_linkopts = [token for opt in attr_linkopts for token in ctx.tokenize(opt)]
for split_transition_key, child_toolchain in cc_toolchains.items():
cc_toolchain = child_toolchain[CcWrapperInfo].provider
deps = split_deps.get(split_transition_key, [])
platform_info = child_toolchain[TestApplePlatformInfo]
build_common_variables = apple_common.compilation_support.build_common_variables if _USE_OLD_API else compilation_support.build_common_variables
common_variables = build_common_variables(
ctx = ctx,
toolchain = cc_toolchain,
deps = deps,
attr_linkopts = attr_linkopts,
)
cc_infos.append(CcInfo(
compilation_context = cc_common.merge_compilation_contexts(
compilation_contexts =
common_variables.objc_compilation_context.cc_compilation_contexts,
),
linking_context = cc_common.merge_linking_contexts(
linking_contexts = common_variables.objc_linking_context.cc_linking_contexts,
),
))
cc_linking_context = _subtract_linking_contexts(
owner = ctx.label,
linking_contexts = common_variables.objc_linking_context.cc_linking_contexts,
avoid_dep_linking_contexts = [],
)
additional_outputs = []
extensions = {}
dsym_binary = None
if ctx.fragments.cpp.apple_generate_dsym:
if ctx.fragments.cpp.objc_should_strip_binary:
suffix = "_bin_unstripped.dwarf"
else:
suffix = "_bin.dwarf"
dsym_binary = ctx.actions.declare_shareable_artifact(
ctx.label.package + "/" + ctx.label.name + suffix,
ctx.bin_dir,
)
extensions["dsym_path"] = dsym_binary.path # dsym symbol file
additional_outputs.append(dsym_binary)
legacy_debug_outputs.setdefault(platform_info.target_arch, {})["dsym_binary"] = dsym_binary
linkmap = None
if ctx.fragments.cpp.objc_generate_linkmap:
linkmap = ctx.actions.declare_shareable_artifact(
ctx.label.package + "/" + ctx.label.name + ".linkmap",
ctx.bin_dir,
)
extensions["linkmap_exec_path"] = linkmap.path # linkmap file
additional_outputs.append(linkmap)
legacy_debug_outputs.setdefault(platform_info.target_arch, {})["linkmap"] = linkmap
name = ctx.label.name + "_bin"
if _USE_OLD_API:
executable = apple_common.compilation_support.register_configuration_specific_link_actions(
name = name,
common_variables = common_variables,
cc_linking_context = cc_linking_context,
build_config = platform_info.target_build_config,
stamp = stamp,
user_variable_extensions = extensions,
additional_outputs = additional_outputs,
deps = deps,
attr_linkopts = attr_linkopts,
extra_link_args = [],
extra_link_inputs = [],
)
else:
executable = _register_configuration_specific_link_actions(
name = name,
common_variables = common_variables,
cc_linking_context = cc_linking_context,
apple_platform_info = platform_info,
extra_link_args = [],
stamp = stamp,
user_variable_extensions = extensions,
additional_outputs = additional_outputs,
extra_link_inputs = [],
attr_linkopts = attr_linkopts,
)
output = {
"binary": executable,
"platform": platform_info.target_os,
"architecture": platform_info.target_arch,
"environment": platform_info.target_environment,
"dsym_binary": dsym_binary,
"linkmap": linkmap,
}
outputs.append(struct(**output))
header_tokens = []
for _, deps in split_deps.items():
for dep in deps:
if CcInfo in dep:
header_tokens.append(dep[CcInfo].compilation_context.validation_artifacts)
output_groups = {"_validation": depset(transitive = header_tokens)}
return struct(
cc_info = cc_common.merge_cc_infos(direct_cc_infos = cc_infos),
output_groups = output_groups,
outputs = outputs,
)
def _build_fully_linked_variable_extensions(*, archive, libs):
extensions = {}
extensions["fully_linked_archive_path"] = archive.path
extensions["objc_library_exec_paths"] = [lib.path for lib in libs]
extensions["cc_library_exec_paths"] = []
extensions["imported_library_exec_paths"] = []
return extensions
def _register_fully_link_action(*, cc_linking_context, common_variables, name):
ctx = common_variables.ctx
feature_configuration = _build_feature_configuration(common_variables)
libraries_to_link = _libraries_from_linking_context(cc_linking_context).to_list()
libraries = _get_libraries_for_linking(libraries_to_link)
output_archive = ctx.actions.declare_file(name + ".a")
extensions = _build_fully_linked_variable_extensions(
archive = output_archive,
libs = libraries,
)
return cc_common.link(
actions = ctx.actions,
additional_inputs = libraries,
cc_toolchain = common_variables.toolchain,
feature_configuration = feature_configuration,
language = "objc",
name = name,
output_type = "archive",
variables_extension = extensions,
)
def link_multi_arch_static_library(*, ctx, cc_toolchains):
"""Copied from rules_apple.
Args:
ctx: rule ctx
cc_toolchains: split toolchain attr
Returns:
struct of linking info
"""
# TODO: Delete when we drop bazel 7.x
legacy_linking_function = getattr(apple_common, "link_multi_arch_static_library", None)
if legacy_linking_function:
return legacy_linking_function(ctx = ctx)
split_deps = ctx.split_attr.deps
split_avoid_deps = ctx.split_attr.avoid_deps
outputs = []
for split_transition_key, child_toolchain in cc_toolchains.items():
cc_toolchain = child_toolchain[CcWrapperInfo].provider
build_common_variables = apple_common.compilation_support.build_common_variables if _USE_OLD_API else compilation_support.build_common_variables
common_variables = build_common_variables(
ctx = ctx,
toolchain = cc_toolchain,
use_pch = True,
deps = split_deps[split_transition_key],
)
avoid_cc_linking_contexts = []
if len(split_avoid_deps.keys()):
for dep in split_avoid_deps[split_transition_key]:
if CcInfo in dep:
avoid_cc_linking_contexts.append(dep[CcInfo].linking_context)
name = ctx.label.name + "-" + cc_toolchain.target_gnu_system_name + "-fl"
cc_linking_context = _subtract_linking_contexts(
owner = ctx.label,
linking_contexts = common_variables.objc_linking_context.cc_linking_contexts,
avoid_dep_linking_contexts = avoid_cc_linking_contexts,
)
register_fully_link = apple_common.compilation_support.register_fully_link_action if _USE_OLD_API else _register_fully_link_action
linking_outputs = register_fully_link(
name = name,
common_variables = common_variables,
cc_linking_context = cc_linking_context,
)
output = {
"library": linking_outputs.library_to_link.static_library,
}
platform_info = child_toolchain[TestApplePlatformInfo]
output["platform"] = platform_info.target_os
output["architecture"] = platform_info.target_arch
output["environment"] = platform_info.target_environment
outputs.append(struct(**output))
header_tokens = []
for _, deps in split_deps.items():
for dep in deps:
if CcInfo in dep:
header_tokens.append(dep[CcInfo].compilation_context.validation_artifacts)
output_groups = {"_validation": depset(transitive = header_tokens)}
return struct(
outputs = outputs,
output_groups = OutputGroupInfo(**output_groups),
)