Skip to content

Commit

Permalink
End-to-end support of SDK clang and swift modules (#901)
Browse files Browse the repository at this point in the history
### What

* Reimplemented sdk_clang_module to support interdependency among
sdk_clang_modules
* Adds rule sdk_swiftmodule_import to support SDK swift modules
* Updates xcode_sdk_frameworks to allow a swift_binary to build with
explicit SDK modules. Also updates the explicit_modules example to build
with SDK modules.

### Test

`bazel run --config=explicit_modules
example/explicit_module:hello_world`
  • Loading branch information
congt authored Aug 26, 2024
1 parent 96cd8d3 commit ebd0bd9
Show file tree
Hide file tree
Showing 9 changed files with 212 additions and 70 deletions.
3 changes: 2 additions & 1 deletion .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ common --enable_bzlmod
build --spawn_strategy=standalone

# Setup Xcode configuration.
build --xcode_version_config=//:host_xcodes
build --xcode_version=15.4.0.15F31d

build --experimental_strict_conflict_checks

Expand Down Expand Up @@ -65,6 +65,7 @@ build:remote_cache --remote_timeout=3600
# Configure for explicit module compilation
build:explicit_modules --features=swift.use_c_modules
build:explicit_modules --features=swift.emit_c_module
build:explicit_modules --features=swift.use_explicit_swift_module_map
build:explicit_modules --repo_env=EXPLICIT_MODULES=1

# By default don't upload local results to remote cache, only CI does this.
Expand Down
19 changes: 4 additions & 15 deletions example/explicit_module/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,23 +1,12 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_binary")
load("//rules/explicit_module:sdk_clang_module.bzl", "sdk_clang_module")

sdk_clang_module(
name = "swift_concurrency_shims",
module_map = "__BAZEL_XCODE_SDKROOT__/usr/lib/swift/shims/module.modulemap",
module_name = "_SwiftConcurrencyShims",
)

sdk_clang_module(
name = "shims",
module_map = "__BAZEL_XCODE_SDKROOT__/usr/lib/swift/shims/module.modulemap",
module_name = "SwiftShims",
load(
"@build_bazel_rules_swift//swift:swift.bzl",
"swift_binary",
)

swift_binary(
name = "hello_world",
srcs = ["main.swift"],
deps = [
":shims",
":swift_concurrency_shims",
"@xcode_sdk_frameworks//version15_4_0_15F31d/MacOSX:Foundation_swift",
],
)
2 changes: 2 additions & 0 deletions example/explicit_module/main.swift
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
import Foundation

print("Hello world")
20 changes: 20 additions & 0 deletions example/explicit_module/modules/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
load(
"@build_bazel_rules_swift//swift:swift.bzl",
"swift_binary",
"swift_library",
)

swift_binary(
name = "car",
srcs = ["main.swift"],
deps = [":car_module_library"],
)

swift_library(
name = "car_module_library",
srcs = ["CarModule.swift"],
module_name = "CarModule",
deps = [
"@xcode_sdk_frameworks//version15_4_0_15F31d/MacOSX:Foundation_swift",
],
)
16 changes: 16 additions & 0 deletions example/explicit_module/modules/CarModule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Foundation

public struct CarInfo {
private let number: Int
private let color: String


public init(number: Int, color: String) {
self.number = number
self.color = color
}

public func description() -> String {
"\(color) \(number)"
}
}
4 changes: 4 additions & 0 deletions example/explicit_module/modules/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import CarModule

let car = CarInfo(number: 10, color: "Grey")
print(car.description())
124 changes: 90 additions & 34 deletions rules/explicit_module/sdk_clang_module.bzl
Original file line number Diff line number Diff line change
@@ -1,47 +1,103 @@
load("@bazel_skylib//lib:dicts.bzl", "dicts")
load("@build_bazel_rules_swift//swift/internal:compiling.bzl", "precompile_clang_module")
load("@build_bazel_rules_swift//swift/internal:feature_names.bzl", "SWIFT_FEATURE_SYSTEM_MODULE")
load("@build_bazel_rules_swift//swift/internal:providers.bzl", "create_swift_info")
load(
"@build_bazel_rules_swift//swift/internal:providers.bzl",
"SwiftInfo",
"SwiftToolchainInfo",
"create_clang_module",
"create_module",
"create_swift_info",
)
load("@build_bazel_rules_swift//swift/internal:swift_common.bzl", "swift_common")
load(
"@build_bazel_rules_swift//swift/internal:utils.bzl",
"compilation_context_for_explicit_module_compilation",
"get_providers",
)

def _sdk_clang_module_impl(ctx):
compilation_context = cc_common.create_compilation_context()
compilation_context_to_compile = (
compilation_context_for_explicit_module_compilation(
compilation_contexts = (
[compilation_context]
),
deps = ctx.attr.deps,
)
)

swift_toolchain = ctx.attr._toolchain[SwiftToolchainInfo]
requested_features = ctx.features + [SWIFT_FEATURE_SYSTEM_MODULE]
feature_configuration = swift_common.configure_features(
ctx = ctx,
requested_features = requested_features,
swift_toolchain = swift_toolchain,
unsupported_features = ctx.disabled_features,
)

swift_infos = get_providers(ctx.attr.deps, SwiftInfo)

precompiled_module = precompile_clang_module(
actions = ctx.actions,
cc_compilation_context = compilation_context_to_compile,
feature_configuration = feature_configuration,
module_map_file = ctx.attr.module_map,
module_name = ctx.attr.module_name,
swift_infos = swift_infos,
swift_toolchain = swift_toolchain,
target_name = ctx.attr.name,
)

return [
swift_common.create_swift_interop_info(
module_map = ctx.attr.module_map,
module_name = ctx.attr.module_name,
requested_features = [SWIFT_FEATURE_SYSTEM_MODULE],
create_swift_info(
modules = [
create_module(
name = ctx.attr.module_name,
clang = create_clang_module(
compilation_context = compilation_context,
module_map = ctx.attr.module_map,
precompiled_module = precompiled_module,
),
),
],
swift_infos = swift_infos,
),
OutputGroupInfo(
swift_explicit_module = depset([precompiled_module]),
),
# We need to return CcInfo and its compilation_context. We may also consider to update swift_clang_module_aspect.
# See https://github.com/bazelbuild/rules_swift/blob/d68b21471e4e9d922b75e2b0621082b8ce017d11/swift/internal/swift_clang_module_aspect.bzl#L548
CcInfo(compilation_context = cc_common.create_compilation_context()),
# Required to add sdk_clang_module targets to the deps of swift_module_alias.
# TODO(cshi): create the SwiftInfo correctly
create_swift_info(),
]

sdk_clang_module = rule(
attrs = {
"deps": attr.label_list(
doc = "The deps of the SDK clang module",
),
"module_map": attr.string(
doc = """\
The path to a SDK framework module map.
Variables `__BAZEL_XCODE_SDKROOT__` and `__BAZEL_XCODE_DEVELOPER_DIR__` will be substitued
appropriately for, i.e.
`/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk`
and
`/Applications/Xcode.app/Contents/Developer` respectively.
""",
mandatory = True,
),
"module_name": attr.string(
doc = """\
The name of the top-level module in the module map that this target represents.
""",
mandatory = True,
),
},
implementation = _sdk_clang_module_impl,
fragments = ["cpp"],
attrs = dicts.add(
swift_common.toolchain_attrs(),
{
"deps": attr.label_list(
doc = "The deps of the SDK clang module",
providers = [[SwiftInfo]],
),
"module_map": attr.string(
doc = """\
The path to a SDK framework module map.
Variables `__BAZEL_XCODE_SDKROOT__` and `__BAZEL_XCODE_DEVELOPER_DIR__` will be substitued
appropriately for, i.e.
`/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk`
and
`/Applications/Xcode.app/Contents/Developer` respectively.
""",
mandatory = True,
),
"module_name": attr.string(
doc = """\
The name of the top-level module in the module map that this target represents.
""",
mandatory = True,
),
},
),
doc = """\
A rule representing a SDK clang module. It's required for explicit module builds.
""",
implementation = _sdk_clang_module_impl,
)
38 changes: 38 additions & 0 deletions rules/explicit_module/sdk_swiftmodule_import.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
def _sdk_swiftmodule_import_impl(ctx):
input_swiftmodule = ctx.attr.swiftmodule_path
if not input_swiftmodule.startswith("/"):
fail("The swiftmodule_path must be an absolute path")
if not input_swiftmodule.endswith(".swiftmodule"):
fail("Expect a .swiftmodule file, but got {}".format(input_swiftmodule))

swfitmodule_name = "{}.swiftmodule".format(ctx.attr.module_name)
output_swiftmodule = ctx.actions.declare_file(swfitmodule_name)
ctx.actions.run_shell(
outputs = [output_swiftmodule],
command = "cp -f \"$1\" \"$2\"",
arguments = [input_swiftmodule, output_swiftmodule.path],
execution_requirements = {"no-remote-exec": "1"},
mnemonic = "ImportSDKSwiftModule",
progress_message = "Import SDK swift module {}".format(swfitmodule_name),
)
return [DefaultInfo(files = depset([output_swiftmodule]))]

sdk_swiftmodule_import = rule(
implementation = _sdk_swiftmodule_import_impl,
attrs = {
"swiftmodule_path": attr.string(
doc = """\
The absolute path to the SDK swiftmodule, e.g.,
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/iphoneos/prebuilt-modules/17.5/UIKit.swiftmodule.
""",
mandatory = True,
),
"module_name": attr.string(
doc = "The swift module name",
mandatory = True,
),
},
doc = """\
A rule imports a SDK swift module and outputs it as a file. It's required for explicit module builds.
""",
)
56 changes: 36 additions & 20 deletions rules/xcode_sdk_frameworks.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ PLATFORM_TUPLES = [
("iPhoneSimulator", "ios"),
]

SDK_BUILD_FILE_TMPL = """load("@build_bazel_rules_apple//apple:apple.bzl", "apple_dynamic_framework_import")
SDK_BUILD_FILE_TMPL = """\
load("@build_bazel_rules_ios//rules/explicit_module:sdk_clang_module.bzl", "sdk_clang_module")
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_module_alias")
load("@build_bazel_rules_ios//rules/explicit_module:sdk_swiftmodule_import.bzl", "sdk_swiftmodule_import")
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_import")
package(default_visibility = ["//visibility:public"])
{sdk_targets}
Expand All @@ -38,9 +39,16 @@ sdk_clang_module(
"""

SWIFT_MODULE_TMPL = """
swift_module_alias(
sdk_swiftmodule_import(
name = "import_swiftmodule_{name}",
module_name = "{name}",
swiftmodule_path = "{swiftmodule_path}",
)
swift_import(
name = "{name}_swift",
module_name = "{name}",
swiftmodule = ":import_swiftmodule_{name}",
deps = [
{deps}
],
Expand Down Expand Up @@ -101,15 +109,15 @@ config_setting(

XCODE_VERSION_BUILD_FILE_TMPL = """package(default_visibility = ["//visibility:public"])
alias(
name = "xcode_sdk_frameworks",
actual = select({{
"//:iPhoneOS": "//{xcode_version}/iPhoneOS:bazel_xcode_imports_swift",
"//:iPhoneSimulator": "//{xcode_version}/iPhoneSimulator:bazel_xcode_imports_swift",
"//:MacOSX": "//{xcode_version}/MacOSX:bazel_xcode_imports_swift",
"//conditions:default": "//{xcode_version}/MacOSX:bazel_xcode_imports_swift",
}})
)
# alias(
# name = "xcode_sdk_frameworks",
# actual = select({{
# "//:iPhoneOS": "//{xcode_version}/iPhoneOS:bazel_xcode_imports_swift",
# "//:iPhoneSimulator": "//{xcode_version}/iPhoneSimulator:bazel_xcode_imports_swift",
# "//:MacOSX": "//{xcode_version}/MacOSX:bazel_xcode_imports_swift",
# "//conditions:default": "//{xcode_version}/MacOSX:bazel_xcode_imports_swift",
# }})
# )
"""

def _find_all_frameworks(sdk_path):
Expand Down Expand Up @@ -180,6 +188,9 @@ def _target_for_module(developer_dir, sdk_path, module_name_by_type, module_deta
module_name = module_name_by_type.get("swift") if is_swift else module_name_by_type.get("clang")
if not module_name:
fail("Expect module name by type, but got {}".format(module_name_by_type))
if module_name == "bazel_xcode_imports":
# Ignore bazel_xcode_imports
return None

module_deps = module_details.get("directDependencies", [])
deps = ["{}_c".format(d["clang"]) for d in module_deps if "clang" in d]
Expand All @@ -189,9 +200,11 @@ def _target_for_module(developer_dir, sdk_path, module_name_by_type, module_deta
deps_string = "\n".join(sorted([" \":{}\",".format(d) for d in deps]))

if is_swift:
swiftmodule_path = module_details["details"]["swift"]["compiledModuleCandidates"][0]
return SWIFT_MODULE_TMPL.format(
name = module_name,
deps = deps_string,
swiftmodule_path = swiftmodule_path,
)

clang_module_map_path = module_details["details"]["clang"]["moduleMapPath"]
Expand Down Expand Up @@ -231,13 +244,15 @@ def _create_build_file_for_sdk(
for i in range(module_count):
name_idx = 2 * i
details_idx = 2 * i + 1
targets.append(_target_for_module(
target = _target_for_module(
developer_dir = developer_dir,
sdk_path = sdk_path,
module_name_by_type = modules_info[name_idx],
module_details = modules_info[details_idx],
overrides = overrides,
))
)
if target:
targets.append(target)

build_file = SDK_BUILD_FILE_TMPL.format(sdk_targets = "".join(targets))
build_file_path = output_folder.get_child("BUILD.bazel")
Expand Down Expand Up @@ -358,7 +373,8 @@ def _create_xcode_framework_targets(
xcode_version_build_file_path = xcode_version_folder.get_child("BUILD.bazel")
repository_ctx.file(
xcode_version_build_file_path,
XCODE_VERSION_BUILD_FILE_TMPL.format(xcode_version = xcode_version_name),
XCODE_VERSION_BUILD_FILE_TMPL,
# XCODE_VERSION_BUILD_FILE_TMPL.format(xcode_version = xcode_version_name),
)

def _stub_frameworks(repository_ctx):
Expand Down Expand Up @@ -419,14 +435,14 @@ def _xcode_sdk_frameworks_impl(repository_ctx):
target = "xcode_sdk_frameworks",
))

root_alias = ROOT_ALIAS_TMPL.format(
target = "xcode_sdk_frameworks",
select_lines = ",\n ".join(framework_select_lines),
)
# root_alias = ROOT_ALIAS_TMPL.format(
# target = "xcode_sdk_frameworks",
# select_lines = ",\n ".join(framework_select_lines),
# )

root_build_file = ROOT_BUILD_FILE_TMPL.format(
config_setting_lines = "".join(config_setting_lines),
xcode_sdk_framework_alias = root_alias,
xcode_sdk_framework_alias = "",
)

repository_ctx.file("BUILD.bazel", root_build_file)
Expand Down

0 comments on commit ebd0bd9

Please sign in to comment.