Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ use_repo(apple_cc_configure, "local_config_apple_cc", "local_config_apple_cc_too

register_toolchains("@local_config_apple_cc_toolchains//:all")

xcode_configure = use_extension("//xcode:xcode_configure.bzl", "xcode_configure_extension")
use_repo(xcode_configure, "local_config_xcode")

bazel_dep(name = "rules_shell", version = "0.3.0", dev_dependency = True)
bazel_dep(name = "stardoc", version = "0.8.0", dev_dependency = True)

Expand Down
2 changes: 1 addition & 1 deletion crosstool/osx_cc_configure.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ load(
"@bazel_tools//tools/cpp:lib_cc_configure.bzl",
"escape_string",
)
load("@bazel_tools//tools/osx:xcode_configure.bzl", "run_xcode_locator")
load("//xcode:xcode_configure.bzl", "run_xcode_locator")

def _get_copts_env_var(repository_ctx, name, default = ""):
"""Get an environment variable and split it on ":" to be used as copts.
Expand Down
7 changes: 7 additions & 0 deletions xcode/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ bzl_library(
deps = ["//xcode/private:providers"],
)

bzl_library(
name = "xcode_configure",
srcs = ["xcode_configure.bzl"],
)

exports_files(["xcode_configure.bzl"])

bzl_library(
name = "xcode_version",
srcs = ["xcode_version.bzl"],
Expand Down
329 changes: 329 additions & 0 deletions xcode/xcode_configure.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
# Copyright 2016 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.
"""Repository rule to generate host xcode_config and xcode_version targets.

The xcode_config and xcode_version targets are configured for xcodes/SDKs
installed on the local host.
"""

OSX_EXECUTE_TIMEOUT = 600

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, timeout):
"""Returns a string containing an xcode_version build target."""
build_contents = ""
decorated_aliases = []
error_msg = ""
for alias in aliases:
decorated_aliases.append("'%s'" % alias)
repository_ctx.report_progress("Fetching SDK information for Xcode %s" % version)
xcodebuild_result = repository_ctx.execute(
["xcrun", "xcodebuild", "-version", "-sdk"],
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,
)
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")
visionos_sdk_version = _search_sdk_output(xcodebuild_result.stdout, "xros")
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 visionos_sdk_version:
build_contents += "\n default_visionos_sdk_version = '%s'," % visionos_sdk_version
if watchos_sdk_version:
build_contents += "\n default_watchos_sdk_version = '%s'," % watchos_sdk_version
build_contents += "\n)\n"
if error_msg:
build_contents += "\n# Error: " + error_msg.replace("\n", " ") + "\n"
print(error_msg) # buildifier: disable=print
return build_contents

VERSION_CONFIG_STUB = """
load("@build_bazel_apple_support//xcode:xcode_config.bzl", "xcode_config")
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 2-tuple containing:
output: 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.
err: An error string describing the error that occurred when attempting
to build and run xcode-locator, or None if the run was successful.
"""
repository_ctx.report_progress("Building xcode-locator")
xcodeloc_src_path = str(repository_ctx.path(xcode_locator_src_label))
env = repository_ctx.os.environ
if "BAZEL_OSX_EXECUTE_TIMEOUT" in env:
timeout = int(env["BAZEL_OSX_EXECUTE_TIMEOUT"])
else:
timeout = OSX_EXECUTE_TIMEOUT

xcrun_result = repository_ctx.execute([
"env",
"-i",
"DEVELOPER_DIR={}".format(env.get("DEVELOPER_DIR", default = "")),
"xcrun",
"--sdk",
"macosx",
"clang",
"-mmacosx-version-min=10.13",
"-fobjc-arc",
"-framework",
"CoreServices",
"-framework",
"Foundation",
"-o",
"xcode-locator-bin",
xcodeloc_src_path,
], 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,
)
return ([], error_msg.replace("\n", " "))

repository_ctx.report_progress("Running xcode-locator")
xcode_locator_result = repository_ctx.execute(
["./xcode-locator-bin", "-v"],
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,
)
return ([], error_msg.replace("\n", " "))
xcode_toolchains = []

# xcode_dump is comprised of newlines with different installed Xcode versions,
# each line of the form <version>:<comma_separated_aliases>:<developer_dir>.
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, None)

def _darwin_build_file(repository_ctx):
"""Evaluates local system state to create xcode_config and xcode_version targets."""
repository_ctx.report_progress("Fetching the default Xcode version")
env = repository_ctx.os.environ

if "BAZEL_OSX_EXECUTE_TIMEOUT" in env:
timeout = int(env["BAZEL_OSX_EXECUTE_TIMEOUT"])
else:
timeout = OSX_EXECUTE_TIMEOUT

xcodebuild_result = repository_ctx.execute([
"env",
"-i",
"DEVELOPER_DIR={}".format(env.get("DEVELOPER_DIR", default = "")),
"xcrun",
"xcodebuild",
"-version",
], timeout)

(toolchains, xcodeloc_err) = run_xcode_locator(
repository_ctx,
Label(repository_ctx.attr.xcode_locator),
)

if xcodeloc_err:
return VERSION_CONFIG_STUB + "\n# Error: " + xcodeloc_err + "\n"

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(
xcodebuild_result.stdout,
"Build version ",
"\n",
)
default_xcode_target = ""
target_names = []
buildcontents = """
load("@build_bazel_apple_support//xcode:xcode_config.bzl", "xcode_config")
load("@build_bazel_apple_support//xcode:available_xcodes.bzl", "available_xcodes")
load("@build_bazel_apple_support//xcode:xcode_version.bzl", "xcode_version")
"""

for toolchain in toolchains:
version = toolchain.version
aliases = toolchain.aliases
developer_dir = toolchain.developer_dir
target_name = "version%s" % version.replace(".", "_")
buildcontents += _xcode_version_output(
repository_ctx,
target_name,
version,
aliases,
developer_dir,
timeout,
)
target_label = "':%s'" % target_name
target_names.append(target_label)
if (version.startswith(default_xcode_version) and
version.endswith(default_xcode_build_version)):
default_xcode_target = target_label
buildcontents += "xcode_config(\n name = 'host_xcodes',"
if target_names:
buildcontents += "\n versions = [%s]," % ", ".join(target_names)
if not default_xcode_target and target_names:
default_xcode_target = sorted(target_names, reverse = True)[0]
print("No default Xcode version is set with 'xcode-select'; picking %s" %
default_xcode_target) # buildifier: disable=print
if default_xcode_target:
buildcontents += "\n default = %s," % default_xcode_target

buildcontents += "\n)\n"
buildcontents += "available_xcodes(\n name = 'host_available_xcodes',"
if target_names:
buildcontents += "\n versions = [%s]," % ", ".join(target_names)
if default_xcode_target:
buildcontents += "\n default = %s," % default_xcode_target
buildcontents += "\n)\n"
if repository_ctx.attr.remote_xcode:
buildcontents += "xcode_config(name = 'all_xcodes',"
buildcontents += "\n remote_versions = '%s', " % repository_ctx.attr.remote_xcode
buildcontents += "\n local_versions = ':host_available_xcodes', "
buildcontents += "\n)\n"
return buildcontents

def _impl(repository_ctx):
"""Implementation for the local_config_xcode repository rule.

Generates a BUILD file containing a root xcode_config target named 'host_xcodes',
which points to an xcode_version target for each version of Xcode installed on
the local host machine. If no versions of Xcode are present on the machine
(for instance, if this is a non-darwin OS), creates a stub target.

Args:
repository_ctx: The repository context.
"""

os_name = repository_ctx.os.name
build_contents = "package(default_visibility = ['//visibility:public'])\n\n"
if (os_name.startswith("mac os")):
build_contents += _darwin_build_file(repository_ctx)
else:
build_contents += VERSION_CONFIG_STUB
repository_ctx.file("BUILD", build_contents)

xcode_autoconf = repository_rule(
environ = [
"DEVELOPER_DIR",
"XCODE_VERSION",
],
implementation = _impl,
configure = True,
attrs = {
"xcode_locator": attr.string(),
"remote_xcode": attr.string(),
},
)

def xcode_configure(xcode_locator_label, remote_xcode_label = None):
"""Generates a repository containing host Xcode version information."""
xcode_autoconf(
name = "local_config_xcode",
xcode_locator = xcode_locator_label,
remote_xcode = remote_xcode_label,
)

def _xcode_configure_extension_impl(module_ctx):
xcode_configure("@bazel_tools//tools/osx:xcode_locator.m")
return module_ctx.extension_metadata(reproducible = True)

xcode_configure_extension = module_extension(implementation = _xcode_configure_extension_impl)