Skip to content
This repository has been archived by the owner on Jan 25, 2024. It is now read-only.

Commit

Permalink
Sort headers in the module maps generated by `swift_clang_module_aspe…
Browse files Browse the repository at this point in the history
…ct`, and rewrite generation to use a multi-line `Args` object as a file writer instead of building up an analysis time string.

With this approach, each "arg" (or each entry in a list/depset of args) is treated as its own line in the output file; we use the `format_each` and `map_each` parameters to provide any additional text that should surround that value on the line.

More importantly, this adds support for expanding tree artifacts (directories) if one is provided in a compilation context.

PiperOrigin-RevId: 367221120
  • Loading branch information
allevato authored and swiple-rules-gardener committed Apr 7, 2021
1 parent 665ea75 commit 5f7af69
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 65 deletions.
145 changes: 84 additions & 61 deletions swift/internal/module_maps.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@

"""Logic for generating Clang module map files."""

load("@bazel_skylib//lib:paths.bzl", "paths")

def write_module_map(
actions,
module_map_file,
Expand Down Expand Up @@ -55,69 +53,93 @@ def write_module_map(
written in the module map file should be relative to the workspace
or relative to the module map file.
"""
content = 'module "{}" {{\n'.format(module_name)
if exported_module_ids:
content += "".join([
" export {}\n".format(module_id)
for module_id in exported_module_ids
])
content += "\n"

content += "".join([
' header "{}"\n'.format(_header_path(
header_file = header_file,
module_map_file = module_map_file,
workspace_relative = workspace_relative,
))
for header_file in public_headers
])
content += "".join([
' private header "{}"\n'.format(_header_path(
header_file = header_file,
module_map_file = module_map_file,
workspace_relative = workspace_relative,
))
for header_file in private_headers
])
content += "".join([
' textual header "{}"\n'.format(_header_path(
header_file = header_file,
module_map_file = module_map_file,
workspace_relative = workspace_relative,
))
for header_file in public_textual_headers
])
content += "".join([
' private textual header "{}"\n'.format(_header_path(
header_file = header_file,
module_map_file = module_map_file,
workspace_relative = workspace_relative,
))
for header_file in private_textual_headers
])

content += "".join([
' use "{}"\n'.format(name)
for name in dependent_module_names
])

content += "}\n"

# In the non-workspace-relative case, precompute the relative-to-dir and the
# repeated `../` string used to go back up to the workspace root instead of
# recomputing it every time a header path is written.
if workspace_relative:
relative_to_dir = None
back_to_root_path = None
else:
relative_to_dir = module_map_file.dirname
back_to_root_path = "../" * len(relative_to_dir.split("/"))

content = actions.args()
content.set_param_file_format("multiline")

content.add(module_name, format = 'module "%s" {')

# Write an `export` declaration for each of the module identifiers that
# should be re-exported by this module.
content.add_all(exported_module_ids, format_each = " export %s")
content.add("")

def _add_headers(*, headers, kind):
# Each header is added to the `Args` object as a tuple along with
# `relative_to_dir` and `back_to_root_path`. This gives the mapping
# function the information it needs to relativize the header paths even
# when they're expanded from a tree artifact (and thus not known at
# analysis time).
content.add_all(
[(file, relative_to_dir, back_to_root_path) for file in headers],
format_each = ' {} "%s"'.format(kind),
map_each = _header_info_mapper,
)

_add_headers(headers = public_headers, kind = "header")
_add_headers(headers = private_headers, kind = "private header")
_add_headers(headers = public_textual_headers, kind = "textual header")
_add_headers(
headers = private_textual_headers,
kind = "private textual header",
)
content.add("")

# Write a `use` declaration for each of the module's dependencies.
content.add_all(dependent_module_names, format_each = ' use "%s"')
content.add("}")

actions.write(
content = content,
output = module_map_file,
)

def _header_path(header_file, module_map_file, workspace_relative):
def _header_info_mapper(header_info, directory_expander):
"""Maps header info passed to the `Args` object to a list of header paths.
Args:
header_info: A tuple containing three elements: a `File` representing a
header (or a directory containing headers), the path to the
directory that should be used to relativize the header paths, and
the path string consisting of repeated `../` segments that should be
used to return from the module map's directory to the workspace
root. The latter two elements will be `None` if the headers should
be written workspace-relative).
directory_expander: The object used to expand tree artifacts into the
list of files in that directory.
Returns:
A list of file paths as they should be written into the module map file.
"""
return [
_header_path(file, header_info[1], header_info[2])
for file in directory_expander.expand(header_info[0])
]

def _header_path(header_file, relative_to_dir, back_to_root_path):
"""Returns the path to a header file to be written in the module map.
Args:
header_file: A `File` representing the header whose path should be
returned.
module_map_file: A `File` representing the module map being written,
which is used during path relativization if necessary.
workspace_relative: A Boolean value indicating whether the path should
be workspace-relative or module-map-relative.
relative_to_dir: A `File` representing the module map being
written, which is used during path relativization if necessary. If
this is `None`, then no relativization is performed of the header
path and the workspace-relative path is used instead.
back_to_root_path: A path string consisting of repeated `../` segments
that should be used to return from the module map's directory to the
workspace root. This should be `None` if `relative_to_dir` is
`None`.
Returns:
The path to the header file, relative to either the workspace or the
Expand All @@ -126,20 +148,21 @@ def _header_path(header_file, module_map_file, workspace_relative):

# If the module map is workspace-relative, then the file's path is what we
# want.
if workspace_relative:
if not relative_to_dir:
return header_file.path

# Minor optimization for the generated Objective-C header of a Swift module,
# which will be in the same directory as the module map file -- we can just
# use the header's basename instead of the elaborate relative path
# computation below.
if header_file.dirname == module_map_file.dirname:
# use the header's basename instead of the elaborate relative path string
# below.
if header_file.dirname == relative_to_dir:
return header_file.basename

# Otherwise, since the module map is generated, we need to get the full path
# to it rather than just its short path (that is, the path starting with
# bazel-out/). Then, we can simply walk up the same number of parent
# directories as there are path segments, and append the header file's path
# to that.
num_segments = len(paths.dirname(module_map_file.path).split("/"))
return ("../" * num_segments) + header_file.path
# to that. The `back_to_root_path` string is guaranteed to end in a slash,
# so we use simple concatenation instead of Skylib's `paths.join` to avoid
# extra work.
return back_to_root_path + header_file.path
21 changes: 17 additions & 4 deletions swift/internal/swift_clang_module_aspect.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -128,15 +128,28 @@ def _generate_module_map(
actions = actions,
target_name = target.label.name,
)

# Sort dependent module names and the headers to ensure a deterministic
# order in the output file, in the event the compilation context would ever
# change this on us. For files, use the execution path as the sorting key.
def _path_sorting_key(file):
return file.path

write_module_map(
actions = actions,
dependent_module_names = dependent_module_names,
dependent_module_names = sorted(dependent_module_names),
exported_module_ids = ["*"],
module_map_file = module_map_file,
module_name = module_name,
private_headers = private_headers,
public_headers = compilation_context.direct_public_headers,
public_textual_headers = compilation_context.direct_textual_headers,
private_headers = sorted(private_headers, key = _path_sorting_key),
public_headers = sorted(
compilation_context.direct_public_headers,
key = _path_sorting_key,
),
public_textual_headers = sorted(
compilation_context.direct_textual_headers,
key = _path_sorting_key,
),
workspace_relative = workspace_relative,
)
return module_map_file
Expand Down

0 comments on commit 5f7af69

Please sign in to comment.