Skip to content

Commit 7483792

Browse files
allevatoluispadron
authored andcommitted
Implement swift_synthesize_interface_aspect.
This aspect uses the new `swift-synthesize-interface` driver mode that will be available in Swift 6.1 to generate a file that contains the synthesized Swift interface for a module (such as a C/Obj-C module), similar to the functionality provided by SourceKit. This can be used by IDEs and other clients to generate those interfaces without the complexity of a SourceKit invocation, which often do not play nicely with advanced build scenarios. PiperOrigin-RevId: 731345457
1 parent ec9283b commit 7483792

18 files changed

+527
-7
lines changed

swift/BUILD

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ bzl_library(
9696
deps = [
9797
"//swift/internal:compiling",
9898
"//swift/internal:features",
99+
"//swift/internal:interface_synthesizing",
99100
"//swift/internal:linking",
100101
"//swift/internal:symbol_graph_extracting",
101102
"//swift/internal:toolchain_utils",
@@ -207,7 +208,6 @@ bzl_library(
207208
"//swift/internal:utils",
208209
"@bazel_skylib//lib:dicts",
209210
"@bazel_skylib//lib:sets",
210-
"@bazel_skylib//rules:common_settings",
211211
],
212212
)
213213

@@ -249,6 +249,7 @@ bzl_library(
249249
"//swift/internal:attrs",
250250
"//swift/internal:feature_names",
251251
"//swift/internal:providers",
252+
"//swift/internal:toolchain_utils",
252253
"//swift/internal:utils",
253254
],
254255
)
@@ -280,6 +281,18 @@ bzl_library(
280281
],
281282
)
282283

284+
bzl_library(
285+
name = "swift_synthesize_interface_aspect",
286+
srcs = ["swift_synthesize_interface_aspect.bzl"],
287+
deps = [
288+
":providers",
289+
":swift_clang_module_aspect",
290+
"//swift/internal:features",
291+
"//swift/internal:interface_synthesizing",
292+
"//swift/internal:toolchain_utils",
293+
],
294+
)
295+
283296
bzl_library(
284297
name = "swift_test",
285298
srcs = ["swift_test.bzl"],

swift/internal/BUILD

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,17 @@ bzl_library(
162162
],
163163
)
164164

