Skip to content

Commit

Permalink
Add a mechanism to provide a list of protocol names for constant valu…
Browse files Browse the repository at this point in the history
…e extraction.

If a module populates `const_gather_protocols` when calling `create_swift_module_context`, any target that directly depends on that module will automatically have that list of protocols passed to the compiler via `-const-gather-protocols-file`. The output group `const_values` will then contain the JSON file with the extracted constant information.

This is meant to support Apple's AppIntents framework, which uses the output from `-emit-const-values-path` as an input to the AppIntent bundling tool.

PiperOrigin-RevId: 588388131
(cherry picked from commit 18f2f87)

# Conflicts:
#	swift/internal/compiling.bzl
#	swift/internal/output_groups.bzl
#	swift/providers.bzl
#	swift/toolchains/config/compile_config.bzl
#	test/rules/provider_test.bzl
#	tools/worker/swift_runner.cc
  • Loading branch information
allevato authored and BalestraPatrick committed Mar 6, 2024
1 parent 4ec597e commit 36ee85b
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 5 deletions.
111 changes: 108 additions & 3 deletions swift/internal/compiling.bzl
Expand Up @@ -376,6 +376,12 @@ def compile_action_configs(
actions = [swift_action_names.DERIVE_FILES],
configurators = [_emit_objc_header_path_configurator],
),
swift_toolchain_config.action_config(
actions = [
swift_action_names.COMPILE,
],
configurators = [_constant_value_extraction_configurator],
),
]

if generated_header_rewriter:
Expand Down Expand Up @@ -1351,6 +1357,10 @@ def _emit_module_interface_path_configurator(prerequisites, args):
"""Adds the `.swiftinterface` output path to the command line."""
args.add("-emit-module-interface-path", prerequisites.swiftinterface_file)

def _emit_const_values_path_configurator(prerequisites, args):
"""Adds the `.swiftconst` output path to the command line."""
args.add("-emit-const-values-path", prerequisites.swiftconst_values)

def _emit_objc_header_path_configurator(prerequisites, args):
"""Adds the generated header output path to the command line."""
if prerequisites.generated_header_file:
Expand Down Expand Up @@ -1969,6 +1979,23 @@ def _conditional_compilation_flag_configurator(prerequisites, args):
format_each = "-D%s",
)

def _constant_value_extraction_configurator(prerequisites, args):
"""Adds flags related to constant value extraction to the command line."""
if not prerequisites.const_gather_protocols_file:
return None

args.add("-emit-const-values")
args.add_all(
[
"-const-gather-protocols-file",
prerequisites.const_gather_protocols_file,
],
before_each = "-Xfrontend",
)
return swift_toolchain_config.config_result(
inputs = [prerequisites.const_gather_protocols_file],
)

def _additional_inputs_configurator(prerequisites, _args):
"""Propagates additional input files to the action.
Expand Down Expand Up @@ -2342,6 +2369,9 @@ def compile(
contains the symbol graph data generated by the compiler if the
`"swift.emit_symbol_graph"` feature is enabled, otherwise this
will be `None`.
* `const_values_files`: A list of `File`s that contains JSON
representations of constant values extracted from the source
files, if requested via a direct dependency.
"""

# Collect the `SwiftInfo` providers that represent the dependencies of the
Expand All @@ -2356,9 +2386,15 @@ def compile(
swift_toolchain.generated_header_module_implicit_deps_providers.swift_infos
)

const_gather_protocols_file = _maybe_create_const_protocols_file(
actions = actions,
swift_infos = generated_module_deps_swift_infos,
target_name = target_name,
)
compile_outputs, other_outputs = _declare_compile_outputs(
srcs = srcs,
actions = actions,
extract_const_values = bool(const_gather_protocols_file),
feature_configuration = feature_configuration,
generated_header_name = generated_header_name,
generated_module_deps_swift_infos = generated_module_deps_swift_infos,
Expand All @@ -2377,7 +2413,7 @@ def compile(
compile_outputs.swiftinterface_file,
compile_outputs.indexstore_directory,
compile_outputs.macro_expansion_directory,
]) + compile_outputs.object_files
]) + compile_outputs.object_files + compile_outputs.const_values_files
all_derived_outputs = compact([
# The `.swiftmodule` file is explicitly listed as the first output
# because it will always exist and because Bazel uses it as a key for
Expand All @@ -2403,7 +2439,7 @@ def compile(
compile_outputs.indexstore_directory,
compile_outputs.macro_expansion_directory,
compile_outputs.symbol_graph_directory,
]) + compile_outputs.object_files + other_outputs
]) + compile_outputs.object_files + compile_outputs.const_values_files + other_outputs
all_derived_outputs = []

