From ebd0bd9dcb2b420d797c0017069e5d0cda29dc3d Mon Sep 17 00:00:00 2001 From: Cong Shi Date: Mon, 26 Aug 2024 09:53:59 -0700 Subject: [PATCH] End-to-end support of SDK clang and swift modules (#901) ### 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` --- .bazelrc | 3 +- example/explicit_module/BUILD.bazel | 19 +-- example/explicit_module/main.swift | 2 + example/explicit_module/modules/BUILD.bazel | 20 +++ .../explicit_module/modules/CarModule.swift | 16 +++ example/explicit_module/modules/main.swift | 4 + rules/explicit_module/sdk_clang_module.bzl | 124 +++++++++++++----- .../sdk_swiftmodule_import.bzl | 38 ++++++ rules/xcode_sdk_frameworks.bzl | 56 +++++--- 9 files changed, 212 insertions(+), 70 deletions(-) create mode 100644 example/explicit_module/modules/BUILD.bazel create mode 100644 example/explicit_module/modules/CarModule.swift create mode 100644 example/explicit_module/modules/main.swift create mode 100644 rules/explicit_module/sdk_swiftmodule_import.bzl diff --git a/.bazelrc b/.bazelrc index f4cfa7fd..a9d89cc7 100644 --- a/.bazelrc +++ b/.bazelrc @@ -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 @@ -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. diff --git a/example/explicit_module/BUILD.bazel b/example/explicit_module/BUILD.bazel index 77ba2012..83ced6d5 100644 --- a/example/explicit_module/BUILD.bazel +++ b/example/explicit_module/BUILD.bazel @@ -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", ], ) diff --git a/example/explicit_module/main.swift b/example/explicit_module/main.swift index 44159b39..6dc79d29 100644 --- a/example/explicit_module/main.swift +++ b/example/explicit_module/main.swift @@ -1 +1,3 @@ +import Foundation + print("Hello world") diff --git a/example/explicit_module/modules/BUILD.bazel b/example/explicit_module/modules/BUILD.bazel new file mode 100644 index 00000000..6bc35f34 --- /dev/null +++ b/example/explicit_module/modules/BUILD.bazel @@ -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", + ], +) diff --git a/example/explicit_module/modules/CarModule.swift b/example/explicit_module/modules/CarModule.swift new file mode 100644 index 00000000..df50f619 --- /dev/null +++ b/example/explicit_module/modules/CarModule.swift @@ -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)" + } +} diff --git a/example/explicit_module/modules/main.swift b/example/explicit_module/modules/main.swift new file mode 100644 index 00000000..c28418a2 --- /dev/null +++ b/example/explicit_module/modules/main.swift @@ -0,0 +1,4 @@ +import CarModule + +let car = CarInfo(number: 10, color: "Grey") +print(car.description()) diff --git a/rules/explicit_module/sdk_clang_module.bzl b/rules/explicit_module/sdk_clang_module.bzl index e8dee2ef..8fc57f18 100644 --- a/rules/explicit_module/sdk_clang_module.bzl +++ b/rules/explicit_module/sdk_clang_module.bzl @@ -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, ) diff --git a/rules/explicit_module/sdk_swiftmodule_import.bzl b/rules/explicit_module/sdk_swiftmodule_import.bzl new file mode 100644 index 00000000..34ae72ac --- /dev/null +++ b/rules/explicit_module/sdk_swiftmodule_import.bzl @@ -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. +""", +) diff --git a/rules/xcode_sdk_frameworks.bzl b/rules/xcode_sdk_frameworks.bzl index 14d20cde..fa2b8338 100644 --- a/rules/xcode_sdk_frameworks.bzl +++ b/rules/xcode_sdk_frameworks.bzl @@ -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} @@ -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} ], @@ -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): @@ -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] @@ -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"] @@ -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") @@ -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): @@ -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)