165+
bzl_library(
166+
name = "interface_synthesizing",
167+
srcs = ["interface_synthesizing.bzl"],
168+
deps = [
169+
":action_names",
170+
":actions",
171+
":utils",
172+
"//swift:providers",
173+
],
174+
)
175+
165176
bzl_library(
166177
name = "linking",
167178
srcs = ["linking.bzl"],

swift/internal/action_names.bzl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ SWIFT_ACTION_PRECOMPILE_C_MODULE = "SwiftPrecompileCModule"
4646
# other tooling.
4747
SWIFT_ACTION_SYMBOL_GRAPH_EXTRACT = "SwiftSymbolGraphExtract"
4848

49+
# Synthesizes the Swift interface from a compiled module.
50+
SWIFT_ACTION_SYNTHESIZE_INTERFACE = "SwiftSynthesizeInterface"
51+
4952
def all_action_names():
5053
"""A convenience function to return all actions defined by this rule set."""
5154
return (
@@ -57,4 +60,5 @@ def all_action_names():
5760
SWIFT_ACTION_MODULEWRAP,
5861
SWIFT_ACTION_PRECOMPILE_C_MODULE,
5962
SWIFT_ACTION_SYMBOL_GRAPH_EXTRACT,
63+
SWIFT_ACTION_SYNTHESIZE_INTERFACE,
6064
)
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Copyright 2025 The Bazel Authors. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Functions relating to synthesizing Swift interfaces from non-Swift targets."""
16+
17+
load("//swift:providers.bzl", "SwiftInfo")
18+
load(":action_names.bzl", "SWIFT_ACTION_SYNTHESIZE_INTERFACE")
19+
load(":actions.bzl", "run_toolchain_action")
20+
load(":utils.bzl", "merge_compilation_contexts")
21+
22+
def synthesize_interface(
23+
*,
24+
actions,
25+
compilation_contexts,
26+
feature_configuration,
27+
module_name,
28+
output_file,
29+
swift_infos,
30+
swift_toolchain):
31+
"""Extracts the symbol graph from a Swift module.
32+
33+
Args:
34+
actions: The object used to register actions.
35+
compilation_contexts: A list of `CcCompilationContext`s that represent
36+
C/Objective-C requirements of the target being compiled, such as
37+
Swift-compatible preprocessor defines, header search paths, and so
38+
forth. These are typically retrieved from the `CcInfo` providers of
39+
a target's dependencies.
40+
feature_configuration: The Swift feature configuration.
41+
module_name: The name of the module whose symbol graph should be
42+
extracted.
43+
output_file: A `File` into which the synthesized interface will be
44+
written.
45+
swift_infos: A list of `SwiftInfo` providers from dependencies of the
46+
target being compiled. This should include both propagated and
47+
non-propagated (implementation-only) dependencies.
48+
swift_toolchain: The `SwiftToolchainInfo` provider of the toolchain.
49+
"""
50+
merged_compilation_context = merge_compilation_contexts(
51+
transitive_compilation_contexts = compilation_contexts + [
52+
cc_info.compilation_context
53+
for cc_info in swift_toolchain.implicit_deps_providers.cc_infos
54+
],
55+
)
56+
merged_swift_info = SwiftInfo(
57+
swift_infos = (
58+
swift_infos + swift_toolchain.implicit_deps_providers.swift_infos
59+
),
60+
)
61+
62+
# Flattening this `depset` is necessary because we need to extract the
63+
# module maps or precompiled modules out of structured values and do so
64+
# conditionally.
65+
transitive_modules = merged_swift_info.transitive_modules.to_list()
66+
67+
transitive_swiftmodules = []
68+
for module in transitive_modules:
69+
swift_module = module.swift
70+
if swift_module:
71+
transitive_swiftmodules.append(swift_module.swiftmodule)
72+
73+
prerequisites = struct(
74+
bin_dir = feature_configuration._bin_dir,
75+
cc_compilation_context = merged_compilation_context,
76+
genfiles_dir = feature_configuration._genfiles_dir,
77+
is_swift = True,
78+
module_name = module_name,
79+
output_file = output_file,
80+
target_label = feature_configuration._label,
81+
transitive_modules = transitive_modules,
82+
transitive_swiftmodules = transitive_swiftmodules,
83+
)
84+
85+
run_toolchain_action(
86+
actions = actions,
87+
action_name = SWIFT_ACTION_SYNTHESIZE_INTERFACE,
88+
feature_configuration = feature_configuration,
89+
outputs = [output_file],
90+
prerequisites = prerequisites,
91+
progress_message = (
92+
"Synthesizing Swift interface for {}".format(module_name)
93+
),
94+
swift_toolchain = swift_toolchain,
95+
)

swift/providers.bzl

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,31 @@ has the same fields as documented in `direct_symbol_graphs`.
269269
},
270270
)
271271

272+
SwiftSynthesizedInterfaceInfo = provider(
273+
doc = "Propagates synthesized Swift interfaces for modules.",
274+
fields = {
275+
"direct_modules": """\
276+
`List` of `struct`s representing the synthesized interfaces for the modules in
277+
the target that propagated this provider. This list will be empty if propagated
278+
by a target that does not contain any modules that Swift can synthesize an
279+
interface for (i.e., C, or Swift itself), but its `transitive_modules` may be
280+
non-empty if it has dependencies for which interfaces can be synthesized.
281+
282+
Each `struct` has the following fields:
283+
284+
* `module_name`: A string denoting the name of the module whose interface is
285+
synthesized.
286+
287+
* `synthesized_interface`: A `File` containing the synthesized interface.
288+
""",
289+
"transitive_modules": """\
290+
`Depset` of `struct`s representing the synthesized interfaces for the modules in
291+
the target that propagated this provider and all of its dependencies. Each
292+
`struct` has the same fields as documented in `direct_modules`.
293+
""",
294+
},
295+
)
296+
272297
SwiftToolchainInfo = provider(
273298
doc = """
274299
Propagates information about a Swift toolchain to compilation and linking rules

swift/swift_common.bzl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ load(
3333
"get_cc_feature_configuration",
3434
"is_feature_enabled",
3535
)
36+
load(
37+
"//swift/internal:interface_synthesizing.bzl",
38+
"synthesize_interface",
39+
)
3640
load(
3741
"//swift/internal:linking.bzl",
3842
"create_linking_context_from_compilation_outputs",
@@ -60,5 +64,6 @@ swift_common = struct(
6064
get_toolchain = get_swift_toolchain,
6165
is_enabled = is_feature_enabled,
6266
precompile_clang_module = precompile_clang_module,
67+
synthesize_interface = synthesize_interface,
6368
use_toolchain = use_swift_toolchain,
6469
)
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# Copyright 2025 The Bazel Authors. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Implementation of the `swift_synthesize_interface_aspect` aspect."""
16+
17+
load(
18+
"//swift/internal:features.bzl",
19+
"configure_features",
20+
)
21+
load(
22+
"//swift/internal:interface_synthesizing.bzl",
23+
"synthesize_interface",
24+
)
25+
load(
26+
"//swift/internal:toolchain_utils.bzl",
27+
"get_swift_toolchain",
28+
"use_swift_toolchain",
29+
)
30+
load(":providers.bzl", "SwiftInfo", "SwiftSynthesizedInterfaceInfo")
31+
load(":swift_clang_module_aspect.bzl", "swift_clang_module_aspect")
32+
33+
visibility("public")
34+
35+
def _get_swift_info(target):
36+
"""Returns the SwiftInfo provider or None if it is not present."""
37+
if SwiftInfo in target:
38+
return target[SwiftInfo]
39+
else:
40+
return None
41+
42+
def _swift_synthesize_interface_aspect_impl(target, aspect_ctx):
43+
direct_outputs = []
44+
synthesized_modules = []
45+
46+
swift_info = _get_swift_info(target)
47+
if swift_info and swift_info.direct_modules:
48+
swift_toolchain = get_swift_toolchain(aspect_ctx)
49+
feature_configuration = configure_features(
50+
ctx = aspect_ctx,
51+
swift_toolchain = swift_toolchain,
52+
requested_features = aspect_ctx.features,
53+
unsupported_features = aspect_ctx.disabled_features,
54+
)
55+
56+
if CcInfo in target:
57+
compilation_context = target[CcInfo].compilation_context
58+
else:
59+
compilation_context = cc_common.create_compilation_context()
60+
61+
for module in swift_info.direct_modules:
62+
output_file = aspect_ctx.actions.declare_file(
63+
"{}.synthesized.swift".format(target.label.name),
64+
)
65+
direct_outputs.append(output_file)
66+
synthesize_interface(
67+
actions = aspect_ctx.actions,
68+
compilation_contexts = [compilation_context],
69+
feature_configuration = feature_configuration,
70+
module_name = module.name,
71+
output_file = output_file,
72+
swift_infos = [swift_info],
73+
swift_toolchain = swift_toolchain,
74+
)
75+
synthesized_modules.append(
76+
struct(
77+
module_name = module.name,
78+
synthesized_interface = output_file,
79+
),
80+
)
81+
82+
transitive_synthesized_modules = []
83+
for dep in getattr(aspect_ctx.rule.attr, "deps", []):
84+
if SwiftSynthesizedInterfaceInfo in dep:
85+
synth_info = dep[SwiftSynthesizedInterfaceInfo]
86+
transitive_synthesized_modules.append(
87+
synth_info.transitive_modules,
88+
)
89+
90+
return [
91+
OutputGroupInfo(
92+
swift_synthesized_interface = depset(direct_outputs),
93+
),
94+
SwiftSynthesizedInterfaceInfo(
95+
direct_modules = synthesized_modules,
96+
transitive_modules = depset(
97+
synthesized_modules,
98+
transitive = transitive_synthesized_modules,
99+
),
100+
),
101+
]
102+
103+
swift_synthesize_interface_aspect = aspect(
104+
attr_aspects = ["deps"],
105+
doc = """\
106+
Synthesizes the Swift interface for the target to which it is applied.
107+
108+
This aspect invokes `swift-synthesize-interface` on the target to which
109+
it is applied and produces the output in a file located at
110+
`bazel-bin/<package_name>/<target_name>.synthesized.swift`. This output
111+
file can be obtained by requesting the output group named
112+
`swift_synthesized_interface` during the build.
113+
114+
The output group only contains the synthesized interface for the target
115+
to which the aspect is *directly* applied; it does not contain the
116+
synthesized interfaces for any transitive dependencies. If the full
117+
transitive closure of synthesized interfaces is needed, then clients
118+
should read the `SwiftSynthesizedInterfaceInfo` provider, which contains
119+
a `depset` with the full transitive closure.
120+
""",
121+
fragments = ["cpp"],
122+
implementation = _swift_synthesize_interface_aspect_impl,
123+
provides = [SwiftSynthesizedInterfaceInfo],
124+
requires = [swift_clang_module_aspect],
125+
toolchains = use_swift_toolchain(),
126+
)

swift/toolchains/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ bzl_library(
2222
"//swift/toolchains/config:all_actions_config",
2323
"//swift/toolchains/config:compile_config",
2424
"//swift/toolchains/config:symbol_graph_config",
25+
"//swift/toolchains/config:synthesize_interface_config",
2526
"//swift/toolchains/config:tool_config",
2627
"@bazel_skylib//lib:dicts",
2728
"@bazel_skylib//lib:paths",
@@ -49,6 +50,7 @@ bzl_library(
4950
"//swift/toolchains/config:compile_config",
5051
"//swift/toolchains/config:compile_module_interface_config",
5152
"//swift/toolchains/config:symbol_graph_config",
53+
"//swift/toolchains/config:synthesize_interface_config",
5254
"//swift/toolchains/config:tool_config",
5355
"@bazel_skylib//lib:dicts",
5456
"@bazel_skylib//lib:paths",

swift/toolchains/config/BUILD

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,15 @@ bzl_library(
6666
],
6767
)
6868

69+
bzl_library(
70+
name = "synthesize_interface_config",
71+
srcs = ["synthesize_interface_config.bzl"],
72+
deps = [
73+
":action_config",
74+
"//swift/internal:action_names",
75+
],
76+
)
77+
6978
bzl_library(
7079
name = "tool_config",
7180
srcs = ["tool_config.bzl"],

0 commit comments

Comments
 (0)