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
  • Loading branch information
allevato authored and swiple-rules-gardener committed Dec 6, 2023
1 parent 87c2a14 commit 18f2f87
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 41 deletions.
171 changes: 133 additions & 38 deletions swift/internal/compiling.bzl
Expand Up @@ -305,6 +305,10 @@ def compile(
* `supplemental_outputs`: A `struct` representing supplemental,
optional outputs. Its fields are:
* `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.
* `indexstore_directory`: A directory-type `File` that represents
the indexstore output files created when the feature
`swift.index_while_building` is enabled.
Expand Down Expand Up @@ -335,9 +339,44 @@ def compile(
swift_toolchain.generated_header_module_implicit_deps_providers.swift_infos
)

# These are the `SwiftInfo` providers that will be merged with the compiled
# module context and returned as the `swift_info` field of this function's
# result. Note that private deps are explicitly not included here, as they
# are not supposed to be propagated.
#
# TODO(allevato): It would potentially clean things up if we included the
# toolchain's implicit dependencies here as well. Do this and make sure it
# doesn't break anything unexpected.
swift_infos_to_propagate = swift_infos + _cross_imported_swift_infos(
swift_toolchain = swift_toolchain,
user_swift_infos = swift_infos + private_swift_infos,
)

all_swift_infos = (
swift_infos_to_propagate +
private_swift_infos +
swift_toolchain.implicit_deps_providers.swift_infos
)
merged_swift_info = SwiftInfo(swift_infos = all_swift_infos)

# Flattening this `depset` is necessary because we need to extract the
# module maps or precompiled modules out of structured values and do so
# conditionally. This should not lead to poor performance because the
# flattening happens only once as the action is being registered, rather
# than the same `depset` being flattened and re-merged multiple times up
# the build graph.
transitive_modules = merged_swift_info.transitive_modules.to_list()

const_gather_protocols_file = _maybe_create_const_protocols_file(
actions = actions,
swift_infos = all_swift_infos,
target_name = target_name,
)

compile_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 @@ -357,7 +396,7 @@ def compile(
compile_outputs.generated_header_file,
compile_outputs.indexstore_directory,
compile_outputs.macro_expansion_directory,
]) + compile_outputs.object_files
]) + compile_outputs.object_files + compile_outputs.const_values_files

merged_compilation_context = merge_compilation_contexts(
transitive_compilation_contexts = compilation_contexts + [
Expand All @@ -366,34 +405,6 @@ def compile(
],
)

# These are the `SwiftInfo` providers that will be merged with the compiled
# module context and returned as the `swift_info` field of this function's
# result. Note that private deps are explicitly not included here, as they
# are not supposed to be propagated.
#
# TODO(allevato): It would potentially clean things up if we included the
# toolchain's implicit dependencies here as well. Do this and make sure it
# doesn't break anything unexpected.
swift_infos_to_propagate = swift_infos + _cross_imported_swift_infos(
swift_toolchain = swift_toolchain,
user_swift_infos = swift_infos + private_swift_infos,
)

all_swift_infos = (
swift_infos_to_propagate +
private_swift_infos +
swift_toolchain.implicit_deps_providers.swift_infos
)
merged_swift_info = SwiftInfo(swift_infos = all_swift_infos)

# Flattening this `depset` is necessary because we need to extract the
# module maps or precompiled modules out of structured values and do so
# conditionally. This should not lead to poor performance because the
# flattening happens only once as the action is being registered, rather
# than the same `depset` being flattened and re-merged multiple times up
# the build graph.
transitive_modules = merged_swift_info.transitive_modules.to_list()

transitive_swiftmodules = []
defines_set = sets.make(defines)
for module in transitive_modules:
Expand Down Expand Up @@ -469,6 +480,7 @@ def compile(
),
bin_dir = feature_configuration._bin_dir,
cc_compilation_context = merged_compilation_context,
const_gather_protocols_file = const_gather_protocols_file,
defines = sets.to_list(defines_set),
deps_modules_file = deps_modules_file,
explicit_swift_module_map_file = explicit_swift_module_map_file,
Expand Down Expand Up @@ -573,6 +585,7 @@ def compile(
module_context = module_context,
compilation_outputs = compilation_outputs,
supplemental_outputs = struct(
const_values_files = compile_outputs.const_values_files,
indexstore_directory = compile_outputs.indexstore_directory,
macro_expansion_directory = (
compile_outputs.macro_expansion_directory
Expand Down Expand Up @@ -897,6 +910,7 @@ def _cross_imported_swift_infos(*, swift_toolchain, user_swift_infos):
def _declare_compile_outputs(
*,
actions,
extract_const_values,
feature_configuration,
generated_header_name,
generated_module_deps_swift_infos,
Expand All @@ -908,6 +922,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 @@ -1020,6 +1036,9 @@ def _declare_compile_outputs(
# declare the output file that the compiler will generate and there are
# no other partial outputs.
object_files = [actions.declare_file("{}.o".format(target_name))]
const_values_files = [
actions.declare_file("{}.swiftconstvalues".format(target_name)),
]
output_file_map = None
# TODO(b/147451378): Support indexing even with a single object file.

Expand All @@ -1028,11 +1047,14 @@ 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,
srcs = srcs,
target_name = target_name,
include_index_unit_paths = include_index_unit_paths,
)
object_files = output_info.object_files
const_values_files = output_info.const_values_files
output_file_map = output_info.output_file_map

if is_feature_enabled(
Expand All @@ -1049,6 +1071,7 @@ def _declare_compile_outputs(
macro_expansion_directory = None

return struct(
const_values_files = const_values_files,
generated_header_file = generated_header,
generated_module_map_file = generated_module_map,
indexstore_directory = indexstore_directory,
Expand All @@ -1061,16 +1084,17 @@ def _declare_compile_outputs(
swiftsourceinfo_file = swiftsourceinfo_file,
)

def _declare_per_source_object_file(actions, target_name, src):
"""Declares a file for a per-source object file during compilation.
def _declare_per_source_output_file(actions, extension, target_name, src):
"""Declares a file for a per-source output file during compilation.
These files are produced when the compiler is invoked with multiple frontend
invocations (i.e., whole module optimization disabled); in that case, there
is a `.o` file produced for each source file, rather than a single `.o` for
the entire module.
invocations (i.e., whole module optimization disabled), when it is expected
that certain outputs (such as object files) produce one output per source
file rather than one for the entire module.
Args:
actions: The context's actions object.
extension: The output file's extension, without a leading dot.
target_name: The name of the target being built.
src: A `File` representing the source file being compiled.
Expand All @@ -1083,18 +1107,24 @@ def _declare_per_source_object_file(actions, target_name, src):
dirname = paths.join(objs_dir, paths.dirname(owner_rel_path))

return actions.declare_file(
paths.join(dirname, "{}.o".format(basename)),
paths.join(dirname, "{}.{}".format(basename, extension)),
)

def _declare_multiple_outputs_and_write_output_file_map(
actions,
extract_const_values,
is_wmo,
srcs,
target_name,
include_index_unit_paths):
"""Declares low-level outputs and writes the output map for a compilation.
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.
srcs: The list of source files that will be compiled.
target_name: The name (excluding package path) of the target being
built.
Expand All @@ -1118,14 +1148,24 @@ 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`.
output_map = {}
whole_module_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 = []

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:
# Declare the object file (there is one per source file).
obj = _declare_per_source_object_file(
obj = _declare_per_source_output_file(
actions = actions,
extension = "o",
target_name = target_name,
src = src,
)
Expand All @@ -1135,14 +1175,29 @@ def _declare_multiple_outputs_and_write_output_file_map(
}
if include_index_unit_paths:
file_outputs["index-unit-output-path"] = obj.path

if extract_const_values and not is_wmo:
const_values_file = _declare_per_source_output_file(
actions = actions,
extension = "swiftconstvalues",
target_name = target_name,
src = src,
)
const_values_files.append(const_values_file)
file_outputs["const-values"] = const_values_file.path

output_map[src.path] = file_outputs

if whole_module_map:
output_map[""] = whole_module_map

actions.write(
content = json.encode(output_map),
output = output_map_file,
)

return struct(
const_values_files = const_values_files,
object_files = output_objs,
output_file_map = output_map_file,
)
Expand Down Expand Up @@ -1193,6 +1248,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 @@ -1215,7 +1271,10 @@ def _emitted_output_nature(feature_configuration, user_compile_flags):
feature_name = SWIFT_FEATURE__NUM_THREADS_1_IN_SWIFTCOPTS,
) or find_num_threads_flag_value(user_compile_flags) == 1

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

def _write_deps_modules_file(
actions,
Expand All @@ -1242,3 +1301,39 @@ def _write_deps_modules_file(
content = deps_mapping,
output = deps_modules_file,
)

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
5 changes: 5 additions & 0 deletions swift/internal/output_groups.bzl
Expand Up @@ -32,16 +32,21 @@ def supplemental_compilation_output_groups(*supplemental_outputs):
"""
indexstore_files = []
macro_expansions_files = []
const_values_files = []

for outputs in supplemental_outputs:
if outputs.indexstore_directory:
indexstore_files.append(outputs.indexstore_directory)
if outputs.macro_expansion_directory:
macro_expansions_files.append(outputs.macro_expansion_directory)
if outputs.const_values_files:
const_values_files.extend(outputs.const_values_files)

output_groups = {}
if indexstore_files:
output_groups["indexstore"] = depset(indexstore_files)
if macro_expansions_files:
output_groups["macro_expansions"] = depset(macro_expansions_files)
if const_values_files:
output_groups["const_values"] = depset(const_values_files)
return output_groups
5 changes: 5 additions & 0 deletions swift/providers.bzl
Expand Up @@ -343,6 +343,7 @@ def create_swift_module_context(
*,
name,
clang = None,
const_gather_protocols = [],
is_framework = False,
is_system = False,
swift = None):
Expand Down Expand Up @@ -370,6 +371,9 @@ def create_swift_module_context(
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.
is_framework: Indictates whether the module is a framework module. The
default value is `False`.
is_system: Indicates whether the module is a system module. The default
Expand Down Expand Up @@ -397,6 +401,7 @@ def create_swift_module_context(
"""
return struct(
clang = clang,
const_gather_protocols = tuple(const_gather_protocols),
is_framework = is_framework,
is_system = is_system,
name = name,
Expand Down

0 comments on commit 18f2f87

Please sign in to comment.