Add feature to switch to cpp variables for objc executable action
With linking info having migrated to CcInfo, we no longer need the custom objc
variables for linking actions. This change adds support for objc link action to
emit cpp link variables. This requires changes to the crosstool as well, so we
add a feature "use_cpp_variables_for_objc_executable" to control whether to use
it. Once we have migrated all the crosstools, we can delete support for the
objc linking variables.
A nice benefit of switching to cpp variables is that we get the ability
eliminate archive actions for free (for linkers that support start/end_lib).
PiperOrigin-RevId: 684871090
Change-Id: Ifc223ee87fe343df3aebe77e6fd352f4f2acb52b
diff --git a/src/main/starlark/builtins_bzl/common/objc/compilation_support.bzl b/src/main/starlark/builtins_bzl/common/objc/compilation_support.bzl
index 7c44e55..c93ecd9 100644
--- a/src/main/starlark/builtins_bzl/common/objc/compilation_support.bzl
+++ b/src/main/starlark/builtins_bzl/common/objc/compilation_support.bzl
@@ -737,35 +737,15 @@
)
return stripped_binary
-def _dedup_sdk_linkopts(linker_inputs):
- duplicates = {}
+def _create_deduped_linkopts_list(linker_inputs):
+ seen_flags = {}
final_linkopts = []
-
for linker_input in linker_inputs.to_list():
- flags = linker_input.user_link_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 duplicates:
- final_linkopts.extend([previous_arg, framework])
- duplicates[key] = None
- 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 duplicates:
- final_linkopts.extend([arg.split(",")[1], framework])
- duplicates[key] = None
- elif arg.startswith("-l"):
- if arg not in duplicates:
- final_linkopts.append(arg)
- duplicates[arg] = None
- else:
- final_linkopts.append(arg)
+ (_, new_flags, seen_flags) = _dedup_link_flags(
+ linker_input.user_link_flags,
+ seen_flags,
+ )
+ final_linkopts.extend(new_flags)
return final_linkopts
@@ -827,22 +807,6 @@
ctx = common_variables.ctx
feature_configuration = _build_feature_configuration(common_variables, False, False)
- # We need to split input libraries into those that require -force_load and those that don't.
- # Clang loads archives specified in filelists and also specified as -force_load twice,
- # resulting in duplicate symbol errors unless they are deduped.
- libraries_to_link = cc_helper.libraries_from_linking_context(cc_linking_context).to_list()
- always_link_libraries, as_needed_libraries = _classify_libraries(libraries_to_link)
-
- replace_libs = _register_j2objc_dead_code_removal_actions(common_variables, deps, build_config)
-
- # Substitutes both sets of unpruned J2ObjC libraries with pruned ones
- always_link_libraries = [replace_libs.get(lib, lib) for lib in always_link_libraries]
- as_needed_libraries = [replace_libs.get(lib, lib) for lib in as_needed_libraries]
-
- static_runtimes = common_variables.toolchain.static_runtime_lib(
- feature_configuration = feature_configuration,
- )
-
# When compilation_mode=opt and objc_enable_binary_stripping are specified, the unstripped
# binary containing debug symbols is generated by the linker, which also needs the debug
# symbols for dead-code removal. The binary is also used to generate dSYM bundle if
@@ -860,6 +824,246 @@
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,
+ build_config,
+ extra_link_args,
+ stamp,
+ user_variable_extensions,
+ additional_outputs,
+ deps,
+ extra_link_inputs,
+ attr_linkopts,
+ )
+ else:
+ return _register_configuration_specific_link_actions_with_objc_variables(
+ name,
+ binary,
+ common_variables,
+ feature_configuration,
+ cc_linking_context,
+ build_config,
+ extra_link_args,
+ stamp,
+ user_variable_extensions,
+ additional_outputs,
+ deps,
+ extra_link_inputs,
+ attr_linkopts,
+ )
+
+def _register_configuration_specific_link_actions_with_cpp_variables(
+ name,
+ binary,
+ common_variables,
+ feature_configuration,
+ cc_linking_context,
+ build_config,
+ extra_link_args,
+ stamp,
+ user_variable_extensions,
+ additional_outputs,
+ deps,
+ extra_link_inputs,
+ attr_linkopts):
+ ctx = common_variables.ctx
+
+ replace_libs = _register_j2objc_dead_code_removal_actions(common_variables, deps, build_config)
+
+ if replace_libs:
+ cc_linking_context = _substitute_j2objc_pruned_libraries(
+ ctx.actions,
+ feature_configuration,
+ common_variables.toolchain,
+ cc_linking_context,
+ replace_libs,
+ )
+
+ cc_linking_context = _create_deduped_linkopts_linking_context(ctx.label, cc_linking_context)
+
+ prefixed_attr_linkopts = [
+ "-Wl,%s" % linkopt
+ for linkopt in attr_linkopts
+ ]
+
+ 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 = 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 = extra_link_args + prefixed_attr_linkopts,
+ variables_extension = user_variable_extensions,
+ )
+
+ if not (ctx.fragments.cpp.objc_enable_binary_stripping() and
+ ctx.fragments.cpp.compilation_mode() == "opt"):
+ return binary
+ else:
+ return _register_binary_strip_action(
+ ctx,
+ name,
+ binary,
+ feature_configuration,
+ build_config,
+ extra_link_args,
+ )
+
+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("-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 _create_deduped_linkopts_linking_context(owner, 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),
+ ))
+
+ # Why does linker_input not expose linkstamp? This needs to be fixed.
+ linker_inputs.append(cc_common.create_linker_input(
+ owner = owner,
+ linkstamps = cc_linking_context.linkstamps(),
+ ))
+
+ return cc_common.create_linking_context(
+ linker_inputs = depset(linker_inputs),
+ )
+
+def _substitute_j2objc_pruned_libraries(
+ actions,
+ feature_configuration,
+ cc_toolchain,
+ cc_linking_context,
+ replace_libs):
+ linker_inputs = []
+ for linker_input in cc_linking_context.linker_inputs.to_list():
+ need_replacement = any([
+ _get_library_for_linking(library_to_link) in replace_libs
+ for library_to_link in linker_input.libraries
+ ])
+
+ if need_replacement:
+ libraries_to_link = []
+ for library_to_link in linker_input.libraries:
+ library = _get_library_for_linking(library_to_link)
+ if library not in replace_libs:
+ libraries_to_link.append(library_to_link)
+ else:
+ new_library_to_link = cc_common.create_library_to_link(
+ actions = actions,
+ feature_configuration = feature_configuration,
+ cc_toolchain = cc_toolchain,
+ static_library = replace_libs[library],
+ alwayslink = library_to_link.alwayslink,
+ )
+ libraries_to_link.append(new_library_to_link)
+
+ new_linker_input = cc_common.create_linker_input(
+ owner = linker_input.owner,
+ libraries = depset(libraries_to_link),
+ user_link_flags = linker_input.user_link_flags,
+ additional_inputs = depset(linker_input.additional_inputs),
+ )
+
+ linker_inputs.append(new_linker_input)
+ else:
+ linker_inputs.append(linker_input)
+
+ return cc_common.create_linking_context(
+ linker_inputs = depset(linker_inputs),
+ )
+
+def _register_configuration_specific_link_actions_with_objc_variables(
+ name,
+ binary,
+ common_variables,
+ feature_configuration,
+ cc_linking_context,
+ build_config,
+ extra_link_args,
+ stamp,
+ user_variable_extensions,
+ additional_outputs,
+ deps,
+ extra_link_inputs,
+ attr_linkopts):
+ ctx = common_variables.ctx
+
+ # We need to split input libraries into those that require -force_load and those that don't.
+ # Clang loads archives specified in filelists and also specified as -force_load twice,
+ # resulting in duplicate symbol errors unless they are deduped.
+ libraries_to_link = cc_helper.libraries_from_linking_context(cc_linking_context).to_list()
+ always_link_libraries, as_needed_libraries = _classify_libraries(libraries_to_link)
+
+ replace_libs = _register_j2objc_dead_code_removal_actions(common_variables, deps, build_config)
+
+ # Substitutes both sets of unpruned J2ObjC libraries with pruned ones
+ always_link_libraries = [replace_libs.get(lib, lib) for lib in always_link_libraries]
+ as_needed_libraries = [replace_libs.get(lib, lib) for lib in as_needed_libraries]
+
+ static_runtimes = common_variables.toolchain.static_runtime_lib(
+ feature_configuration = feature_configuration,
+ )
+
# Passing large numbers of inputs on the command line triggers a bug in Apple's Clang
# (b/29094356), so we'll create an input list manually and pass -filelist path/to/input/list.
@@ -886,7 +1090,7 @@
# 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": _dedup_sdk_linkopts(cc_linking_context.linker_inputs),
+ "dep_linkopts": _create_deduped_linkopts_list(cc_linking_context.linker_inputs),
"attr_linkopts": attr_linkopts, # linkopts arising from rule attributes
}
additional_inputs = [