diff --git a/.bazelrc b/.bazelrc index 8bd02c0f..2816f859 100644 --- a/.bazelrc +++ b/.bazelrc @@ -36,6 +36,12 @@ build --features apple.arm64_simulator_use_device_deps # Note - tree artifacts needs to be on build:lldb_ios_test --spawn_strategy=standalone --apple_platform_type=ios --define=apple.experimental.tree_artifact_outputs=1 +# Configure for explicit module compilation +# also requires setting EXPLICIT_MODULE env variable to generate the frameworks repo. +build:explicit_modules --features=swift.use_c_modules +build:explicit_modules --features=swift.emit_c_module +build:explicit_modules --action_env=EXPLICIT_MODULES=1 + # Delete the VM test suite for github build --deleted_packages tests/ios/vmd diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4e8f2227..2e988d4e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -131,3 +131,57 @@ jobs: run: .github/workflows/xcode_select.sh - name: Build App run: bazelisk build -s tests/ios/app/App --apple_platform_type=ios --ios_minimum_os=10.2 --ios_multi_cpus=i386,x86_64 + explicit_module_tests: + name: Build and Test w/ explicit modules + runs-on: macos-12 + steps: + - uses: actions/checkout@v3 + - name: Select Xcode + run: .github/workflows/xcode_select.sh + - name: Build and Test + run: | + # Host config + bazelisk test --build_tests_only --local_test_jobs=1 \ + --config=explicit_modules -- \ + //... -//tests/ios/... + + bazelisk build --config=explicit_modules -- \ + //... -//tests/ios/... \ + -//tests/macos/xcconfig:all + + # `deleted_packages` is needed below in order to override the value of the .bazelrc file + bazelisk test --build_tests_only -k --local_test_jobs=1 --apple_platform_type=ios --deleted_packages='' \ + --config=explicit_modules -- \ + //tests/ios/... \ + -//tests/ios/frameworks/dynamic:AppWithExtension_output_test \ + -//tests/ios/frameworks/dynamic:App_output_test \ + -//tests/ios/frameworks/dynamic:TestAppWithDylibs \ + -//tests/ios/frameworks/dynamic:TestAppWithDylibs_output_test \ + -//tests/ios/frameworks/mixed-source/custom-module-map:CustomModuleMapTests \ + -//tests/ios/frameworks/mixed-source/only-source:MixedSourceTest \ + -//tests/ios/frameworks/testonly:MixedSourceTest \ + -//tests/ios/unit-test/test-imports-app:TestImports-Unit-Tests \ + -//tests/ios/frameworks/sources-with-prebuilt-binaries:MixedSourceFrameworkTests + + bazelisk build --apple_platform_type=ios --deleted_packages='' \ + --config=explicit_modules -- \ + `bazel query 'kind("ios_application rule", //...)'` \ + -//tests/ios/app:AppTestOnly \ + -//tests/ios/app:AppTestOnlyWithExtension \ + -//tests/ios/app:AppWithDefines \ + -//tests/ios/app:AppWithSelectableCopts \ + -//tests/ios/frameworks/dynamic:App \ + -//tests/ios/frameworks/dynamic:AppWithExtension \ + -//tests/ios/in-tree-vendor-prebuilt-deps/dynamic/app:App \ + -//tests/ios/in-tree-vendor-prebuilt-deps/dynamic/app:AppWithSelectableCopts \ + -//tests/ios/in-tree-vendor-prebuilt-deps/static/app:App \ + -//tests/ios/in-tree-vendor-prebuilt-deps/static/app:AppWithSelectableCopts \ + -//tests/ios/unit-test/test-imports-app:TestImports-App \ + -//tests/ios/xcconfig:App \ + -//tests/ios/xcframeworks/Basic:XCFrameworksApp \ + -//tests/ios/xcframeworks/StaticLib:StaticLibApp + - uses: actions/upload-artifact@v2 + if: failure() + with: + name: bazel-testlogs + path: bazel-testlogs diff --git a/BUILD.bazel b/BUILD.bazel index b5f5736a..962375f8 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,6 +1,6 @@ # Pull buildifer.mac as an http_file, then depend on the file group to make an # executable -load("@build_bazel_rules_swift//swift/internal:feature_names.bzl", "SWIFT_FEATURE_USE_GLOBAL_INDEX_STORE") +load("@build_bazel_rules_swift//swift/internal:feature_names.bzl", "SWIFT_FEATURE_USE_C_MODULES", "SWIFT_FEATURE_USE_GLOBAL_INDEX_STORE") load("//rules:features.bzl", "feature_names") load("@bazel_skylib//lib:selects.bzl", "selects") @@ -19,6 +19,13 @@ config_setting( }, ) +config_setting( + name = "explicit_modules", + values = { + "features": SWIFT_FEATURE_USE_C_MODULES, + }, +) + config_setting( name = "virtualize_frameworks", values = { diff --git a/WORKSPACE b/WORKSPACE index 8e8ee513..c0b37ad4 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -105,7 +105,6 @@ load_framework_dependencies() load("//tools/toolchains/xcode_configure:xcode_configure.bzl", "xcode_configure") xcode_configure( - remote_xcode_label = "", xcode_locator_label = "//tools/toolchains/xcode_configure:xcode_locator.m", ) diff --git a/rules/legacy_xcodeproj.bzl b/rules/legacy_xcodeproj.bzl index 9a8d2307..ac17274c 100644 --- a/rules/legacy_xcodeproj.bzl +++ b/rules/legacy_xcodeproj.bzl @@ -348,7 +348,7 @@ def _xcodeproj_aspect_impl(target, ctx): swift_info = target[SwiftInfo] swift_defines.append(depset(_collect_swift_defines(swift_info.direct_modules))) swift_defines.append(depset(_collect_swift_defines(swift_info.transitive_modules.to_list()))) - swift_module_paths = [m.swift.swiftmodule.path for m in target[SwiftInfo].direct_modules] + swift_module_paths = [m.swift.swiftmodule.path for m in target[SwiftInfo].direct_modules if m.swift] providers.append( _SrcsInfo( srcs = depset(srcs, transitive = _get_attr_values_for_name(deps, _SrcsInfo, "srcs")), diff --git a/rules/library.bzl b/rules/library.bzl index 50e58a87..b7b1f267 100644 --- a/rules/library.bzl +++ b/rules/library.bzl @@ -561,7 +561,7 @@ def apple_library(name, library_tools = {}, export_private_headers = True, names tags_manual = tags if "manual" in tags else tags + _MANUAL platforms = kwargs.pop("platforms", None) private_deps = [] + kwargs.pop("private_deps", []) - lib_names = [] + lib_names = ["@xcode_sdk_frameworks"] fetch_default_xcconfig = library_tools["fetch_default_xcconfig"](name, default_xcconfig_name, **kwargs) if default_xcconfig_name else {} copts_by_build_setting = copts_by_build_setting_with_defaults(xcconfig, fetch_default_xcconfig, xcconfig_by_build_setting) enable_framework_vfs = kwargs.pop("enable_framework_vfs", False) or namespace_is_module_name @@ -915,13 +915,10 @@ def apple_library(name, library_tools = {}, export_private_headers = True, names if swift_sources: additional_swift_copts.extend(("-Xcc", "-I.")) - if module_map: - # Frameworks find the modulemap file via the framework vfs overlay - if not namespace_is_module_name: - additional_swift_copts += ["-Xcc", "-fmodule-map-file=" + "$(execpath " + module_map + ")"] - additional_swift_copts.append( - "-import-underlying-module", - ) + + # Frameworks find the modulemap file via the framework vfs overlay + if module_map and not namespace_is_module_name: + additional_swift_copts += ["-Xcc", "-fmodule-map-file=" + "$(execpath " + module_map + ")"] swiftc_inputs = other_inputs + objc_hdrs + objc_private_hdrs if module_map: swiftc_inputs.append(module_map) @@ -971,11 +968,14 @@ def apple_library(name, library_tools = {}, export_private_headers = True, names copts = copts_by_build_setting.swift_copts + swift_copts + select({ "@build_bazel_rules_ios//:virtualize_frameworks": framework_vfs_swift_copts, "//conditions:default": framework_vfs_swift_copts if enable_framework_vfs else [], + }) + select({ + "@build_bazel_rules_ios//:explicit_modules": [], + "//conditions:default": ["-import-underlying-module"] if module_map else [], }) + additional_swift_copts, deps = deps + private_deps + lib_names + select({ "@build_bazel_rules_ios//:virtualize_frameworks": [framework_vfs_overlay_name_swift], "//conditions:default": [framework_vfs_overlay_name_swift] if enable_framework_vfs else [], - }), + }) + (["@xcode_sdk_frameworks//:XCTest"] if testonly else []), swiftc_inputs = swiftc_inputs, features = ["swift.no_generated_module_map", "swift.use_pch_output_dir"] + select({ "@build_bazel_rules_ios//:virtualize_frameworks": ["swift.vfsoverlay"], diff --git a/rules/repositories.bzl b/rules/repositories.bzl index 7c9184cc..00b42f37 100644 --- a/rules/repositories.bzl +++ b/rules/repositories.bzl @@ -111,6 +111,7 @@ swift_binary( name = "arm64-to-sim", srcs = glob(["Sources/arm64-to-sim/*.swift"]), visibility = ["//visibility:public"], + deps = ["@xcode_sdk_frameworks"], ) """, ) diff --git a/rules/third_party/xcbuildkit_repositories.bzl b/rules/third_party/xcbuildkit_repositories.bzl index 235c539a..34cad7a3 100644 --- a/rules/third_party/xcbuildkit_repositories.bzl +++ b/rules/third_party/xcbuildkit_repositories.bzl @@ -83,7 +83,7 @@ swift_library( name = name, srcs = ",\n".join(['"%s"' % x for x in srcs]), defines = ",\n".join(['"%s"' % x for x in defines]), - deps = ",\n".join(['"%s"' % namespaced_dep_name(x) for x in deps]), + deps = ",\n".join(['"%s"' % namespaced_dep_name(x) for x in deps] + ['"@xcode_sdk_frameworks"']), copts = ",\n".join(['"%s"' % x for x in copts]), )) diff --git a/tests/ios/frameworks/mixed-source/swift-library-compatibility/BUILD.bazel b/tests/ios/frameworks/mixed-source/swift-library-compatibility/BUILD.bazel index 3553e55b..bae96fc5 100644 --- a/tests/ios/frameworks/mixed-source/swift-library-compatibility/BUILD.bazel +++ b/tests/ios/frameworks/mixed-source/swift-library-compatibility/BUILD.bazel @@ -6,6 +6,7 @@ swift_library( srcs = ["empty.swift"], deps = [ "//tests/ios/frameworks/objc:ObjcFramework", + "@xcode_sdk_frameworks", ], ) diff --git a/tests/ios/xcodeproj/fixtures/test_custom_output_path_expected_diff.txt b/tests/ios/xcodeproj/fixtures/test_custom_output_path_expected_diff.txt index d3ade379..d0337d9c 100755 --- a/tests/ios/xcodeproj/fixtures/test_custom_output_path_expected_diff.txt +++ b/tests/ios/xcodeproj/fixtures/test_custom_output_path_expected_diff.txt @@ -1,13 +1,13 @@ diff -r ./tests/ios/xcodeproj/Test-LLDB-Logs-Project.xcodeproj/project.pbxproj ./tests/ios/xcodeproj/custom_output_path/Test-LLDB-Logs-Project.xcodeproj/project.pbxproj -45c45 +47c47 < path = ../../..; --- > path = ../../../..; -211c211 +213c213 < BAZEL_WORKSPACE_ROOT = $SRCROOT/../../..; --- > BAZEL_WORKSPACE_ROOT = $SRCROOT/../../../..; -268c268 +270c270 < BAZEL_WORKSPACE_ROOT = $SRCROOT/../../..; --- > BAZEL_WORKSPACE_ROOT = $SRCROOT/../../../..; diff --git a/tools/toolchains/xcode_configure/xcode_configure.bzl b/tools/toolchains/xcode_configure/xcode_configure.bzl index 5ce1b5bc..88832c66 100644 --- a/tools/toolchains/xcode_configure/xcode_configure.bzl +++ b/tools/toolchains/xcode_configure/xcode_configure.bzl @@ -17,166 +17,10 @@ installed on the local host. """ -_EXECUTE_TIMEOUT = 120 - -def _search_string(fullstring, prefix, suffix): - """Returns the substring between two given substrings of a larger string. - - Args: - fullstring: The larger string to search. - prefix: The substring that should occur directly before the returned string. - suffix: The substring that should occur directly after the returned string. - Returns: - A string occurring in fullstring exactly prefixed by prefix, and exactly - terminated by suffix. For example, ("hello goodbye", "lo ", " bye") will - return "good". If there is no such string, returns the empty string. - """ - - prefix_index = fullstring.find(prefix) - if (prefix_index < 0): - return "" - result_start_index = prefix_index + len(prefix) - suffix_index = fullstring.find(suffix, result_start_index) - if (suffix_index < 0): - return "" - return fullstring[result_start_index:suffix_index] - -def _search_sdk_output(output, sdkname): - """Returns the sdk version given xcodebuild stdout and an sdkname.""" - return _search_string(output, "(%s" % sdkname, ")") - -def _xcode_version_output(repository_ctx, name, version, aliases, developer_dir): - """Returns a string containing an xcode_version build target.""" - build_contents = "" - decorated_aliases = [] - error_msg = "" - for alias in aliases: - decorated_aliases.append("'%s'" % alias) - xcodebuild_result = repository_ctx.execute( - ["xcrun", "xcodebuild", "-version", "-sdk"], - _EXECUTE_TIMEOUT, - {"DEVELOPER_DIR": developer_dir}, - ) - if (xcodebuild_result.return_code != 0): - error_msg = ( - "Invoking xcodebuild failed, developer dir: {devdir} ," + - "return code {code}, stderr: {err}, stdout: {out}" - ).format( - devdir = developer_dir, - code = xcodebuild_result.return_code, - err = xcodebuild_result.stderr, - out = xcodebuild_result.stdout, - ) - fail(error_msg) +load(":xcode_locator.bzl", "run_xcode_locator", "search_string", "xcode_version_output") +load(":xcode_sdk_frameworks.bzl", "xcode_sdk_frameworks") - ios_sdk_version = _search_sdk_output(xcodebuild_result.stdout, "iphoneos") - tvos_sdk_version = _search_sdk_output(xcodebuild_result.stdout, "appletvos") - macos_sdk_version = _search_sdk_output(xcodebuild_result.stdout, "macosx") - watchos_sdk_version = _search_sdk_output(xcodebuild_result.stdout, "watchos") - build_contents += "xcode_version(\n name = '%s'," % name - build_contents += "\n version = '%s'," % version - if aliases: - build_contents += "\n aliases = [%s]," % " ,".join(decorated_aliases) - if ios_sdk_version: - build_contents += "\n default_ios_sdk_version = '%s'," % ios_sdk_version - if tvos_sdk_version: - build_contents += "\n default_tvos_sdk_version = '%s'," % tvos_sdk_version - if macos_sdk_version: - build_contents += "\n default_macos_sdk_version = '%s'," % macos_sdk_version - if watchos_sdk_version: - build_contents += "\n default_watchos_sdk_version = '%s'," % watchos_sdk_version - build_contents += "\n)\n" - return build_contents - -VERSION_CONFIG_STUB = "xcode_config(name = 'host_xcodes')" - -def run_xcode_locator(repository_ctx, xcode_locator_src_label): - """Generates xcode-locator from source and runs it. - - Builds xcode-locator in the current repository directory. - Returns the standard output of running xcode-locator with -v, which will - return information about locally installed Xcode toolchains and the versions - they are associated with. - - This should only be invoked on a darwin OS, as xcode-locator cannot be built - otherwise. - - Args: - repository_ctx: The repository context. - xcode_locator_src_label: The label of the source file for xcode-locator. - Returns: - A list representing installed xcode toolchain information. Each - element of the list is a struct containing information for one installed - toolchain. This is an empty list if there was an error building or - running xcode-locator. - """ - xcodeloc_src_path = str(repository_ctx.path(xcode_locator_src_label)) - env = repository_ctx.os.environ - xcrun_result = repository_ctx.execute([ - "env", - "-i", - "DEVELOPER_DIR={}".format(env.get("DEVELOPER_DIR", default = "")), - "xcrun", - "--sdk", - "macosx", - "clang", - "-mmacosx-version-min=10.9", - "-fobjc-arc", - "-framework", - "CoreServices", - "-framework", - "Foundation", - "-o", - "xcode-locator-bin", - xcodeloc_src_path, - ], _EXECUTE_TIMEOUT) - - if (xcrun_result.return_code != 0): - suggestion = "" - if "Agreeing to the Xcode/iOS license" in xcrun_result.stderr: - suggestion = ("(You may need to sign the xcode license." + - " Try running 'sudo xcodebuild -license')") - error_msg = ( - "Generating xcode-locator-bin failed. {suggestion} " + - "return code {code}, stderr: {err}, stdout: {out}" - ).format( - suggestion = suggestion, - code = xcrun_result.return_code, - err = xcrun_result.stderr, - out = xcrun_result.stdout, - ) - fail(error_msg.replace("\n", " ")) - - xcode_locator_result = repository_ctx.execute( - ["./xcode-locator-bin", "-v"], - _EXECUTE_TIMEOUT, - ) - if (xcode_locator_result.return_code != 0): - error_msg = ( - "Invoking xcode-locator failed, " + - "return code {code}, stderr: {err}, stdout: {out}" - ).format( - code = xcode_locator_result.return_code, - err = xcode_locator_result.stderr, - out = xcode_locator_result.stdout, - ) - - fail(error_msg.replace("\n", " ")) - xcode_toolchains = [] - - # xcode_dump is comprised of newlines with different installed xcode versions, - # each line of the form ::. - xcode_dump = xcode_locator_result.stdout - for xcodeversion in xcode_dump.split("\n"): - if ":" in xcodeversion: - infosplit = xcodeversion.split(":") - toolchain = struct( - version = infosplit[0], - aliases = infosplit[1].split(","), - developer_dir = infosplit[2], - ) - xcode_toolchains.append(toolchain) - return xcode_toolchains +_EXECUTE_TIMEOUT = 120 def _darwin_build_file(repository_ctx): """Evaluates local system state to create xcode_config and xcode_version targets.""" @@ -198,8 +42,8 @@ def _darwin_build_file(repository_ctx): default_xcode_version = "" default_xcode_build_version = "" if xcodebuild_result.return_code == 0: - default_xcode_version = _search_string(xcodebuild_result.stdout, "Xcode ", "\n") - default_xcode_build_version = _search_string( + default_xcode_version = search_string(xcodebuild_result.stdout, "Xcode ", "\n") + default_xcode_build_version = search_string( xcodebuild_result.stdout, "Build version ", "\n", @@ -213,13 +57,14 @@ def _darwin_build_file(repository_ctx): aliases = toolchain.aliases developer_dir = toolchain.developer_dir target_name = "version%s" % version.replace(".", "_") - buildcontents += _xcode_version_output( + version_output = xcode_version_output( repository_ctx, target_name, version, aliases, developer_dir, ) + buildcontents += version_output target_label = "':%s'" % target_name target_names.append(target_label) if (version.startswith(default_xcode_version) and @@ -249,6 +94,8 @@ def _darwin_build_file(repository_ctx): buildcontents += "\n)\n" return buildcontents +VERSION_CONFIG_STUB = "xcode_config(name = 'host_xcodes')" + def _impl(repository_ctx): """Implementation for the local_config_xcode repository rule. @@ -285,3 +132,7 @@ def xcode_configure(xcode_locator_label, remote_xcode_label = None): xcode_locator = xcode_locator_label, remote_xcode = remote_xcode_label, ) + xcode_sdk_frameworks( + name = "xcode_sdk_frameworks", + xcode_locator = xcode_locator_label, + ) diff --git a/tools/toolchains/xcode_configure/xcode_locator.bzl b/tools/toolchains/xcode_configure/xcode_locator.bzl new file mode 100644 index 00000000..a28052b1 --- /dev/null +++ b/tools/toolchains/xcode_configure/xcode_locator.bzl @@ -0,0 +1,171 @@ +_EXECUTE_TIMEOUT = 120 + +def search_string(fullstring, prefix, suffix): + """Returns the substring between two given substrings of a larger string. + + Args: + fullstring: The larger string to search. + prefix: The substring that should occur directly before the returned string. + suffix: The substring that should occur directly after the returned string. + Returns: + A string occurring in fullstring exactly prefixed by prefix, and exactly + terminated by suffix. For example, ("hello goodbye", "lo ", " bye") will + return "good". If there is no such string, returns the empty string. + """ + + prefix_index = fullstring.find(prefix) + if (prefix_index < 0): + return "" + result_start_index = prefix_index + len(prefix) + suffix_index = fullstring.find(suffix, result_start_index) + if (suffix_index < 0): + return "" + return fullstring[result_start_index:suffix_index] + +def _search_sdk_output(output, sdkname): + """Returns the sdk version given xcodebuild stdout and an sdkname.""" + return search_string(output, "(%s" % sdkname, ")") + +def _xcode_versions(repository_ctx, developer_dir): + xcodebuild_result = repository_ctx.execute( + ["xcrun", "xcodebuild", "-version", "-sdk"], + _EXECUTE_TIMEOUT, + {"DEVELOPER_DIR": developer_dir}, + ) + error_msg = "" + if (xcodebuild_result.return_code != 0): + error_msg = ( + "Invoking xcodebuild failed, developer dir: {devdir} ," + + "return code {code}, stderr: {err}, stdout: {out}" + ).format( + devdir = developer_dir, + code = xcodebuild_result.return_code, + err = xcodebuild_result.stderr, + out = xcodebuild_result.stdout, + ) + fail(error_msg) + + ios_sdk_version = _search_sdk_output(xcodebuild_result.stdout, "iphoneos") + tvos_sdk_version = _search_sdk_output(xcodebuild_result.stdout, "appletvos") + macos_sdk_version = _search_sdk_output(xcodebuild_result.stdout, "macosx") + watchos_sdk_version = _search_sdk_output(xcodebuild_result.stdout, "watchos") + return ios_sdk_version, tvos_sdk_version, macos_sdk_version, watchos_sdk_version + +def xcode_version_dict(repository_ctx, developer_dir): + ios_sdk_version, tvos_sdk_version, macos_sdk_version, watchos_sdk_version = _xcode_versions(repository_ctx, developer_dir) + return { + "ios": ios_sdk_version, + "tvos": tvos_sdk_version, + "macos": macos_sdk_version, + "watchos": watchos_sdk_version, + } + +def xcode_version_output(repository_ctx, name, version, aliases, developer_dir): + """Returns a string containing an xcode_version build target.""" + build_contents = "" + decorated_aliases = [] + for alias in aliases: + decorated_aliases.append("'%s'" % alias) + ios_sdk_version, tvos_sdk_version, macos_sdk_version, watchos_sdk_version = _xcode_versions(repository_ctx, developer_dir) + build_contents += "xcode_version(\n name = '%s'," % name + build_contents += "\n version = '%s'," % version + if aliases: + build_contents += "\n aliases = [%s]," % " ,".join(decorated_aliases) + if ios_sdk_version: + build_contents += "\n default_ios_sdk_version = '%s'," % ios_sdk_version + if tvos_sdk_version: + build_contents += "\n default_tvos_sdk_version = '%s'," % tvos_sdk_version + if macos_sdk_version: + build_contents += "\n default_macos_sdk_version = '%s'," % macos_sdk_version + if watchos_sdk_version: + build_contents += "\n default_watchos_sdk_version = '%s'," % watchos_sdk_version + build_contents += "\n)\n" + return build_contents + +def run_xcode_locator(repository_ctx, xcode_locator_src_label): + """Generates xcode-locator from source and runs it. + + Builds xcode-locator in the current repository directory. + Returns the standard output of running xcode-locator with -v, which will + return information about locally installed Xcode toolchains and the versions + they are associated with. + + This should only be invoked on a darwin OS, as xcode-locator cannot be built + otherwise. + + Args: + repository_ctx: The repository context. + xcode_locator_src_label: The label of the source file for xcode-locator. + Returns: + A list representing installed xcode toolchain information. Each + element of the list is a struct containing information for one installed + toolchain. This is an empty list if there was an error building or + running xcode-locator. + """ + xcodeloc_src_path = str(repository_ctx.path(xcode_locator_src_label)) + env = repository_ctx.os.environ + xcrun_result = repository_ctx.execute([ + "env", + "-i", + "DEVELOPER_DIR={}".format(env.get("DEVELOPER_DIR", default = "")), + "xcrun", + "--sdk", + "macosx", + "clang", + "-mmacosx-version-min=10.9", + "-fobjc-arc", + "-framework", + "CoreServices", + "-framework", + "Foundation", + "-o", + "xcode-locator-bin", + xcodeloc_src_path, + ], _EXECUTE_TIMEOUT) + + if (xcrun_result.return_code != 0): + suggestion = "" + if "Agreeing to the Xcode/iOS license" in xcrun_result.stderr: + suggestion = ("(You may need to sign the xcode license." + + " Try running 'sudo xcodebuild -license')") + error_msg = ( + "Generating xcode-locator-bin failed. {suggestion} " + + "return code {code}, stderr: {err}, stdout: {out}" + ).format( + suggestion = suggestion, + code = xcrun_result.return_code, + err = xcrun_result.stderr, + out = xcrun_result.stdout, + ) + fail(error_msg.replace("\n", " ")) + + xcode_locator_result = repository_ctx.execute( + ["./xcode-locator-bin", "-v"], + _EXECUTE_TIMEOUT, + ) + if (xcode_locator_result.return_code != 0): + error_msg = ( + "Invoking xcode-locator failed, " + + "return code {code}, stderr: {err}, stdout: {out}" + ).format( + code = xcode_locator_result.return_code, + err = xcode_locator_result.stderr, + out = xcode_locator_result.stdout, + ) + + fail(error_msg.replace("\n", " ")) + xcode_toolchains = [] + + # xcode_dump is comprised of newlines with different installed xcode versions, + # each line of the form ::. + xcode_dump = xcode_locator_result.stdout + for xcodeversion in xcode_dump.split("\n"): + if ":" in xcodeversion: + infosplit = xcodeversion.split(":") + toolchain = struct( + version = infosplit[0], + aliases = infosplit[1].split(","), + developer_dir = infosplit[2], + ) + xcode_toolchains.append(toolchain) + return xcode_toolchains diff --git a/tools/toolchains/xcode_configure/xcode_sdk_frameworks.bzl b/tools/toolchains/xcode_configure/xcode_sdk_frameworks.bzl new file mode 100644 index 00000000..b4dea826 --- /dev/null +++ b/tools/toolchains/xcode_configure/xcode_sdk_frameworks.bzl @@ -0,0 +1,424 @@ +"""Repository rule for adding explicit module dependencies via rules_swift. + +Locates all local Xcode versions, scans through the frameworks in all SDKs, and makes them +accessible via `@xcode_sdk_frameworks` or similar. All swift targets will +need to depend directly on this module in order to build with explicit modules. +""" + +load(":xcode_locator.bzl", "run_xcode_locator", "xcode_version_dict") + +PLATFORM_TUPLES = [ + ("AppleTVOS", "tvos"), + ("AppleTVSimulator", "tvos"), + ("MacOSX", "macos"), + ("WatchOS", "watchos"), + ("WatchSimulator", "watchos"), + ("iPhoneOS", "ios"), + ("iPhoneSimulator", "ios"), +] + +BUILD_FILE_HEADER = """load("@build_bazel_rules_swift//swift:swift.bzl", "swift_module_alias", "swift_c_module") +load("@build_bazel_rules_apple//apple:apple.bzl", "apple_dynamic_framework_import") + +package(default_visibility = ["//visibility:public"]) + +""" + +IMPORTS_FILE = "bazel_xcode_imports.swift" + +C_MODULE_TMPL = """ +swift_c_module( + name = "{name}_c", + module_name = "{name}", + system_module_map = "{module_map_file}", + deps = [ +{deps} + ], +) +""" + +SWIFT_MODULE_TMPL = """ +swift_module_alias( + name = "{name}_swift", + deps = [ +{deps} + ], +) +""" + +XCTEST_LIB = """ +apple_dynamic_framework_import( + name = "XCTest", + framework_imports = glob(["XCTest.framework/**"]), + deps = [ + ":CoreGraphics_c", + ":Foundation_c", + ] + select({ + "//:iPhoneOS": ["UIKit_c"], + "//:iPhoneSimulator": ["UIKit_c"], + "//:MacOSX": ["AppKit_c"], + "//:AppleTVOS": ["UIKit_c"], + "//:AppleTVSimulator": ["UIKit_c"], + "//:WatchOS": ["UIKit_c"], + "//:WatchSimulator": ["UIKit_c"], + "//conditions:default": ["AppKit_c"], + }) +) +""" + +ROOT_BUILD_FILE = """load("@bazel_skylib//lib:selects.bzl", "selects") + +package(default_visibility = ["//visibility:public"]) + +selects.config_setting_group( + name = "iPhoneOS", + match_any = ["@build_bazel_rules_apple//apple:ios_armv7", "@build_bazel_rules_apple//apple:ios_arm64"], +) + +selects.config_setting_group( + name = "iPhoneSimulator", + match_any = ["@build_bazel_rules_apple//apple:ios_i386", "@build_bazel_rules_apple//apple:ios_sim_arm64", "@build_bazel_rules_apple//apple:ios_x86_64"], +) + +selects.config_setting_group( + name = "AppleTVOS", + match_any = ["@build_bazel_rules_apple//apple:tvos_arm64"], +) + +selects.config_setting_group( + name = "AppleTVSimulator", + match_any = ["@build_bazel_rules_apple//apple:tvos_sim_arm64", "@build_bazel_rules_apple//apple:tvos_x86_64"], +) + +selects.config_setting_group( + name = "WatchOS", + match_any = ["@build_bazel_rules_apple//apple:watchos_arm64", "@build_bazel_rules_apple//apple:watchos_armv7k"], +) + +selects.config_setting_group( + name = "WatchSimulator", + match_any = ["@build_bazel_rules_apple//apple:watchos_x86_64", "@build_bazel_rules_apple//apple:watchos_arm64_32", "@build_bazel_rules_apple//apple:watchos_i386"], +) + +selects.config_setting_group( + name = "MacOSX", + match_any = ["@build_bazel_rules_apple//apple:darwin_x86_64", "@build_bazel_rules_apple//apple:darwin_arm64", "@build_bazel_rules_apple//apple:darwin_arm64e"], +) +""" + +ROOT_ALIAS_TMPL = """ +alias( + name = "{target}", + actual = select({{ + {select_lines}, + }}) +) +""" + +SELECT_LINE_TMPL = '":{label}": "//{xcode_dir}:{target}"' + +CONFIG_SETTING_TMPL = """ +config_setting( + name = "{label}", + flag_values = {{ + "@bazel_tools//tools/osx:xcode_version_flag_exact": "{version}", + }}, +) +""" + +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", + "//:AppleTVOS": "//{xcode_version}/AppleTVOS:bazel_xcode_imports_swift", + "//:AppleTVSimulator": "//{xcode_version}/AppleTVSimulator:bazel_xcode_imports_swift", + "//:WatchOS": "//{xcode_version}/WatchOS:bazel_xcode_imports_swift", + "//:WatchSimulator": "//{xcode_version}/WatchSimulator:bazel_xcode_imports_swift", + "//conditions:default": "//{xcode_version}/MacOSX:bazel_xcode_imports_swift", + }}) +) + +alias( + name = "XCTest", + actual = select({{ + "//:iPhoneOS": "//{xcode_version}/iPhoneOS:XCTest", + "//:iPhoneSimulator": "//{xcode_version}/iPhoneSimulator:XCTest", + "//:MacOSX": "//{xcode_version}/MacOSX:XCTest", + "//:AppleTVOS": "//{xcode_version}/AppleTVOS:XCTest", + "//:AppleTVSimulator": "//{xcode_version}/AppleTVSimulator:XCTest", + "//:WatchOS": "//{xcode_version}/WatchOS:XCTest", + "//:WatchSimulator": "//{xcode_version}/WatchSimulator:XCTest", + "//conditions:default": "//{xcode_version}/MacOSX:XCTest", + }}) +) + +""" + +def _find_all_frameworks(sdk_path): + frameworks_dir = sdk_path.get_child("System").get_child("Library").get_child("Frameworks") + if not frameworks_dir.exists: + # Skip things like DriverKit + return [] + frameworks = frameworks_dir.readdir() + return [ + f.basename.replace(".framework", "") + for f in frameworks + if f.basename.endswith(".framework") and not f.basename.startswith("_") + ] + +def _find_all_swift_libs(sdk_path): + swift_lib_dir = sdk_path.get_child("usr").get_child("lib").get_child("swift") + swift_libs = swift_lib_dir.readdir() + return [ + s.basename.replace(".swiftmodule", "") + for s in swift_libs + if s.basename.endswith(".swiftmodule") and not s.basename.startswith("_") + ] + +def _get_dependency_graph(repository_ctx, developer_dir, sdk_path, target_triple): + """Scans the dependencies for a library that imports every framework in a given SDK. + + Returns the JSON obtained from swiftc. + """ + frameworks = _find_all_frameworks(sdk_path) + frameworks.extend(_find_all_swift_libs(sdk_path)) + + import_file_content = "".join(["import {}\n".format(f) for f in frameworks]) + repository_ctx.file(IMPORTS_FILE, content = import_file_content) + resource_dir = "{}/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift".format(developer_dir) + swiftc = "{}/Toolchains/XcodeDefault.xctoolchain/usr/bin/swiftc".format(developer_dir) + + repository_ctx.report_progress("Scanning deps for {}".format(sdk_path)) + + deps_result = repository_ctx.execute( + [ + swiftc, + "-scan-dependencies", + "-sdk", + sdk_path, + "-resource-dir", + resource_dir, + "-target", + target_triple, + IMPORTS_FILE, + ], + ) + repository_ctx.delete(IMPORTS_FILE) + if deps_result.return_code != 0: + fail("Could not scan dependencies for {}\n{}".format(sdk_path, deps_result.stderr)) + return json.decode(deps_result.stdout) + +def _sub_bazel_path_vars(path, sdk_path, developer_dir): + path = path.replace(str(sdk_path), "__BAZEL_XCODE_SDKROOT__") + path = path.replace(str(developer_dir), "__BAZEL_XCODE_DEVELOPER_DIR__") + return path + +def _rule_for_pkg(developer_dir, sdk_path, pkg, overrides): + """Returns a string containing the BUILD target for a given framework.""" + module_path = pkg.get("modulePath", "") + if not module_path: + return "" + module_name = module_path.replace(".pcm", "").replace(".swiftmodule", "") + + clang = pkg.get("details", {}).get("clang", {}) + + pkg_deps = pkg.get("directDependencies", []) + deps = ["{}_c".format(d["clang"]) for d in pkg_deps if "clang" in d] + deps += ["{}_swift".format(d["swift"]) for d in pkg_deps if "swift" in d] + if overrides and not clang: + deps += overrides.get(module_name, []) + deps_string = "\n".join(sorted([" \":{}\",".format(d) for d in deps])) + + if clang: + return C_MODULE_TMPL.format( + name = module_name, + module_map_file = _sub_bazel_path_vars(clang["moduleMapPath"], sdk_path, developer_dir), + deps = deps_string, + ) + + return SWIFT_MODULE_TMPL.format( + name = module_name, + deps = deps_string, + ) + +def _create_build_file_for_sdk( + repository_ctx, + developer_dir, + sdk_path, + output_folder, + platform_name, + target_triple, + overrides): + """Creates a BUILD.bazel file connecting all the frameworks in a specified SDK.""" + + scan_deps = _get_dependency_graph( + repository_ctx = repository_ctx, + developer_dir = developer_dir, + sdk_path = sdk_path, + target_triple = target_triple, + ) + + repository_ctx.file(output_folder.get_child("deps.json"), json.encode(scan_deps)) + + build_file = BUILD_FILE_HEADER + + for pkg in scan_deps.get("modules", []): + build_file += _rule_for_pkg( + developer_dir = developer_dir, + sdk_path = sdk_path, + pkg = pkg, + overrides = overrides, + ) + + repository_ctx.symlink( + "{developer_dir}/Platforms/{platform}.platform/Developer/Library/Frameworks/XCTest.framework".format( + developer_dir = developer_dir, + platform = platform_name, + ), + output_folder.get_child("XCTest.framework"), + ) + build_file += XCTEST_LIB + + build_file_path = output_folder.get_child("BUILD.bazel") + repository_ctx.file(build_file_path, build_file) + +def _platform_target_triple(host_cpu, platform, target_name, version): + is_simulator = platform.endswith("Simulator") + if host_cpu != "aarch64" and (is_simulator or platform == "MacOSX"): + cpu = "x86_64" + elif target_name == "watchos" and not is_simulator: + cpu = "arm64_32" + else: + cpu = "arm64" + return "{cpu}-apple-{platform}{version}{suffix}".format( + cpu = cpu, + platform = target_name, + version = version, + suffix = "-simulator" if is_simulator else "", + ) + +def _get_overrides(platform, xcode_version): + """Various hacks to work around issues with dependency scanning.""" + overrides = {} + if xcode_version >= "14.1" and platform == "MacOSX": + overrides["_StoreKit_SwiftUI"] = ["LocalAuthenticationEmbeddedUI_c"] + overrides["_GroupActivities_AppKit"] = ["_CoreData_CloudKit_swift"] + if xcode_version >= "14.0": + overrides["GroupActivities"] = ["_CoreData_CloudKit_swift"] + return overrides + +def _create_xcode_framework_targets( + repository_ctx, + xcode_version_name, + developer_dir, + xcode_version): + """Creates a BUILD file for all the SDK frameworks contained in a given Xcode version.""" + developer_dir_path = repository_ctx.path(developer_dir) + platforms_dir = developer_dir_path.get_child("Platforms") + versions = xcode_version_dict(repository_ctx, developer_dir) + xcode_version_folder = repository_ctx.path(xcode_version_name) + host_cpu = repository_ctx.os.arch + for platform_name, target_name in PLATFORM_TUPLES: + platform_dir = platforms_dir.get_child(platform_name + ".platform") + platform_version = versions[target_name] + + sdk_path = platform_dir.get_child("Developer").get_child("SDKs").get_child("{}.sdk".format(platform_name)) + target_triple = _platform_target_triple(host_cpu, platform_name, target_name, platform_version) + + output_folder = xcode_version_folder.get_child(platform_name) + overrides = _get_overrides(platform_name, xcode_version) + _create_build_file_for_sdk( + repository_ctx = repository_ctx, + developer_dir = developer_dir_path, + sdk_path = sdk_path, + output_folder = output_folder, + platform_name = platform_name, + target_triple = target_triple, + overrides = overrides, + ) + + 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), + ) + +def _stub_frameworks(repository_ctx): + repository_ctx.file( + "BUILD.bazel", + content = """load("@build_bazel_rules_swift//swift:swift.bzl", "swift_module_alias") + +package(default_visibility = ["//visibility:public"]) + +objc_library( + name = "xcode_sdk_frameworks", +) + +objc_library( + name = "XCTest", +) + +""", + ) + +def _impl(repository_ctx): + os_name = repository_ctx.os.name.lower() + + if not os_name.startswith("mac os"): + return + + use_explicit_modules = repository_ctx.os.environ.get("EXPLICIT_MODULES", False) + if not use_explicit_modules: + _stub_frameworks(repository_ctx) + return + + toolchains = run_xcode_locator( + repository_ctx, + Label(repository_ctx.attr.xcode_locator), + ) + + build_file = ROOT_BUILD_FILE + framework_select_lines = [] + xctest_select_lines = [] + + for toolchain in toolchains: + target_name = "version{}".format(toolchain.version.replace(".", "_")) + _create_xcode_framework_targets( + repository_ctx = repository_ctx, + xcode_version_name = target_name, + developer_dir = toolchain.developer_dir, + xcode_version = toolchain.version, + ) + + label = "xcode_{}".format(toolchain.version.replace(".", "_")) + build_file += (CONFIG_SETTING_TMPL.format( + label = label, + version = toolchain.version, + )) + + framework_select_lines.append(SELECT_LINE_TMPL.format( + label = label, + xcode_dir = target_name, + target = "xcode_sdk_frameworks", + )) + xctest_select_lines.append(SELECT_LINE_TMPL.format( + label = label, + xcode_dir = target_name, + target = "XCTest", + )) + + build_file += (ROOT_ALIAS_TMPL.format(target = "xcode_sdk_frameworks", select_lines = ",\n ".join(framework_select_lines))) + build_file += (ROOT_ALIAS_TMPL.format(target = "XCTest", select_lines = ",\n ".join(xctest_select_lines))) + repository_ctx.file("BUILD.bazel", build_file) + +xcode_sdk_frameworks = repository_rule( + implementation = _impl, + environ = ["EXPLICIT_MODULES"], + attrs = { + "xcode_locator": attr.string(), + }, +) diff --git a/tools/xcode_version_computation/BUILD.bazel b/tools/xcode_version_computation/BUILD.bazel index 9f9c18d2..4f4bdbce 100644 --- a/tools/xcode_version_computation/BUILD.bazel +++ b/tools/xcode_version_computation/BUILD.bazel @@ -4,4 +4,5 @@ swift_binary( name = "xcode_version_computation", srcs = ["main.swift"], visibility = ["//visibility:public"], + deps = ["@xcode_sdk_frameworks"], )