diff --git a/test/xcode_config_test.bzl b/test/xcode_config_test.bzl index 5f3da8c..0515a3d 100644 --- a/test/xcode_config_test.bzl +++ b/test/xcode_config_test.bzl @@ -19,6 +19,10 @@ load("//xcode:available_xcodes.bzl", "available_xcodes") load("//xcode:xcode_config.bzl", "xcode_config") load("//xcode:xcode_config_alias.bzl", "xcode_config_alias") load("//xcode:xcode_version.bzl", "xcode_version") +load( + "//xcode/private:providers.bzl", + "XcodeVersionPropertiesInfo", +) # buildifier: disable=bzl-visibility load(":test_helpers.bzl", "FIXTURE_TAGS", "find_action", "make_all_tests") visibility("private") @@ -26,7 +30,7 @@ visibility("private") # ------------------------------------------------------------------------------ def _version_retriever_impl(ctx): - xcode_properties = ctx.attr.dep[apple_common.XcodeProperties] + xcode_properties = ctx.attr.dep[XcodeVersionPropertiesInfo] version = xcode_properties.xcode_version return [config_common.FeatureFlagInfo(value = version)] @@ -264,6 +268,8 @@ def _accepts_flag_for_mutually_available(namer): _accepts_flag_for_mutually_available_test( name = "accepts_flag_for_mutually_available", target_under_test = "accepts_flag_for_mutually_available__foo", + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) return ["accepts_flag_for_mutually_available"] @@ -308,6 +314,8 @@ def _prefers_flag_over_mutually_available(namer): _prefers_flag_over_mutually_available_test( name = "prefers_flag_over_mutually_available", target_under_test = "prefers_flag_over_mutually_available__foo", + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) return ["prefers_flag_over_mutually_available"] @@ -352,6 +360,8 @@ def _warn_with_explicit_local_only_version(namer): _warn_with_explicit_local_only_version_test( name = "warn_with_explicit_local_only_version", target_under_test = "warn_with_explicit_local_only_version__foo", + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) return ["warn_with_explicit_local_only_version"] @@ -400,6 +410,8 @@ def _prefer_local_default_if_no_mutual_no_flag_different_main_version(namer): _prefer_local_default_if_no_mutual_no_flag_different_main_version_test( name = "prefer_local_default_if_no_mutual_no_flag_different_main_version", target_under_test = "prefer_local_default_if_no_mutual_no_flag_different_main_version__foo", + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) return ["prefer_local_default_if_no_mutual_no_flag_different_main_version"] @@ -447,6 +459,8 @@ def _prefer_local_default_if_no_mutual_no_flag_different_build_alias(namer): _prefer_local_default_if_no_mutual_no_flag_different_build_alias_test( name = "prefer_local_default_if_no_mutual_no_flag_different_build_alias", target_under_test = "prefer_local_default_if_no_mutual_no_flag_different_build_alias__foo", + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) return ["prefer_local_default_if_no_mutual_no_flag_different_build_alias"] @@ -494,6 +508,8 @@ def _prefer_local_default_if_no_mutual_no_flag_different_full_version(namer): _prefer_local_default_if_no_mutual_no_flag_different_full_version_test( name = "prefer_local_default_if_no_mutual_no_flag_different_full_version", target_under_test = "prefer_local_default_if_no_mutual_no_flag_different_full_version__foo", + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) return ["prefer_local_default_if_no_mutual_no_flag_different_full_version"] @@ -545,6 +561,8 @@ def _choose_newest_mutual_xcode(namer): _choose_newest_mutual_xcode_test( name = "choose_newest_mutual_xcode", target_under_test = "choose_newest_mutual_xcode__foo", + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) return ["choose_newest_mutual_xcode"] @@ -816,18 +834,26 @@ def _config_alias_config_setting(namer): _config_alias_config_setting_no_flag_test( name = "config_alias_config_setting_no_flag", target_under_test = namer("gen"), + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) _config_alias_config_setting_6_4_test( name = "config_alias_config_setting_6_4", target_under_test = namer("gen"), + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) _config_alias_config_setting_6_test( name = "config_alias_config_setting_6", target_under_test = namer("gen"), + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) _config_alias_config_setting_12_test( name = "config_alias_config_setting_12", target_under_test = namer("gen"), + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) return [ "config_alias_config_setting_no_flag", @@ -960,10 +986,14 @@ def _default_version_config_setting(namer): _default_version_config_setting_no_flag_test( name = "default_version_config_setting_no_flag", target_under_test = namer("gen"), + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) _default_version_config_setting_6_4_test( name = "default_version_config_setting_6_4", target_under_test = namer("gen"), + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) return [ "default_version_config_setting_no_flag", @@ -1013,6 +1043,8 @@ def _valid_version(namer): _valid_version_test( name = "valid_version", target_under_test = "valid_version__foo", + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) return ["valid_version"] @@ -1053,6 +1085,8 @@ def _valid_alias_dotted_version(namer): _valid_alias_dotted_version_test( name = "valid_alias_dotted_version", target_under_test = "valid_alias_dotted_version__foo", + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) return ["valid_alias_dotted_version"] @@ -1093,6 +1127,8 @@ def _valid_alias_nonnumerical(namer): _valid_alias_nonnumerical_test( name = "valid_alias_nonnumerical", target_under_test = "valid_alias_nonnumerical__foo", + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) return ["valid_alias_nonnumerical"] @@ -1176,6 +1212,8 @@ def _requires_default(namer): _requires_default_test( name = "requires_default", target_under_test = "requires_default__foo", + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) return ["requires_default"] @@ -1210,6 +1248,8 @@ def _duplicate_aliases_defined_version(namer): _duplicate_aliases_defined_version_test( name = "duplicate_aliases_defined_version", target_under_test = "duplicate_aliases_defined_version__foo", + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) return ["duplicate_aliases_defined_version"] @@ -1248,6 +1288,8 @@ def _duplicate_aliases_within_available_xcodes(namer): _duplicate_aliases_within_available_xcodes_test( name = "duplicate_aliases_within_available_xcodes", target_under_test = "duplicate_aliases_within_available_xcodes__foo", + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) return ["duplicate_aliases_within_available_xcodes"] @@ -1282,6 +1324,8 @@ def _version_aliased_to_itself(namer): _version_aliased_to_itself_test( name = "version_aliased_to_itself", target_under_test = "version_aliased_to_itself__foo", + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) return ["version_aliased_to_itself"] @@ -1322,6 +1366,8 @@ def _duplicate_version_numbers(namer): _duplicate_version_numbers_test( name = "duplicate_version_numbers", target_under_test = "duplicate_version_numbers__foo", + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) return ["duplicate_version_numbers"] @@ -1358,6 +1404,8 @@ def _version_conflicts_with_alias(namer): _version_conflicts_with_alias_test( name = "version_conflicts_with_alias", target_under_test = "version_conflicts_with_alias__foo", + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) return ["version_conflicts_with_alias"] @@ -1415,6 +1463,8 @@ def _default_ios_sdk_version(namer): _default_ios_sdk_version_test( name = "default_ios_sdk_version", target_under_test = "default_ios_sdk_version__foo", + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) return ["default_ios_sdk_version"] @@ -1482,6 +1532,8 @@ def _default_sdk_versions(namer): _default_sdk_versions_test( name = "default_sdk_versions", target_under_test = "default_sdk_versions__foo", + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) return ["default_sdk_versions"] @@ -1553,6 +1605,8 @@ def _default_sdk_versions_selected_xcode(namer): _default_sdk_versions_selected_xcode_test( name = "default_sdk_versions_selected_xcode", target_under_test = "default_sdk_versions_selected_xcode__foo", + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) return ["default_sdk_versions_selected_xcode"] @@ -1625,6 +1679,8 @@ def _override_default_sdk_versions(namer): _override_default_sdk_versions_test( name = "override_default_sdk_versions", target_under_test = "override_default_sdk_versions__foo", + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) return ["override_default_sdk_versions"] @@ -1682,6 +1738,8 @@ def _default_without_version(namer): _default_without_version_test( name = "default_without_version", target_under_test = "default_without_version__foo", + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) return ["default_without_version"] @@ -1731,6 +1789,8 @@ def _version_does_not_contain_default(namer): _version_does_not_contain_default_test( name = "version_does_not_contain_default", target_under_test = "version_does_not_contain_default__foo", + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) return ["version_does_not_contain_default"] @@ -1786,10 +1846,14 @@ def _configuration_field_for_rule(namer): _configuration_field_for_rule_1_test( name = "configuration_field_for_rule_1", target_under_test = namer("provider_grabber"), + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) _configuration_field_for_rule_2_test( name = "configuration_field_for_rule_2", target_under_test = namer("provider_grabber"), + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) return [ "configuration_field_for_rule_1", @@ -1876,10 +1940,14 @@ def _configuration_field_for_aspect(namer): _configuration_field_for_aspect_1_test( name = "configuration_field_for_aspect_1", target_under_test = namer("provider_grabber"), + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) _configuration_field_for_aspect_2_test( name = "configuration_field_for_aspect_2", target_under_test = namer("provider_grabber"), + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) return [ "configuration_field_for_aspect_1", @@ -1939,6 +2007,8 @@ def _explicit_xcodes_mode_no_flag(namer): _explicit_xcodes_mode_no_flag_test( name = "explicit_xcodes_mode_no_flag", target_under_test = "explicit_xcodes_mode_no_flag__foo", + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) return ["explicit_xcodes_mode_no_flag"] @@ -1976,6 +2046,8 @@ def _explicit_xcodes_mode_with_flag(namer): _explicit_xcodes_mode_with_flag_test( name = "explicit_xcodes_mode_with_flag", target_under_test = "explicit_xcodes_mode_with_flag__foo", + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) return ["explicit_xcodes_mode_with_flag"] @@ -2017,6 +2089,8 @@ def _available_xcodes_mode_no_flag(namer): _available_xcodes_mode_no_flag_test( name = "available_xcodes_mode_no_flag", target_under_test = "available_xcodes_mode_no_flag__foo", + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) return ["available_xcodes_mode_no_flag"] @@ -2056,6 +2130,8 @@ def _available_xcodes_mode_different_alias(namer): _available_xcodes_mode_different_alias_test( name = "available_xcodes_mode_different_alias", target_under_test = "available_xcodes_mode_different_alias__foo", + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) return ["available_xcodes_mode_different_alias"] @@ -2093,6 +2169,8 @@ def _available_xcodes_mode_different_alias_fully_specified(namer): _available_xcodes_mode_different_alias_fully_specified_test( name = "available_xcodes_mode_different_alias_fully_specified", target_under_test = "available_xcodes_mode_different_alias_fully_specified__foo", + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) return ["available_xcodes_mode_different_alias_fully_specified"] @@ -2135,6 +2213,8 @@ def _available_xcodes_mode_with_flag(namer): _available_xcodes_mode_with_flag_test( name = "available_xcodes_mode_with_flag", target_under_test = "available_xcodes_mode_with_flag__foo", + # TODO: Remove once we test with Bazel 8+ + tags = ["manual"], ) return ["available_xcodes_mode_with_flag"] diff --git a/test/xcode_version_test.bzl b/test/xcode_version_test.bzl index 1518a05..b97289d 100644 --- a/test/xcode_version_test.bzl +++ b/test/xcode_version_test.bzl @@ -16,6 +16,10 @@ load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts") load("//xcode:xcode_version.bzl", "xcode_version") +load( + "//xcode/private:providers.bzl", + "XcodeVersionPropertiesInfo", +) # buildifier: disable=bzl-visibility load(":test_helpers.bzl", "FIXTURE_TAGS", "make_all_tests") visibility("private") @@ -44,7 +48,7 @@ def _read_version_from_provider_test_impl(ctx): env = analysistest.begin(ctx) target_under_test = analysistest.target_under_test(env) - xcode_properties = target_under_test[apple_common.XcodeProperties] + xcode_properties = target_under_test[XcodeVersionPropertiesInfo] asserts.equals(env, "8", xcode_properties.xcode_version) asserts.equals(env, "9.0", xcode_properties.default_ios_sdk_version) diff --git a/xcode/BUILD b/xcode/BUILD index 6d856d1..788fea1 100644 --- a/xcode/BUILD +++ b/xcode/BUILD @@ -9,6 +9,7 @@ package( bzl_library( name = "available_xcodes", srcs = ["available_xcodes.bzl"], + deps = ["//xcode/private:providers"], ) bzl_library( @@ -19,22 +20,27 @@ bzl_library( bzl_library( name = "xcode_config", srcs = ["xcode_config.bzl"], + deps = ["//xcode/private:providers"], ) bzl_library( name = "xcode_config_alias", srcs = ["xcode_config_alias.bzl"], + deps = ["//xcode/private:providers"], ) bzl_library( name = "xcode_version", srcs = ["xcode_version.bzl"], + deps = ["//xcode/private:providers"], ) # Consumed by bazel tests. filegroup( name = "for_bazel_tests", testonly = True, - srcs = glob(["**"]), + srcs = glob(["**"]) + [ + "//xcode/private:for_bazel_tests", + ], visibility = ["//:__pkg__"], ) diff --git a/xcode/available_xcodes.bzl b/xcode/available_xcodes.bzl index ec0adfd..18e8314 100644 --- a/xcode/available_xcodes.bzl +++ b/xcode/available_xcodes.bzl @@ -14,11 +14,45 @@ """Implementation of the `available_xcodes` build rule.""" +load( + "@build_bazel_apple_support//xcode/private:providers.bzl", + "AvailableXcodesInfo", + "XcodeVersionRuleInfo", +) + visibility("public") -def available_xcodes(name, **kwargs): - # TODO: b/311385128 - Migrate the native implementation here. - native.available_xcodes( - name = name, - **kwargs - ) +def _available_xcodes_impl(ctx): + available_versions = [ + target[XcodeVersionRuleInfo] + for target in ctx.attr.versions + ] + default_version = ctx.attr.default[XcodeVersionRuleInfo] + + return [ + AvailableXcodesInfo( + available_versions = available_versions, + default_version = default_version, + ), + ] + +available_xcodes = rule( + attrs = { + "default": attr.label( + doc = "The default Xcode version for this platform.", + mandatory = True, + providers = [[XcodeVersionRuleInfo]], + ), + "versions": attr.label_list( + doc = "The Xcode versions that are available on this platform.", + providers = [[XcodeVersionRuleInfo]], + ), + }, + doc = """\ +Two targets of this rule can be depended on by an `xcode_config` rule instance +to indicate the remotely and locally available Xcode versions. This allows +selection of an official Xcode version from the collectively available Xcodes. + """, + implementation = _available_xcodes_impl, + provides = [AvailableXcodesInfo], +) diff --git a/xcode/private/BUILD b/xcode/private/BUILD new file mode 100644 index 0000000..11160cc --- /dev/null +++ b/xcode/private/BUILD @@ -0,0 +1,20 @@ +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") + +licenses(["notice"]) + +package( + default_visibility = ["//xcode:__subpackages__"], +) + +bzl_library( + name = "providers", + srcs = ["providers.bzl"], +) + +# Consumed by bazel tests. +filegroup( + name = "for_bazel_tests", + testonly = True, + srcs = glob(["**"]), + visibility = ["//xcode:__pkg__"], +) diff --git a/xcode/private/providers.bzl b/xcode/private/providers.bzl new file mode 100644 index 0000000..6e2339b --- /dev/null +++ b/xcode/private/providers.bzl @@ -0,0 +1,110 @@ +# Copyright 2024 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Providers used internally by the Xcode rules not meant for client use.""" + +visibility([ + "//test/...", + "//xcode/...", +]) + +AvailableXcodesInfo = provider( + doc = """\ +The available Xcode versions computed from the `available_xcodes` rule. +""", + fields = { + "available_versions": """\ +The available Xcode versions from `available_xcodes`. +""", + "default_version": """\ +The default Xcode version from `available_xcodes`. +""", + }, +) + +def _xcode_version_properties_info_init( + *, + xcode_version, + default_ios_sdk_version = "8.4", + default_macos_sdk_version = "10.11", + default_tvos_sdk_version = "9.0", + default_watchos_sdk_version = "2.0", + default_visionos_sdk_version = "1.0"): + # Ensure that all fields get default values if they weren't specified. + return { + "xcode_version": xcode_version, + "default_ios_sdk_version": default_ios_sdk_version, + "default_macos_sdk_version": default_macos_sdk_version, + "default_tvos_sdk_version": default_tvos_sdk_version, + "default_watchos_sdk_version": default_watchos_sdk_version, + "default_visionos_sdk_version": default_visionos_sdk_version, + } + +XcodeVersionPropertiesInfo, _new_xcode_version_properties_info = provider( + doc = """\ +Information about a specific Xcode version, such as its default SDK versions. +""", + fields = { + "xcode_version": """\ +A string representing the Xcode version number, or `None` if it is unknown. +""", + "default_ios_sdk_version": """\ +A string representing the default iOS SDK version number for this version of +Xcode, or `None` if it is unknown. +""", + "default_macos_sdk_version": """\ +A string representing the default macOS SDK version number for this version of +Xcode, or `None` if it is unknown. +""", + "default_tvos_sdk_version": """\ +A string representing the default tvOS SDK version number for this version of +Xcode, or `None` if it is unknown. +""", + "default_watchos_sdk_version": """\ +A string representing the default watchOS SDK version number for this version of +Xcode, or `None` if it is unknown. +""", + "default_visionos_sdk_version": """\ +A string representing the default visionOS SDK version number for this version +of Xcode, or `None` if it is unknown. +""", + }, + init = _xcode_version_properties_info_init, +) + +XcodeVersionRuleInfo = provider( + doc = """\ +The information in a single target of the `xcode_version` rule. A single target +of this rule contains an official version label decided by Apple, a number of +supported aliases one might use to reference this version, and various +properties of the Xcode version (such as default SDK versions). + +For example, one may want to reference official Xcode version 7.0.1 using the +"7" or "7.0" aliases. This official version of Xcode may have a default +supported iOS SDK of 9.0. +""", + fields = { + "aliases": """\ +A list of strings denoting aliases that can be used to reference this Xcode +version. +""", + "label": """\ +The build `Label` of the `xcode_version` target that propagated this provider. +""", + "xcode_version_properties": """\ +An `XcodeVersionPropertiesInfo` provider that contains the details about this +Xcode version, such as its default SDK versions. +""", + }, +) diff --git a/xcode/xcode_config.bzl b/xcode/xcode_config.bzl index 5d4a855..18f155e 100644 --- a/xcode/xcode_config.bzl +++ b/xcode/xcode_config.bzl @@ -14,11 +14,374 @@ """Implementation of the `xcode_config` build rule.""" +load( + "@build_bazel_apple_support//xcode/private:providers.bzl", + "AvailableXcodesInfo", + "XcodeVersionPropertiesInfo", + "XcodeVersionRuleInfo", +) + visibility("public") -def xcode_config(name, **kwargs): - # TODO: b/311385128 - Migrate the native implementation here. - native.xcode_config( - name = name, - **kwargs +unavailable_xcode_message = "'bazel fetch --configure' (Bzlmod) or 'bazel sync --configure' (WORKSPACE)" + +def _xcode_config_impl(ctx): + apple_fragment = ctx.fragments.apple + cpp_fragment = ctx.fragments.cpp + + explicit_default_version = ctx.attr.default[XcodeVersionRuleInfo] if ctx.attr.default else None + explicit_versions = [ + target[XcodeVersionRuleInfo] + for target in ctx.attr.versions + ] if ctx.attr.versions else [] + remote_versions = [ + target + for target in ctx.attr.remote_versions[AvailableXcodesInfo].available_versions + ] if ctx.attr.remote_versions else [] + local_versions = [ + target + for target in ctx.attr.local_versions[AvailableXcodesInfo].available_versions + ] if ctx.attr.local_versions else [] + + local_default_version = ctx.attr.local_versions[AvailableXcodesInfo].default_version if ctx.attr.local_versions else None + xcode_version_properties = None + availability = "unknown" + + if _use_available_xcodes( + explicit_default_version, + explicit_versions, + local_versions, + remote_versions, + ): + xcode_version_properties, availability = _resolve_xcode_from_local_and_remote( + local_versions, + remote_versions, + apple_fragment.xcode_version_flag, + apple_fragment.prefer_mutual_xcode, + local_default_version, + ) + else: + xcode_version_properties = _resolve_explicitly_defined_version( + explicit_versions, + explicit_default_version, + apple_fragment.xcode_version_flag, + ) + availability = "UNKNOWN" + + ios_sdk_version = apple_fragment.ios_sdk_version_flag or _dotted_version_or_default(xcode_version_properties.default_ios_sdk_version, "8.4") + macos_sdk_version = apple_fragment.macos_sdk_version_flag or _dotted_version_or_default(xcode_version_properties.default_macos_sdk_version, "10.11") + tvos_sdk_version = apple_fragment.tvos_sdk_version_flag or _dotted_version_or_default(xcode_version_properties.default_tvos_sdk_version, "9.0") + watchos_sdk_version = apple_fragment.watchos_sdk_version_flag or _dotted_version_or_default(xcode_version_properties.default_watchos_sdk_version, "2.0") + visionos_sdk_version = _dotted_version_or_default(xcode_version_properties.default_visionos_sdk_version, "1.0") + + ios_minimum_os = apple_fragment.ios_minimum_os_flag or ios_sdk_version + macos_minimum_os = apple_fragment.macos_minimum_os_flag or macos_sdk_version + tvos_minimum_os = apple_fragment.tvos_minimum_os_flag or tvos_sdk_version + watchos_minimum_os = apple_fragment.watchos_minimum_os_flag or watchos_sdk_version + if cpp_fragment.minimum_os_version(): + visionos_minimum_os = apple_common.dotted_version(cpp_fragment.minimum_os_version()) + else: + visionos_minimum_os = visionos_sdk_version + + # TODO: b/335817541 - At this time, there is still one place in the Bazel + # Starlark built-in code that relies on this specific provider -- the code + # in `objc/compilation_support.bzl` that registers the + # `ObjcBinarySymbolStrip` action. Until that code is removed or updated, we + # must make sure to always return this provider. However, we should also + # return a newer, modernized provider, and have non-builtin Starlark clients + # migrate to that provider ASAP. + xcode_versions = apple_common.XcodeVersionConfig( + ios_sdk_version = str(ios_sdk_version), + ios_minimum_os_version = str(ios_minimum_os), + visionos_sdk_version = str(visionos_sdk_version), + visionos_minimum_os_version = str(visionos_minimum_os), + watchos_sdk_version = str(watchos_sdk_version), + watchos_minimum_os_version = str(watchos_minimum_os), + tvos_sdk_version = str(tvos_sdk_version), + tvos_minimum_os_version = str(tvos_minimum_os), + macos_sdk_version = str(macos_sdk_version), + macos_minimum_os_version = str(macos_minimum_os), + xcode_version = xcode_version_properties.xcode_version, + availability = availability, + xcode_version_flag = apple_fragment.xcode_version_flag, + include_xcode_execution_info = apple_fragment.include_xcode_exec_requirements, ) + return [ + DefaultInfo(runfiles = ctx.runfiles()), + xcode_versions, + xcode_version_properties, + ] + +xcode_config = rule( + attrs = { + "default": attr.label( + doc = """\ +The default official version of Xcode to use. + +The version specified by the provided `xcode_version` target is to be used if +no `xcode_version` build flag is specified. This is required if any `versions` +are set. This may not be set if `remote_versions` or `local_versions` is set. +""", + providers = [[XcodeVersionRuleInfo]], + ), + "versions": attr.label_list( + doc = """\ +Accepted `xcode_version` targets that may be used. + +If the value of the `xcode_version` build flag matches one of the aliases or +version number of any of the given `xcode_version` targets, the matching target +will be used. This may not be set if `remote_versions` or `local_versions` is +set. +""", + providers = [[XcodeVersionRuleInfo]], + ), + "remote_versions": attr.label( + doc = """\ +The `xcode_version` targets that are available remotely. + +These are used along with `local_versions` to select a mutually available +version. This may not be set if `versions` is set. +""", + providers = [[AvailableXcodesInfo]], + ), + "local_versions": attr.label( + doc = """\ +The `xcode_version` targets that are available locally. + +These are used along with `remote_versions` to select a mutually available +version. This may not be set if `versions` is set. +""", + providers = [[AvailableXcodesInfo]], + ), + }, + doc = """\ +A single target of this rule can be referenced by the `--xcode_version_config` +build flag to translate the `--xcode_version` flag into an accepted official +Xcode version. This allows selection of an official Xcode version from a number +of registered aliases. +""", + fragments = ["apple", "cpp"], + implementation = _xcode_config_impl, +) + +def _use_available_xcodes( + explicit_default_version, + explicit_versions, + local_versions, + remote_versions): + if remote_versions: + if explicit_versions: + fail("'versions' may not be set if '[local,remote]_versions' is set.") + if explicit_default_version: + fail("'default' may not be set if '[local,remote]_versions' is set.") + if not local_versions: + fail("if 'remote_versions' are set, you must also set 'local_versions'") + return True + return False + +def _duplicate_alias_error(alias, versions): + labels_containing_alias = [] + for version in versions: + if alias in version.aliases or (version.xcode_version_properties.xcode_version == alias): + labels_containing_alias.append(str(version.label)) + return "'{}' is registered to multiple labels ({}) in a single xcode_config rule".format( + alias, + ", ".join(labels_containing_alias), + ) + +def _aliases_to_xcode_version(versions): + version_map = {} + if not versions: + return version_map + for version in versions: + for alias in version.aliases: + if alias in version_map: + fail(_duplicate_alias_error(alias, versions)) + else: + version_map[alias] = version + version_string = version.xcode_version_properties.xcode_version + if version_string not in version.aliases: # only add the version if it's not also an alias + if version_string in version_map: + fail(_duplicate_alias_error(version_string, versions)) + else: + version_map[version_string] = version + return version_map + +def _resolve_xcode_from_local_and_remote( + local_versions, + remote_versions, + xcode_version_flag, + prefer_mutual_xcode, + local_default_version): + local_alias_to_version_map = _aliases_to_xcode_version(local_versions) + remote_alias_to_version_map = _aliases_to_xcode_version(remote_versions) + + # A version is mutually available (available both locally and remotely) if the local version + # attribute matches either the version attribute or one of the aliases of the remote version. + # mutually_vailable_versions is a subset of remote_versions. + # We assume the "version" attribute in local xcode_version contains a full version string, + # e.g. including the build, while the versions in "alias" attribute may be less granular. + # We don't make this assumption for remote xcode_versions. + mutually_available_versions = {} + for version in local_versions: + version_string = version.xcode_version_properties.xcode_version + if version_string in remote_alias_to_version_map: + mutually_available_versions[version_string] = remote_alias_to_version_map[version_string] + + # We'd log an event here if we could!! + if xcode_version_flag: + remote_version_from_flag = remote_alias_to_version_map.get(xcode_version_flag) + local_version_from_flag = local_alias_to_version_map.get(xcode_version_flag) + availability = "BOTH" + + if remote_version_from_flag and local_version_from_flag: + local_version_from_remote_versions = remote_alias_to_version_map.get(local_version_from_flag.xcode_version_properties.xcode_version) + if local_version_from_remote_versions: + return remote_version_from_flag.xcode_version_properties, availability + else: + fail( + ("Xcode version {0} was selected, either because --xcode_version was passed, or" + + " because xcode-select points to this version locally. This corresponds to" + + " local Xcode version {1}. That build of version {0} is not available" + + " remotely, but there is a different build of version {2}, which has" + + " version {2} and aliases {3}. You probably meant to use this version." + + " Please download it *and delete version {1}, then run `bazel shutdown`" + + " to continue using dynamic execution. If you really did intend to use" + + " local version {1}, please specify it fully with --xcode_version={1}.").format( + xcode_version_flag, + local_version_from_flag.xcode_version_properties.xcode_version, + remote_version_from_flag.xcode_version_properties.xcode_version, + remote_version_from_flag.aliases, + ), + ) + + elif local_version_from_flag: + error = ( + " --xcode_version={} specified, but it is not available remotely. Actions " + + "requiring Xcode will be run locally, which could make your build slower." + ).format( + xcode_version_flag, + ) + if (mutually_available_versions): + error += " Consider using one of [{}].".format( + ", ".join([version for version in mutually_available_versions]), + ) + + # buildifier: disable=print + print(error) + return local_version_from_flag.xcode_version_properties, "LOCAL" + + elif remote_version_from_flag: + # buildifier: disable=print + print(("--xcode_version={version} specified, but it is not available locally. " + + "Your build will fail if any actions require a local Xcode. " + + "If you believe you have '{version}' installed, try running {command}," + + "and then re-run your command. Locally available versions: {local_versions}. ") + .format( + version = xcode_version_flag, + command = unavailable_xcode_message, + local_versions = ", ".join([version for version in local_alias_to_version_map.keys()]), + )) + availability = "REMOTE" + + return remote_version_from_flag.xcode_version_properties, availability + + else: # fail if we can't find any version to match + fail( + ("--xcode_version={0} specified, but '{0}' is not an available Xcode version." + + " Locally available versions: [{2}]. Remotely available versions: [{3}]. If" + + " you believe you have '{0}' installed, try running {1}, and then" + + " re-run your command.").format( + xcode_version_flag, + unavailable_xcode_message, + ", ".join([version.xcode_version_properties.xcode_version for version in local_versions]), + ", ".join([version.xcode_version_properties.xcode_version for version in remote_versions]), + ), + ) + + # --xcode_version is not set + availability = "UNKNOWN" + local_version = None + + # If there aren't any mutually available versions, select the local default. + if not mutually_available_versions: + # buildifier: disable=print + print( + ("Using a local Xcode version, '{}', since there are no" + + " remotely available Xcodes on this machine. Consider downloading one of the" + + " remotely available Xcode versions ({}) in order to get the best build" + + " performance.").format(local_default_version.xcode_version_properties.xcode_version, ", ".join([version.xcode_version_properties.xcode_version for version in remote_versions])), + ) + local_version = local_default_version + availability = "LOCAL" + elif (local_default_version.xcode_version_properties.xcode_version in remote_alias_to_version_map): + # If the local default version is also available remotely, use it. + availability = "BOTH" + local_version = remote_alias_to_version_map.get(local_default_version.xcode_version_properties.xcode_version) + else: + # If an alias of the local default version is available remotely, use it. + for version_number in local_default_version.aliases: + if version_number in remote_alias_to_version_map: + availability = "BOTH" + local_version = remote_alias_to_version_map.get(version_number) + break + + if local_version: + return local_version.xcode_version_properties, availability + + # The local default is not available remotely. + if prefer_mutual_xcode: + # If we prefer a mutually available version, the newest one. + newest_version = "0.0" + default_version = None + for _, version in mutually_available_versions.items(): + if _compare_version_strings(version.xcode_version_properties.xcode_version, newest_version) > 0: + default_version = version + newest_version = default_version.xcode_version_properties.xcode_version + + return default_version.xcode_version_properties, "BOTH" + else: + # Use the local default + return local_default_version.xcode_version_properties, "LOCAL" + +def _compare_version_strings(first, second): + return apple_common.dotted_version(first).compare_to( + apple_common.dotted_version(second), + ) + +def _resolve_explicitly_defined_version( + explicit_versions, + explicit_default_version, + xcode_version_flag): + if explicit_default_version and explicit_default_version.label not in [ + version.label + for version in explicit_versions + ]: + fail( + "default label '{}' must be contained in versions attribute".format( + explicit_default_version.label, + ), + ) + if not explicit_versions: + if explicit_default_version: + fail("default label must be contained in versions attribute") + return XcodeVersionPropertiesInfo(xcode_version = None) + + if not explicit_default_version: + fail("if any versions are specified, a default version must be specified") + + alias_to_versions = _aliases_to_xcode_version(explicit_versions) + if xcode_version_flag: + flag_version = alias_to_versions.get(str(xcode_version_flag)) + if flag_version: + return flag_version.xcode_version_properties + else: + fail( + ("--xcode_version={0} specified, but '{0}' is not an available Xcode version. " + + "If you believe you have '{0}' installed, try running \"bazel shutdown\", and then " + + "re-run your command.").format(xcode_version_flag), + ) + return alias_to_versions.get(explicit_default_version.xcode_version_properties.xcode_version).xcode_version_properties + +def _dotted_version_or_default(field, default): + return apple_common.dotted_version(field) or default diff --git a/xcode/xcode_config_alias.bzl b/xcode/xcode_config_alias.bzl index b0ed996..541676e 100644 --- a/xcode/xcode_config_alias.bzl +++ b/xcode/xcode_config_alias.bzl @@ -12,13 +12,47 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Implementation of the `xcode_config_alias` build rule.""" +"""Implementation of the `xcode_config_alias` build rule. + +This rule is an alias to the `xcode_config` rule currently in use, which in turn +depends on the current configuration; in particular, the value of the +`--xcode_version_config`. + +This is intentionally undocumented for users; the workspace is expected to +contain exactly one instance of this rule under `@bazel_tools//tools/osx` and +people who want to get data this rule provides should depend on that one. +""" + +load( + "@build_bazel_apple_support//xcode/private:providers.bzl", + "XcodeVersionPropertiesInfo", +) visibility("public") -def xcode_config_alias(name, **kwargs): - # TODO: b/311385128 - Migrate the native implementation here. - native.xcode_config_alias( - name = name, - **kwargs - ) +def _xcode_config_alias_impl(ctx): + xcode_config = ctx.attr._xcode_config + return [ + xcode_config[XcodeVersionPropertiesInfo], + # TODO: b/335817541 - At this time, there is still one place in the + # Bazel Starlark built-in code that relies on this specific provider -- + # the code in `objc/compilation_support.bzl` that registers the + # `ObjcBinarySymbolStrip` action. Until that code is removed or updated, + # we must make sure to always return this provider. However, we should + # also return a newer, modernized provider, and have non-builtin + # Starlark clients migrate to that provider ASAP. + xcode_config[apple_common.XcodeVersionConfig], + ] + +xcode_config_alias = rule( + attrs = { + "_xcode_config": attr.label( + default = configuration_field( + fragment = "apple", + name = "xcode_config_label", + ), + ), + }, + fragments = ["apple"], + implementation = _xcode_config_alias_impl, +) diff --git a/xcode/xcode_version.bzl b/xcode/xcode_version.bzl index a91b567..bc52f99 100644 --- a/xcode/xcode_version.bzl +++ b/xcode/xcode_version.bzl @@ -14,11 +14,100 @@ """Implementation of the `xcode_version` build rule.""" +load( + "@build_bazel_apple_support//xcode/private:providers.bzl", + "XcodeVersionPropertiesInfo", + "XcodeVersionRuleInfo", +) + visibility("public") -def xcode_version(name, **kwargs): - # TODO: b/311385128 - Migrate the native implementation here. - native.xcode_version( - name = name, - **kwargs +def _xcode_version_impl(ctx): + xcode_version_properties = XcodeVersionPropertiesInfo( + xcode_version = ctx.attr.version, + default_ios_sdk_version = ctx.attr.default_ios_sdk_version, + default_visionos_sdk_version = ctx.attr.default_visionos_sdk_version, + default_watchos_sdk_version = ctx.attr.default_watchos_sdk_version, + default_tvos_sdk_version = ctx.attr.default_tvos_sdk_version, + default_macos_sdk_version = ctx.attr.default_macos_sdk_version, ) + return [ + xcode_version_properties, + XcodeVersionRuleInfo( + label = ctx.label, + xcode_version_properties = xcode_version_properties, + aliases = ctx.attr.aliases, + ), + DefaultInfo(runfiles = ctx.runfiles()), + ] + +xcode_version = rule( + attrs = { + "aliases": attr.string_list( + doc = """\ +Accepted aliases for this version of Xcode. If the value of the +`--xcode_version` build flag matches any of the given alias strings, this Xcode +version will be used. +""", + allow_empty = True, + mandatory = False, + ), + "default_ios_sdk_version": attr.string( + default = "8.4", + doc = """\ +The iOS SDK version that is used by default when this version of Xcode is being +used. The `--ios_sdk_version` build flag will override the value specified here. + +NOTE: The `--ios_sdk_version` flag is deprecated and not recommended for use. +""", + mandatory = False, + ), + "default_macos_sdk_version": attr.string( + default = "10.11", + doc = """\ +The macOS SDK version that is used by default when this version of Xcode is +being used. The `--macos_sdk_version` build flag will override the value +specified here. + +NOTE: The `--macos_sdk_version` flag is deprecated and not recommended for use. +""", + mandatory = False, + ), + "default_tvos_sdk_version": attr.string( + default = "9.0", + doc = """\ +The tvOS SDK version that is used by default when this version of Xcode is being +used. The `--tvos_sdk_version` build flag will override the value specified +here. + +NOTE: The `--tvos_sdk_version` flag is deprecated and not recommended for use. +""", + mandatory = False, + ), + "default_visionos_sdk_version": attr.string( + default = "1.0", + doc = """\ +The visionOS SDK version that is used by default when this version of Xcode is +being used. +""", + mandatory = False, + ), + "default_watchos_sdk_version": attr.string( + default = "2.0", + doc = """\ +The watchOS SDK version that is used by default when this version of Xcode is +being used. The `--watchos_sdk_version` build flag will override the value +specified here. + +NOTE: The `--watchos_sdk_version` flag is deprecated and not recommended for +use. +""", + mandatory = False, + ), + "version": attr.string( + doc = "The official version number for this version of Xcode.", + mandatory = True, + ), + }, + implementation = _xcode_version_impl, +)