Skip to content

Commit 23cef3d

Browse files
committed
Introduce basic support for xcode_config error message customization
- Moved nearly all diagnostic messages in xcode_config to strings using named format fields - Moved the most basic, frequently occurring, `fail`ure messages into attrs on xcode_config - Updated docstrings
1 parent a8b3c96 commit 23cef3d

1 file changed

Lines changed: 121 additions & 66 deletions

File tree

xcode/xcode_config.bzl

Lines changed: 121 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,26 @@ load("@bazel_features//:features.bzl", "bazel_features")
2828

2929
visibility("public")
3030

31-
unavailable_xcode_message = "'bazel fetch --configure'"
31+
xcode_config_message_templates = struct(
32+
local_version_located_but_conflicts_with_remote_version = """\
33+
Xcode version {version} was selected, either because --xcode_version was passed, or \
34+
because xcode-select points to this version locally. This corresponds to \
35+
local Xcode version {local_version}. That build of version {version} is not available \
36+
remotely, but there is a different build of version {remote_version}, which has \
37+
version {remote_version} and aliases {remote_version_aliases}. You probably meant to use this version. \
38+
Please download it *and delete version {local_version}, then run `bazel shutdown` \
39+
to continue using dynamic execution. If you really did intend to use \
40+
local version {local_version}, please specify it fully with --xcode_version={local_version}.""",
41+
explicit_version_with_local_and_remote_versions_unavailable = """\
42+
--xcode_version={version} specified, but '{version}' is not an available Xcode version. \
43+
Locally available versions: [{local_versions}]. Remotely available versions: [{remote_versions}]. If \
44+
you believe you have '{version}' installed, try running {command}, and then \
45+
re-run your command.""",
46+
explicit_version_unavailable = """\
47+
--xcode_version={version} specified, but '{version}' is not an available Xcode version. \
48+
If you believe you have '{version}' installed, try running "bazel shutdown", and then \
49+
re-run your command.""",
50+
)
3251

3352
def _xcode_config_impl(ctx):
3453
# TODO: remove when we test with Bazel 8+
@@ -68,11 +87,14 @@ def _xcode_config_impl(ctx):
6887
read_possibly_native_flag(ctx, "xcode_version"),
6988
read_possibly_native_flag(ctx, "experimental_prefer_mutual_xcode"),
7089
local_default_version,
90+
ctx.attr.explicit_version_with_local_and_remote_versions_unavailable_message,
91+
ctx.attr.local_version_located_but_conflicts_with_remote_version_message,
7192
)
7293
else:
7394
xcode_version_properties = _resolve_explicitly_defined_version(
7495
explicit_versions,
7596
explicit_default_version,
97+
ctx.attr.explicit_version_unavailable_message,
7698
read_possibly_native_flag(ctx, "xcode_version"),
7799
)
78100
availability = "UNKNOWN"
@@ -173,6 +195,44 @@ version. This may not be set if `versions` is set.
173195
""",
174196
providers = [[AvailableXcodesInfo]],
175197
),
198+
# Message templates
199+
"explicit_version_with_local_and_remote_versions_unavailable_message": attr.string(
200+
default = xcode_config_message_templates.explicit_version_with_local_and_remote_versions_unavailable,
201+
doc = """\
202+
Template string. Contains the following named fields:
203+
204+
- `version`: the provided Xcode version
205+
- `command`: a command to suggest running to reconfigure your Bazel client
206+
- `local_versions`: a comma-separated list of allowed Xcode versions for local execution defined in the xcode_config
207+
- `remote_versions`: a comma-separated list of allowed Xcode versions for remote execution defined in the xcode_config
208+
209+
Emitted when either `local_versions` or `remote_versions` are set in the xcode_config,
210+
but a provided explicitly-defined Xcode version is unavailable under neither of them.""",
211+
),
212+
"explicit_version_unavailable_message": attr.string(
213+
default = xcode_config_message_templates.explicit_version_unavailable,
214+
doc = """\
215+
Template string. Contains the following named fields:
216+
217+
- `version`: the provided Xcode version
218+
219+
Emitted when a provided explicitly-defined Xcode version is unavailable under the xcode_config.""",
220+
),
221+
"local_version_located_but_conflicts_with_remote_version_message": attr.string(
222+
default = xcode_config_message_templates.local_version_located_but_conflicts_with_remote_version,
223+
doc = """\
224+
Template string. Contains the following named fields:
225+
226+
- `version`: the provided Xcode version
227+
- `local_version`: the resolved local Xcode version that differs from the remote version
228+
- `remote_version`: the resolved remote Xcode version that differs from the local version
229+
- `remote_version_aliases`: any configured aliases the remote version is available under
230+
231+
Emitted when a provided explicitly-defined Xcode version is resolved to a local version
232+
that conflicts with the remote_versions configuration.""",
233+
),
234+
235+
# Private settings
176236
"_xcode_version": attr.label(
177237
default = "@apple_support//xcode:version",
178238
),
@@ -200,6 +260,11 @@ A single target of this rule can be referenced by the `--xcode_version_config`
200260
build flag to translate the `--xcode_version` flag into an accepted official
201261
Xcode version. This allows selection of an official Xcode version from a number
202262
of registered aliases.
263+
264+
Template string messages abide by the semantics of Starlark. A provided string can
265+
contain any of the named values described by the given attribute's docstring,
266+
but cannot contain any fields beyond those described, any positional fields,
267+
or printf-style conversion specifications (%).
203268
""",
204269
fragments = ["apple", "cpp"],
205270
implementation = _xcode_config_impl,
@@ -220,17 +285,17 @@ def _use_available_xcodes(
220285
return True
221286
return False
222287

223-
def _duplicate_alias_error(alias, versions):
224-
labels_containing_alias = []
225-
for version in versions:
226-
if alias in version.aliases or (version.xcode_version_properties.xcode_version == alias):
227-
labels_containing_alias.append(str(version.label))
228-
return "'{}' is registered to multiple labels ({}) in a single xcode_config rule".format(
229-
alias,
230-
", ".join(labels_containing_alias),
231-
)
232-
233288
def _aliases_to_xcode_version(versions):
289+
def _duplicate_alias_error(alias, versions):
290+
labels_containing_alias = []
291+
for version in versions:
292+
if alias in version.aliases or (version.xcode_version_properties.xcode_version == alias):
293+
labels_containing_alias.append(str(version.label))
294+
return "'{alias}' is registered to multiple labels ({labels}) in a single xcode_config rule".format(
295+
alias = alias,
296+
labels = ", ".join(labels_containing_alias),
297+
)
298+
234299
version_map = {}
235300
if not versions:
236301
return version_map
@@ -253,7 +318,9 @@ def _resolve_xcode_from_local_and_remote(
253318
remote_versions,
254319
xcode_version_flag,
255320
prefer_mutual_xcode,
256-
local_default_version):
321+
local_default_version,
322+
explicit_version_with_local_and_remote_versions_unavailable_message,
323+
local_version_located_but_conflicts_with_remote_version_message):
257324
local_alias_to_version_map = _aliases_to_xcode_version(local_versions)
258325
remote_alias_to_version_map = _aliases_to_xcode_version(remote_versions)
259326

