blob: 2b819f07ec9f9aa4d42c97af52ee57a54f2a5b02 [file] [log] [blame]
Chris Parsons381850e2016-08-31 17:04:17 +00001# Copyright 2016 The Bazel Authors. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""Repository rule to generate host xcode_config and xcode_version targets.
15
16 The xcode_config and xcode_version targets are configured for xcodes/SDKs
17 installed on the local host.
18"""
19
schmitt818bc012019-09-30 13:08:19 -070020_EXECUTE_TIMEOUT = 120
21
Chris Parsons381850e2016-08-31 17:04:17 +000022def _search_string(fullstring, prefix, suffix):
vladmos20a042f2018-06-01 04:51:21 -070023 """Returns the substring between two given substrings of a larger string.
Chris Parsons381850e2016-08-31 17:04:17 +000024
vladmos20a042f2018-06-01 04:51:21 -070025 Args:
26 fullstring: The larger string to search.
27 prefix: The substring that should occur directly before the returned string.
jingwen4a74c522018-11-20 11:57:15 -080028 suffix: The substring that should occur directly after the returned string.
vladmos20a042f2018-06-01 04:51:21 -070029 Returns:
30 A string occurring in fullstring exactly prefixed by prefix, and exactly
31 terminated by suffix. For example, ("hello goodbye", "lo ", " bye") will
32 return "good". If there is no such string, returns the empty string.
33 """
Chris Parsons381850e2016-08-31 17:04:17 +000034
vladmos20a042f2018-06-01 04:51:21 -070035 prefix_index = fullstring.find(prefix)
36 if (prefix_index < 0):
37 return ""
38 result_start_index = prefix_index + len(prefix)
39 suffix_index = fullstring.find(suffix, result_start_index)
40 if (suffix_index < 0):
41 return ""
42 return fullstring[result_start_index:suffix_index]
Chris Parsons381850e2016-08-31 17:04:17 +000043
44def _search_sdk_output(output, sdkname):
vladmos20a042f2018-06-01 04:51:21 -070045 """Returns the sdk version given xcodebuild stdout and an sdkname."""
46 return _search_string(output, "(%s" % sdkname, ")")
Chris Parsons381850e2016-08-31 17:04:17 +000047
48def _xcode_version_output(repository_ctx, name, version, aliases, developer_dir):
vladmos20a042f2018-06-01 04:51:21 -070049 """Returns a string containing an xcode_version build target."""
50 build_contents = ""
51 decorated_aliases = []
Keith Smileyc6c9b582019-02-28 11:27:07 -080052 error_msg = ""
vladmos20a042f2018-06-01 04:51:21 -070053 for alias in aliases:
54 decorated_aliases.append("'%s'" % alias)
davg762f9e22021-03-08 07:28:00 -080055 repository_ctx.report_progress("Fetching SDK information for Xcode %s" % version)
vladmos20a042f2018-06-01 04:51:21 -070056 xcodebuild_result = repository_ctx.execute(
57 ["xcrun", "xcodebuild", "-version", "-sdk"],
schmitt818bc012019-09-30 13:08:19 -070058 _EXECUTE_TIMEOUT,
vladmos20a042f2018-06-01 04:51:21 -070059 {"DEVELOPER_DIR": developer_dir},
60 )
61 if (xcodebuild_result.return_code != 0):
62 error_msg = (
63 "Invoking xcodebuild failed, developer dir: {devdir} ," +
64 "return code {code}, stderr: {err}, stdout: {out}"
65 ).format(
66 devdir = developer_dir,
67 code = xcodebuild_result.return_code,
68 err = xcodebuild_result.stderr,
69 out = xcodebuild_result.stdout,
70 )
71 ios_sdk_version = _search_sdk_output(xcodebuild_result.stdout, "iphoneos")
72 tvos_sdk_version = _search_sdk_output(xcodebuild_result.stdout, "appletvos")
73 macos_sdk_version = _search_sdk_output(xcodebuild_result.stdout, "macosx")
74 watchos_sdk_version = _search_sdk_output(xcodebuild_result.stdout, "watchos")
75 build_contents += "xcode_version(\n name = '%s'," % name
76 build_contents += "\n version = '%s'," % version
77 if aliases:
Samuel Giddinsf52e2182020-12-04 14:58:09 -080078 build_contents += "\n aliases = [%s]," % ", ".join(decorated_aliases)
vladmos20a042f2018-06-01 04:51:21 -070079 if ios_sdk_version:
80 build_contents += "\n default_ios_sdk_version = '%s'," % ios_sdk_version
81 if tvos_sdk_version:
82 build_contents += "\n default_tvos_sdk_version = '%s'," % tvos_sdk_version
83 if macos_sdk_version:
84 build_contents += "\n default_macos_sdk_version = '%s'," % macos_sdk_version
85 if watchos_sdk_version:
86 build_contents += "\n default_watchos_sdk_version = '%s'," % watchos_sdk_version
87 build_contents += "\n)\n"
Keith Smileyc6c9b582019-02-28 11:27:07 -080088 if error_msg:
89 build_contents += "\n# Error: " + error_msg.replace("\n", " ") + "\n"
90 print(error_msg)
vladmos20a042f2018-06-01 04:51:21 -070091 return build_contents
Chris Parsons381850e2016-08-31 17:04:17 +000092
Chris Parsonscc867d42016-09-20 15:41:09 +000093VERSION_CONFIG_STUB = "xcode_config(name = 'host_xcodes')"
94
cparsonscc219982017-05-03 22:13:44 +020095def run_xcode_locator(repository_ctx, xcode_locator_src_label):
vladmos20a042f2018-06-01 04:51:21 -070096 """Generates xcode-locator from source and runs it.
cparsonscc219982017-05-03 22:13:44 +020097
vladmos20a042f2018-06-01 04:51:21 -070098 Builds xcode-locator in the current repository directory.
99 Returns the standard output of running xcode-locator with -v, which will
100 return information about locally installed Xcode toolchains and the versions
101 they are associated with.
cparsonscc219982017-05-03 22:13:44 +0200102
vladmos20a042f2018-06-01 04:51:21 -0700103 This should only be invoked on a darwin OS, as xcode-locator cannot be built
104 otherwise.
cparsonscc219982017-05-03 22:13:44 +0200105
vladmos20a042f2018-06-01 04:51:21 -0700106 Args:
107 repository_ctx: The repository context.
108 xcode_locator_src_label: The label of the source file for xcode-locator.
109 Returns:
Keith Smileyc6c9b582019-02-28 11:27:07 -0800110 A 2-tuple containing:
111 output: A list representing installed xcode toolchain information. Each
112 element of the list is a struct containing information for one installed
113 toolchain. This is an empty list if there was an error building or
114 running xcode-locator.
115 err: An error string describing the error that occurred when attempting
116 to build and run xcode-locator, or None if the run was successful.
vladmos20a042f2018-06-01 04:51:21 -0700117 """
davg762f9e22021-03-08 07:28:00 -0800118 repository_ctx.report_progress("Building xcode-locator")
vladmos20a042f2018-06-01 04:51:21 -0700119 xcodeloc_src_path = str(repository_ctx.path(xcode_locator_src_label))
Brentley Jones008f7432020-10-22 07:05:52 -0700120 env = repository_ctx.os.environ
vladmos20a042f2018-06-01 04:51:21 -0700121 xcrun_result = repository_ctx.execute([
122 "env",
123 "-i",
Brentley Jones008f7432020-10-22 07:05:52 -0700124 "DEVELOPER_DIR={}".format(env.get("DEVELOPER_DIR", default = "")),
vladmos20a042f2018-06-01 04:51:21 -0700125 "xcrun",
Keith Smileyada2c552019-09-13 08:09:31 -0700126 "--sdk",
127 "macosx",
vladmos20a042f2018-06-01 04:51:21 -0700128 "clang",
Keith Smiley4ffb36f2019-09-18 09:01:05 -0700129 "-mmacosx-version-min=10.9",
vladmos20a042f2018-06-01 04:51:21 -0700130 "-fobjc-arc",
131 "-framework",
132 "CoreServices",
133 "-framework",
134 "Foundation",
135 "-o",
136 "xcode-locator-bin",
137 xcodeloc_src_path,
schmitt818bc012019-09-30 13:08:19 -0700138 ], _EXECUTE_TIMEOUT)
cparsonscc219982017-05-03 22:13:44 +0200139
vladmos20a042f2018-06-01 04:51:21 -0700140 if (xcrun_result.return_code != 0):
141 suggestion = ""
142 if "Agreeing to the Xcode/iOS license" in xcrun_result.stderr:
143 suggestion = ("(You may need to sign the xcode license." +
144 " Try running 'sudo xcodebuild -license')")
145 error_msg = (
146 "Generating xcode-locator-bin failed. {suggestion} " +
147 "return code {code}, stderr: {err}, stdout: {out}"
148 ).format(
149 suggestion = suggestion,
150 code = xcrun_result.return_code,
151 err = xcrun_result.stderr,
152 out = xcrun_result.stdout,
153 )
Keith Smileyc6c9b582019-02-28 11:27:07 -0800154 return ([], error_msg.replace("\n", " "))
cparsonscc219982017-05-03 22:13:44 +0200155
davg762f9e22021-03-08 07:28:00 -0800156 repository_ctx.report_progress("Running xcode-locator")
schmitt818bc012019-09-30 13:08:19 -0700157 xcode_locator_result = repository_ctx.execute(
158 ["./xcode-locator-bin", "-v"],
159 _EXECUTE_TIMEOUT,
160 )
vladmos20a042f2018-06-01 04:51:21 -0700161 if (xcode_locator_result.return_code != 0):
162 error_msg = (
163 "Invoking xcode-locator failed, " +
164 "return code {code}, stderr: {err}, stdout: {out}"
165 ).format(
166 code = xcode_locator_result.return_code,
167 err = xcode_locator_result.stderr,
168 out = xcode_locator_result.stdout,
Keith Smileyc6c9b582019-02-28 11:27:07 -0800169 )
170 return ([], error_msg.replace("\n", " "))
vladmos20a042f2018-06-01 04:51:21 -0700171 xcode_toolchains = []
cparsonscc219982017-05-03 22:13:44 +0200172
vladmos20a042f2018-06-01 04:51:21 -0700173 # xcode_dump is comprised of newlines with different installed xcode versions,
174 # each line of the form <version>:<comma_separated_aliases>:<developer_dir>.
175 xcode_dump = xcode_locator_result.stdout
176 for xcodeversion in xcode_dump.split("\n"):
177 if ":" in xcodeversion:
178 infosplit = xcodeversion.split(":")
179 toolchain = struct(
180 version = infosplit[0],
181 aliases = infosplit[1].split(","),
182 developer_dir = infosplit[2],
183 )
184 xcode_toolchains.append(toolchain)
Keith Smileyc6c9b582019-02-28 11:27:07 -0800185 return (xcode_toolchains, None)
cparsonscc219982017-05-03 22:13:44 +0200186
Chris Parsons381850e2016-08-31 17:04:17 +0000187def _darwin_build_file(repository_ctx):
vladmos20a042f2018-06-01 04:51:21 -0700188 """Evaluates local system state to create xcode_config and xcode_version targets."""
davg762f9e22021-03-08 07:28:00 -0800189 repository_ctx.report_progress("Fetching the default Xcode version")
Brentley Jones008f7432020-10-22 07:05:52 -0700190 env = repository_ctx.os.environ
191 xcodebuild_result = repository_ctx.execute([
192 "env",
193 "-i",
194 "DEVELOPER_DIR={}".format(env.get("DEVELOPER_DIR", default = "")),
195 "xcrun",
196 "xcodebuild",
197 "-version",
198 ], _EXECUTE_TIMEOUT)
Chris Parsons7479f672016-10-04 18:39:09 +0000199
Keith Smileyc6c9b582019-02-28 11:27:07 -0800200 (toolchains, xcodeloc_err) = run_xcode_locator(
vladmos20a042f2018-06-01 04:51:21 -0700201 repository_ctx,
202 Label(repository_ctx.attr.xcode_locator),
203 )
Chris Parsons381850e2016-08-31 17:04:17 +0000204
Keith Smileyc6c9b582019-02-28 11:27:07 -0800205 if xcodeloc_err:
206 return VERSION_CONFIG_STUB + "\n# Error: " + xcodeloc_err + "\n"
207
Keith Smileyb7d419b2020-03-04 02:09:39 -0800208 default_xcode_version = ""
209 default_xcode_build_version = ""
210 if xcodebuild_result.return_code == 0:
211 default_xcode_version = _search_string(xcodebuild_result.stdout, "Xcode ", "\n")
212 default_xcode_build_version = _search_string(
213 xcodebuild_result.stdout,
214 "Build version ",
215 "\n",
216 )
vladmos20a042f2018-06-01 04:51:21 -0700217 default_xcode_target = ""
218 target_names = []
219 buildcontents = ""
Chris Parsons381850e2016-08-31 17:04:17 +0000220
vladmos20a042f2018-06-01 04:51:21 -0700221 for toolchain in toolchains:
222 version = toolchain.version
223 aliases = toolchain.aliases
224 developer_dir = toolchain.developer_dir
225 target_name = "version%s" % version.replace(".", "_")
Keith Smileyb7d419b2020-03-04 02:09:39 -0800226 buildcontents += _xcode_version_output(
227 repository_ctx,
228 target_name,
229 version,
230 aliases,
231 developer_dir,
232 )
233 target_label = "':%s'" % target_name
234 target_names.append(target_label)
235 if (version.startswith(default_xcode_version) and
236 version.endswith(default_xcode_build_version)):
237 default_xcode_target = target_label
Samuel Giddinsf52e2182020-12-04 14:58:09 -0800238 buildcontents += "xcode_config(\n name = 'host_xcodes',"
vladmos20a042f2018-06-01 04:51:21 -0700239 if target_names:
240 buildcontents += "\n versions = [%s]," % ", ".join(target_names)
Keith Smileyb7d419b2020-03-04 02:09:39 -0800241 if not default_xcode_target and target_names:
242 default_xcode_target = sorted(target_names, reverse = True)[0]
243 print("No default Xcode version is set with 'xcode-select'; picking %s" %
244 default_xcode_target)
vladmos20a042f2018-06-01 04:51:21 -0700245 if default_xcode_target:
Keith Smileyb7d419b2020-03-04 02:09:39 -0800246 buildcontents += "\n default = %s," % default_xcode_target
247
vladmos20a042f2018-06-01 04:51:21 -0700248 buildcontents += "\n)\n"
Samuel Giddinsf52e2182020-12-04 14:58:09 -0800249 buildcontents += "available_xcodes(\n name = 'host_available_xcodes',"
steinmanec1625f2019-12-04 14:54:59 -0800250 if target_names:
251 buildcontents += "\n versions = [%s]," % ", ".join(target_names)
252 if default_xcode_target:
Keith Smileyb7d419b2020-03-04 02:09:39 -0800253 buildcontents += "\n default = %s," % default_xcode_target
steinmanec1625f2019-12-04 14:54:59 -0800254 buildcontents += "\n)\n"
steinmand0b21632020-01-08 13:27:41 -0800255 if repository_ctx.attr.remote_xcode:
256 buildcontents += "xcode_config(name = 'all_xcodes',"
257 buildcontents += "\n remote_versions = '%s', " % repository_ctx.attr.remote_xcode
258 buildcontents += "\n local_versions = ':host_available_xcodes', "
259 buildcontents += "\n)\n"
vladmos20a042f2018-06-01 04:51:21 -0700260 return buildcontents
Chris Parsons381850e2016-08-31 17:04:17 +0000261
262def _impl(repository_ctx):
vladmos20a042f2018-06-01 04:51:21 -0700263 """Implementation for the local_config_xcode repository rule.
Chris Parsons381850e2016-08-31 17:04:17 +0000264
vladmos20a042f2018-06-01 04:51:21 -0700265 Generates a BUILD file containing a root xcode_config target named 'host_xcodes',
266 which points to an xcode_version target for each version of xcode installed on
267 the local host machine. If no versions of xcode are present on the machine
268 (for instance, if this is a non-darwin OS), creates a stub target.
Chris Parsons381850e2016-08-31 17:04:17 +0000269
vladmos20a042f2018-06-01 04:51:21 -0700270 Args:
271 repository_ctx: The repository context.
272 """
Chris Parsons381850e2016-08-31 17:04:17 +0000273
vladmos20a042f2018-06-01 04:51:21 -0700274 os_name = repository_ctx.os.name.lower()
275 build_contents = "package(default_visibility = ['//visibility:public'])\n\n"
276 if (os_name.startswith("mac os")):
277 build_contents += _darwin_build_file(repository_ctx)
278 else:
279 build_contents += VERSION_CONFIG_STUB
280 repository_ctx.file("BUILD", build_contents)
Chris Parsons381850e2016-08-31 17:04:17 +0000281
282xcode_autoconf = repository_rule(
vladmos20a042f2018-06-01 04:51:21 -0700283 implementation = _impl,
284 local = True,
285 attrs = {
Chris Parsons381850e2016-08-31 17:04:17 +0000286 "xcode_locator": attr.string(),
steinmand0b21632020-01-08 13:27:41 -0800287 "remote_xcode": attr.string(),
vladmos20a042f2018-06-01 04:51:21 -0700288 },
Chris Parsons381850e2016-08-31 17:04:17 +0000289)
290
steinmand0b21632020-01-08 13:27:41 -0800291def xcode_configure(xcode_locator_label, remote_xcode_label = None):
vladmos20a042f2018-06-01 04:51:21 -0700292 """Generates a repository containing host xcode version information."""
293 xcode_autoconf(
294 name = "local_config_xcode",
295 xcode_locator = xcode_locator_label,
steinmand0b21632020-01-08 13:27:41 -0800296 remote_xcode = remote_xcode_label,
vladmos20a042f2018-06-01 04:51:21 -0700297 )