# Merge the providers from our dependencies so that we have one each for
Expand Down Expand Up @@ -2510,6 +2546,7 @@ to use swift_common.compile(include_dev_srch_paths = ...) instead.\
additional_inputs = additional_inputs,
bin_dir = feature_configuration._bin_dir,
cc_compilation_context = merged_providers.cc_info.compilation_context,
const_gather_protocols_file = const_gather_protocols_file,
cc_linking_context = merged_providers.cc_info.linking_context,
defines = sets.to_list(defines_set),
explicit_swift_module_map_file = explicit_swift_module_map_file,
Expand Down Expand Up @@ -2646,6 +2683,7 @@ to use swift_common.compile(include_dev_srch_paths = ...) instead.\
indexstore = compile_outputs.indexstore_directory,
macro_expansion_directory = compile_outputs.macro_expansion_directory,
symbol_graph = compile_outputs.symbol_graph_directory,
const_values_files = compile_outputs.const_values_files,
)

return module_context, cc_compilation_outputs, other_compilation_outputs
Expand Down Expand Up @@ -2899,6 +2937,7 @@ def _create_cc_compilation_context(
def _declare_compile_outputs(
*,
actions,
extract_const_values,
feature_configuration,
generated_header_name,
generated_module_deps_swift_infos,
Expand All @@ -2910,6 +2949,8 @@ def _declare_compile_outputs(
Args:
actions: The object used to register actions.
extract_const_values: A Boolean value indicating whether constant values
should be extracted during this compilation.
feature_configuration: A feature configuration obtained from
`swift_common.configure_features`.
generated_header_name: The desired name of the generated header for this
Expand Down Expand Up @@ -3038,6 +3079,9 @@ def _declare_compile_outputs(
src = srcs[0],
)]
other_outputs = []
const_values_files = [
actions.declare_file("{}.swiftconstvalues".format(target_name)),
]
output_file_map = None
derived_files_output_file_map = None
else:
Expand All @@ -3050,6 +3094,8 @@ def _declare_compile_outputs(
# object files so that we can pass them all to the archive action.
output_info = _declare_multiple_outputs_and_write_output_file_map(
actions = actions,
extract_const_values = extract_const_values,
is_wmo = output_nature.is_wmo,
emits_bc = emits_bc,
split_derived_file_generation = split_derived_file_generation,
srcs = srcs,
Expand All @@ -3058,6 +3104,7 @@ def _declare_compile_outputs(
object_files = output_info.object_files
ast_files = output_info.ast_files
other_outputs = output_info.other_outputs
const_values_files = output_info.const_values_files
output_file_map = output_info.output_file_map
derived_files_output_file_map = output_info.derived_files_output_file_map

Expand Down Expand Up @@ -3106,6 +3153,7 @@ def _declare_compile_outputs(

compile_outputs = struct(
ast_files = ast_files,
const_values_files = const_values_files,
generated_header_file = generated_header,
generated_module_map_file = generated_module_map,
indexstore_directory = indexstore_directory,
Expand All @@ -3123,6 +3171,8 @@ def _declare_compile_outputs(

def _declare_multiple_outputs_and_write_output_file_map(
actions,
extract_const_values,
is_wmo,
emits_bc,
split_derived_file_generation,
srcs,
Expand All @@ -3131,6 +3181,10 @@ def _declare_multiple_outputs_and_write_output_file_map(
Args:
actions: The object used to register actions.
extract_const_values: A Boolean value indicating whether constant values
should be extracted during this compilation.
is_wmo: A Boolean value indicating whether whole-module-optimization was
requested.
emits_bc: If `True` the compiler will generate LLVM BC files instead of
object files.
split_derived_file_generation: Whether objects and modules are produced
Expand Down Expand Up @@ -3172,17 +3226,25 @@ def _declare_multiple_outputs_and_write_output_file_map(
# The output map data, which is keyed by source path and will be written to
# `output_map_file` and `derived_files_output_map_file`.
output_map = {}
whole_module_map = {}
derived_files_output_map = {}

# Object files that will be used to build the archive.
# Output files that will be emitted by the compiler.
output_objs = []
const_values_files = []

# Additional files, such as partial Swift modules, that must be declared as
# action outputs although they are not processed further.
other_outputs = []

# AST files that are available in the swift_ast_file output group
ast_files = []
if extract_const_values and is_wmo:
const_value_file = actions.declare_file(
"{}.swiftconstvalues".format(target_name),
)
const_values_files.append(const_value_file)
whole_module_map["const-values"] = const_value_file.path

for src in srcs:
src_output_map = {}
Expand Down Expand Up @@ -3227,6 +3289,7 @@ def _declare_multiple_outputs_and_write_output_file_map(
)

return struct(
const_values_files = const_values_files,
ast_files = ast_files,
object_files = output_objs,
other_outputs = other_outputs,
Expand Down Expand Up @@ -3344,6 +3407,12 @@ def output_groups_from_other_compilation_outputs(*, other_compilation_outputs):
other_compilation_outputs.macro_expansion_directory,
])

if other_compilation_outputs.const_values_files:
if other_compilation_outputs.const_values_files:
output_groups["const_values"] = depset([
other_compilation_outputs.const_values_files,
])

return output_groups

def swift_library_output_map(name):
Expand Down Expand Up @@ -3463,6 +3532,7 @@ def _emitted_output_nature(feature_configuration, user_compile_flags):
* `emits_multiple_objects`: `True` if the Swift frontend emits an
object file per source file, instead of a single object file for the
whole module, in a compilation action with the given flags.
* `is_wmo`: `True` if whole-module-optimization was requested.
"""
is_wmo = (
is_feature_enabled(
Expand All @@ -3487,7 +3557,42 @@ def _emitted_output_nature(feature_configuration, user_compile_flags):

return struct(
emits_multiple_objects = not (is_wmo and is_single_threaded),
is_wmo = is_wmo,
)

def _maybe_create_const_protocols_file(actions, swift_infos, target_name):
"""Create the const extraction protocols file, if necessary.
Args:
actions: The object used to register actions.
swift_infos: A list of `SwiftInfo` providers describing the dependencies
of the code being compiled.
target_name: The name of the build target, which is used to generate
output file names.
Returns:
A file passed as an input to the compiler that lists the protocols whose
conforming types should have values extracted.
"""
const_gather_protocols = []
for swift_info in swift_infos:
for module_context in swift_info.direct_modules:
const_gather_protocols.extend(
module_context.const_gather_protocols,
)

# If there are no protocols to extract, return early.
if not const_gather_protocols:
return None

# Create the input file to the compiler, which contains a JSON array of
# protocol names.
const_gather_protocols_file = actions.declare_file(
"{}_const_extract_protocols.json".format(target_name),
)
actions.write(
content = json.encode(const_gather_protocols),
output = const_gather_protocols_file,
)
return const_gather_protocols_file

def _exclude_swift_incompatible_define(define):
"""A `map_each` helper that excludes a define if it is not Swift-compatible.
Expand Down
5 changes: 5 additions & 0 deletions swift/internal/providers.bzl
Expand Up @@ -322,6 +322,7 @@ def create_module(
*,
name,
clang = None,
const_gather_protocols = [],
compilation_context = None,
is_system = False,
swift = None):
Expand Down Expand Up @@ -349,6 +350,9 @@ def create_module(
contains artifacts related to Clang modules, such as a module map or
precompiled module. This may be `None` if the module is a pure Swift
module with no generated Objective-C interface.
const_gather_protocols: A list of protocol names from which constant
values should be extracted from source code that takes this module
as a *direct* dependency.
compilation_context: A value returned from
`swift_common.create_compilation_context` that contains the
context needed to compile the module being built. This may be `None`
Expand Down Expand Up @@ -379,6 +383,7 @@ def create_module(
"""
return struct(
clang = clang,
const_gather_protocols = tuple(const_gather_protocols),
compilation_context = compilation_context,
is_system = is_system,
name = name,
Expand Down
13 changes: 11 additions & 2 deletions test/rules/provider_test.bzl
Expand Up @@ -65,6 +65,11 @@ def _evaluate_field(env, source, field):
component that was not the final component, then the special value
`_EVALUATE_FIELD_FAILED` is returned.
"""
def evaluate_component(source, component):
if types.is_dict(source):
return source.get(component)
return getattr(source, component, None)

components = field.split(".")

for component in components:
Expand Down Expand Up @@ -93,7 +98,7 @@ def _evaluate_field(env, source, field):
flattened.extend(item)
else:
flattened.append(item)
source = [getattr(item, component, None) for item in flattened]
source = [evaluate_component(item, component) for item in flattened]
if filter_nones:
source = [item for item in source if item != None]
else:
Expand All @@ -107,7 +112,7 @@ def _evaluate_field(env, source, field):
)
return _EVALUATE_FIELD_FAILED

source = getattr(source, component, None)
source = evaluate_component(source, component)
if filter_nones:
source = _normalize_collection(source)
if types.is_list(source):
Expand Down Expand Up @@ -400,6 +405,10 @@ evaluated on every item in that list, not on the list itself. Likewise, if such
a field path component is followed by `!`, then any `None` elements that may
have resulted during evaluation will be removed from the list before evaluating
the next component.
If a value along the field path is a dictionary and the next component
is a valid key in that dictionary, then the value of that dictionary key is
retrieved instead of it being treated as a struct field access.
""",
),
"provider": attr.string(
Expand Down

0 comments on commit 36ee85b

Please sign in to comment.