Skip to content
Draft
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
41 changes: 41 additions & 0 deletions test/test_helpers.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,47 @@ FIXTURE_TAGS = [
"manual",
]

def assert_warning(env, msg_id, data = None):
"""Asserts that a warning was printed.

The logic in this helper is specifically meant to handle warnings printed by
`xcode_config.bzl`, because we want to preserve the behavior of the original
rules and their tests.

Args:
env: The analysis test environment.
msg_id: The fixed identifier of the warning message.
data: The semicolon-delimited, sorted by key, `key=value` string
representation of the format arguments for the message, if any.
"""
expected = "Warning:{}".format(msg_id)
if data:
expected += ":{}".format(data)

found_warnings = []

actions = analysistest.target_actions(env)
for action in actions:
mnemonic = action.mnemonic
if mnemonic == expected:
return
if mnemonic.startswith("Warning:"):
found_warnings.append(mnemonic)

found_warnings_msg = ""
if found_warnings:
found_warnings_msg = "; the following warnings were emitted:\n{}".format(
"\n".join(found_warnings),
)

analysistest.fail(
env,
"Expected warning '{}' was not emitted{}".format(
expected,
found_warnings_msg,
),
)

def find_action(env, mnemonic):
"""Finds the first action with the given mnemonic in the target under test.

Expand Down
64 changes: 42 additions & 22 deletions test/xcode_config_test.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,23 @@
load("@bazel_features//:features.bzl", "bazel_features")
load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts")
load("//xcode:available_xcodes.bzl", "available_xcodes")
load("//xcode:xcode_config.bzl", "xcode_config")
load(
"//xcode:xcode_config.bzl",
"UNAVAILABLE_XCODE_MESSAGE",
"xcode_config",
)
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")
load(
":test_helpers.bzl",
"FIXTURE_TAGS",
"assert_warning",
"find_action",
"make_all_tests",
)

visibility("private")

Expand Down Expand Up @@ -330,6 +340,12 @@ def _prefers_flag_over_mutually_available_test_impl(ctx):
asserts.true(env, "no-local" in xcode_version_info.execution_info())
asserts.true(env, "supports-xcode-requirements-set" in xcode_version_info.execution_info())

assert_warning(
env,
"version_not_available_locally",
"command={};local_versions=8.4;version=5.1.2".format(UNAVAILABLE_XCODE_MESSAGE),
)

return analysistest.end(env)

_prefers_flag_over_mutually_available_test = analysistest.make(
Expand Down Expand Up @@ -368,16 +384,18 @@ def _warn_with_explicit_local_only_version_test_impl(ctx):
target_under_test = analysistest.target_under_test(env)
xcode_version_info = target_under_test[apple_common.XcodeVersionConfig]

# TODO: b/311385128 - Once we move the rules to apple_support, hack up
# something that would let us actually test the warning messages. We can't
# test `print`.

asserts.equals(env, "8.4", str(xcode_version_info.xcode_version()))
asserts.equals(env, "local", xcode_version_info.availability())
asserts.true(env, "requires-darwin" in xcode_version_info.execution_info())
asserts.true(env, "no-remote" in xcode_version_info.execution_info())
asserts.true(env, "supports-xcode-requirements-set" in xcode_version_info.execution_info())

assert_warning(
env,
"explicit_version_not_available_remotely",
"version=8.4",
)

return analysistest.end(env)

_warn_with_explicit_local_only_version_test = analysistest.make(
Expand Down Expand Up @@ -416,16 +434,18 @@ def _prefer_local_default_if_no_mutual_no_flag_different_main_version_test_impl(
target_under_test = analysistest.target_under_test(env)
xcode_version_info = target_under_test[apple_common.XcodeVersionConfig]

# TODO: b/311385128 - Once we move the rules to apple_support, hack up
# something that would let us actually test the warning messages. We can't
# test `print`.

asserts.equals(env, "8.4", str(xcode_version_info.xcode_version()))
asserts.equals(env, "local", xcode_version_info.availability())
asserts.true(env, "requires-darwin" in xcode_version_info.execution_info())
asserts.true(env, "no-remote" in xcode_version_info.execution_info())
asserts.true(env, "supports-xcode-requirements-set" in xcode_version_info.execution_info())

assert_warning(
env,
"local_default_not_available_remotely",
"local_version=8.4;remote_versions=5.1.2",
)

return analysistest.end(env)

_prefer_local_default_if_no_mutual_no_flag_different_main_version_test = analysistest.make(
Expand Down Expand Up @@ -463,16 +483,18 @@ def _prefer_local_default_if_no_mutual_no_flag_different_build_alias_test_impl(c
target_under_test = analysistest.target_under_test(env)
xcode_version_info = target_under_test[apple_common.XcodeVersionConfig]

# TODO: b/311385128 - Once we move the rules to apple_support, hack up
# something that would let us actually test the warning messages. We can't
# test `print`.

asserts.equals(env, "10.0.0.10C504", str(xcode_version_info.xcode_version()))
asserts.equals(env, "local", xcode_version_info.availability())
asserts.true(env, "requires-darwin" in xcode_version_info.execution_info())
asserts.true(env, "no-remote" in xcode_version_info.execution_info())
asserts.true(env, "supports-xcode-requirements-set" in xcode_version_info.execution_info())

assert_warning(
env,
"local_default_not_available_remotely",
"local_version=10.0.0.10C504;remote_versions=10.0",
)

return analysistest.end(env)

_prefer_local_default_if_no_mutual_no_flag_different_build_alias_test = analysistest.make(
Expand Down Expand Up @@ -510,16 +532,18 @@ def _prefer_local_default_if_no_mutual_no_flag_different_full_version_test_impl(
target_under_test = analysistest.target_under_test(env)
xcode_version_info = target_under_test[apple_common.XcodeVersionConfig]

# TODO: b/311385128 - Once we move the rules to apple_support, hack up
# something that would let us actually test the warning messages. We can't
# test `print`.

asserts.equals(env, "10.0.0.10C504", str(xcode_version_info.xcode_version()))
asserts.equals(env, "local", xcode_version_info.availability())
asserts.true(env, "requires-darwin" in xcode_version_info.execution_info())
asserts.true(env, "no-remote" in xcode_version_info.execution_info())
asserts.true(env, "supports-xcode-requirements-set" in xcode_version_info.execution_info())

assert_warning(
env,
"local_default_not_available_remotely",
"local_version=10.0.0.10C504;remote_versions=10.0.0.101ff",
)

return analysistest.end(env)

_prefer_local_default_if_no_mutual_no_flag_different_full_version_test = analysistest.make(
Expand Down Expand Up @@ -561,10 +585,6 @@ def _choose_newest_mutual_xcode_test_impl(ctx):
target_under_test = analysistest.target_under_test(env)
xcode_version_info = target_under_test[apple_common.XcodeVersionConfig]

# TODO: b/311385128 - Once we move the rules to apple_support, hack up
# something that would let us actually test the warning messages. We can't
# test `print`.

asserts.equals(env, "10", str(xcode_version_info.xcode_version()))
asserts.equals(env, "both", xcode_version_info.availability())
asserts.true(env, "requires-darwin" in xcode_version_info.execution_info())
Expand Down
108 changes: 80 additions & 28 deletions xcode/xcode_config.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ load(

visibility("public")

unavailable_xcode_message = "'bazel fetch --configure'"
UNAVAILABLE_XCODE_MESSAGE = "'bazel fetch --configure'"

def _xcode_config_impl(ctx):
# TODO: remove when we test with Bazel 8+
Expand Down Expand Up @@ -59,6 +59,7 @@ def _xcode_config_impl(ctx):
remote_versions,
):
xcode_version_properties, availability = _resolve_xcode_from_local_and_remote(
ctx.actions,
local_versions,
remote_versions,
apple_fragment.xcode_version_flag,
Expand Down Expand Up @@ -221,6 +222,7 @@ def _aliases_to_xcode_version(versions):
return version_map

def _resolve_xcode_from_local_and_remote(
actions,
local_versions,
remote_versions,
xcode_version_flag,
Expand Down Expand Up @@ -269,32 +271,29 @@ def _resolve_xcode_from_local_and_remote(
)

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]),
if mutually_available_versions:
_warn(
actions,
"explicit_version_not_available_remotely_consider_mutual",
version = xcode_version_flag,
mutual_versions = [version for version in mutually_available_versions],
)
else:
_warn(
actions,
"explicit_version_not_available_remotely",
version = xcode_version_flag,
)

# 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(
_warn(
actions,
"version_not_available_locally",
version = xcode_version_flag,
command = unavailable_xcode_message,
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
Expand All @@ -306,7 +305,7 @@ def _resolve_xcode_from_local_and_remote(
" you believe you have '{0}' installed, try running {1}, and then" +
" re-run your command.").format(
xcode_version_flag,
unavailable_xcode_message,
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]),
),
Expand All @@ -318,12 +317,11 @@ def _resolve_xcode_from_local_and_remote(

# 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])),
_warn(
actions,
"local_default_not_available_remotely",
local_version = local_default_version.xcode_version_properties.xcode_version,
remote_versions = ", ".join([version.xcode_version_properties.xcode_version for version in remote_versions]),
)
local_version = local_default_version
availability = "LOCAL"
Expand Down Expand Up @@ -398,3 +396,57 @@ def _resolve_explicitly_defined_version(

def _dotted_version_or_default(field, default):
return apple_common.dotted_version(field) or default

_WARNINGS = {
"version_not_available_locally": """\
--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}.
""",
"local_default_not_available_remotely": """\
Using a local Xcode version, '{local_version}', since there are no \
remotely available Xcodes on this machine. Consider downloading one of the \
remotely available Xcode versions ({remote_versions}) in order to get the best \
build performance.
""",
"explicit_version_not_available_remotely": """\
--xcode_version={version} specified, but it is not available remotely. Actions \
requiring Xcode will be run locally, which could make your build slower.
""",
"explicit_version_not_available_remotely_consider_mutual": """\
--xcode_version={version} specified, but it is not available remotely. Actions \
requiring Xcode will be run locally, which could make your build slower. \
Consider using one of [{mutual_versions}].
""",
}

def _warn(actions, msg_id, **kwargs):
"""Print a warning and also record it as a testable dummy action.

Starlark doesn't support testing the output of the `print()` function, so
this function also registers a dummy action with a specifically formatted
mnemonic that can be read in the test using the `assert_warning` helper.

Args:
actions: The object used to register actions.
msg_id: A string identifying the warning as a key in the `_WARNINGS`
dictionary.
**kwargs: Formatting arguments for the message string.
"""

# buildifier: disable=print
print(_WARNINGS[msg_id].format(**kwargs))

mnemonic = "Warning:{}".format(msg_id)
if kwargs:
# Sort the format arguments by key so that they're deterministically
# ordered for tests.
sorted_values = []
for key in sorted(kwargs.keys()):
sorted_values.append("{}={}".format(key, str(kwargs[key])))
mnemonic += ":{}".format(";".join(sorted_values))

actions.do_nothing(
mnemonic = mnemonic,
)