@@ -274,38 +341,29 @@ def _resolve_xcode_from_local_and_remote(
274341
remote_version_from_flag = remote_alias_to_version_map.get(xcode_version_flag)
275342
local_version_from_flag = local_alias_to_version_map.get(xcode_version_flag)
276343
availability = "BOTH"
344+
unavailable_xcode_message = "'bazel fetch --configure'"
277345

278346
if remote_version_from_flag and local_version_from_flag:
279347
local_version_from_remote_versions = remote_alias_to_version_map.get(local_version_from_flag.xcode_version_properties.xcode_version)
280348
if local_version_from_remote_versions:
281349
return remote_version_from_flag.xcode_version_properties, availability
282350
else:
283351
fail(
284-
("Xcode version {0} was selected, either because --xcode_version was passed, or" +
285-
" because xcode-select points to this version locally. This corresponds to" +
286-
" local Xcode version {1}. That build of version {0} is not available" +
287-
" remotely, but there is a different build of version {2}, which has" +
288-
" version {2} and aliases {3}. You probably meant to use this version." +
289-
" Please download it *and delete version {1}, then run `bazel shutdown`" +
290-
" to continue using dynamic execution. If you really did intend to use" +
291-
" local version {1}, please specify it fully with --xcode_version={1}.").format(
292-
xcode_version_flag,
293-
local_version_from_flag.xcode_version_properties.xcode_version,
294-
remote_version_from_flag.xcode_version_properties.xcode_version,
295-
remote_version_from_flag.aliases,
352+
local_version_located_but_conflicts_with_remote_version_message.format(
353+
version = xcode_version_flag,
354+
local_version = local_version_from_flag.xcode_version_properties.xcode_version,
355+
remote_version = remote_version_from_flag.xcode_version_properties.xcode_version,
356+
remote_version_aliases = remote_version_from_flag.aliases,
296357
),
297358
)
298359

299360
elif local_version_from_flag:
300-
error = (
301-
" --xcode_version={} specified, but it is not available remotely. Actions " +
302-
"requiring Xcode will be run locally, which could make your build slower."
303-
).format(
304-
xcode_version_flag,
305-
)
306-
if (mutually_available_versions):
307-
error += " Consider using one of [{}].".format(
308-
", ".join([version for version in mutually_available_versions]),
361+
error = """\
362+
--xcode_version={version} specified, but it is not available remotely. Actions \
363+
requiring Xcode will be run locally, which could make your build slower.""".format(version = xcode_version_flag)
364+
if mutually_available_versions:
365+
error += " Consider using one of [{available_versions}].".format(
366+
available_versions = ", ".join([version for version in mutually_available_versions]),
309367
)
310368

311369
# buildifier: disable=print
@@ -314,29 +372,28 @@ def _resolve_xcode_from_local_and_remote(
314372

315373
elif remote_version_from_flag:
316374
# buildifier: disable=print
317-
print(("--xcode_version={version} specified, but it is not available locally. " +
318-
"Your build will fail if any actions require a local Xcode. " +
319-
"If you believe you have '{version}' installed, try running {command}," +
320-
"and then re-run your command. Locally available versions: {local_versions}. ")
321-
.format(
322-
version = xcode_version_flag,
323-
command = unavailable_xcode_message,
324-
local_versions = ", ".join([version for version in local_alias_to_version_map.keys()]),
325-
))
375+
print(
376+
"""\
377+
--xcode_version={version} specified, but it is not available locally. \
378+
Your build will fail if any actions require a local Xcode. \
379+
If you believe you have '{version}' installed, try running {command}, \
380+
and then re-run your command. Locally available versions: {local_versions}.""".format(
381+
version = xcode_version_flag,
382+
command = unavailable_xcode_message,
383+
local_versions = ", ".join([version for version in local_alias_to_version_map.keys()]),
384+
),
385+
)
326386
availability = "REMOTE"
327387

328388
return remote_version_from_flag.xcode_version_properties, availability
329389

330390
else: # fail if we can't find any version to match
331391
fail(
332-
("--xcode_version={0} specified, but '{0}' is not an available Xcode version." +
333-
" Locally available versions: [{2}]. Remotely available versions: [{3}]. If" +
334-
" you believe you have '{0}' installed, try running {1}, and then" +
335-
" re-run your command.").format(
336-
xcode_version_flag,
337-
unavailable_xcode_message,
338-
", ".join([version.xcode_version_properties.xcode_version for version in local_versions]),
339-
", ".join([version.xcode_version_properties.xcode_version for version in remote_versions]),
392+
explicit_version_with_local_and_remote_versions_unavailable_message.format(
393+
version = xcode_version_flag,
394+
command = unavailable_xcode_message,
395+
local_versions = ", ".join([version.xcode_version_properties.xcode_version for version in local_versions]),
396+
remote_versions = ", ".join([version.xcode_version_properties.xcode_version for version in remote_versions]),
340397
),
341398
)
342399

@@ -348,14 +405,18 @@ def _resolve_xcode_from_local_and_remote(
348405
if not mutually_available_versions:
349406
# buildifier: disable=print
350407
print(
351-
("Using a local Xcode version, '{}', since there are no" +
352-
" remotely available Xcodes on this machine. Consider downloading one of the" +
353-
" remotely available Xcode versions ({}) in order to get the best build" +
354-
" performance.").format(local_default_version.xcode_version_properties.xcode_version, ", ".join([version.xcode_version_properties.xcode_version for version in remote_versions])),
408+
"""\
409+
Using a local Xcode version, '{local_version}', since there are no \
410+
remotely available Xcodes on this machine. Consider downloading one of the \
411+
remotely available Xcode versions ({remote_versions}) in order to get the best build \
412+
performance.""".format(
413+
local_version = local_default_version.xcode_version_properties.xcode_version,
414+
remote_versions = ", ".join([version.xcode_version_properties.xcode_version for version in remote_versions]),
415+
),
355416
)
356417
local_version = local_default_version
357418
availability = "LOCAL"
358-
elif (local_default_version.xcode_version_properties.xcode_version in remote_alias_to_version_map):
419+
elif local_default_version.xcode_version_properties.xcode_version in remote_alias_to_version_map:
359420
# If the local default version is also available remotely, use it.
360421
availability = "BOTH"
361422
local_version = remote_alias_to_version_map.get(local_default_version.xcode_version_properties.xcode_version)
@@ -396,15 +457,14 @@ def _sdk_version_flag(apple_fragment, name):
396457
def _resolve_explicitly_defined_version(
397458
explicit_versions,
398459
explicit_default_version,
460+
explicit_version_unavailable_message,
399461
xcode_version_flag):
400462
if explicit_default_version and explicit_default_version.label not in [
401463
version.label
402464
for version in explicit_versions
403465
]:
404466
fail(
405-
"default label '{}' must be contained in versions attribute".format(
406-
explicit_default_version.label,
407-
),
467+
"default label '{label}' must be contained in versions attribute".format(label = explicit_default_version.label),
408468
)
409469
if not explicit_versions:
410470
if explicit_default_version:
@@ -417,14 +477,9 @@ def _resolve_explicitly_defined_version(
417477
alias_to_versions = _aliases_to_xcode_version(explicit_versions)
418478
if xcode_version_flag:
419479
flag_version = alias_to_versions.get(str(xcode_version_flag))
420-
if flag_version:
421-
return flag_version.xcode_version_properties
422-
else:
423-
fail(
424-
("--xcode_version={0} specified, but '{0}' is not an available Xcode version. " +
425-
"If you believe you have '{0}' installed, try running \"bazel shutdown\", and then " +
426-
"re-run your command.").format(xcode_version_flag),
427-
)
480+
if not flag_version:
481+
fail(explicit_version_unavailable_message.format(version = xcode_version_flag))
482+
return flag_version.xcode_version_properties
428483
return alias_to_versions.get(explicit_default_version.xcode_version_properties.xcode_version).xcode_version_properties
429484

430485
def _dotted_version_or_default(field, default):

0 commit comments

Comments
 (0)