From 39205cb6069870543bc9019a3de7c1e623d0db2a Mon Sep 17 00:00:00 2001 From: Brentley Jones Date: Wed, 11 Oct 2023 10:57:09 -0500 Subject: [PATCH] Add `xcschemes` Starlark integration Signed-off-by: Brentley Jones --- test/BUILD | 2 + test/internal/xcschemes/BUILD | 26 + .../xcschemes/info_constructors_tests.bzl | 728 ++++++++ .../xcschemes/infos_from_json_tests.bzl | 951 ++++++++++ test/internal/xcschemes/utils.bzl | 121 ++ .../xcschemes/write_schemes_tests.bzl | 1651 +++++++++++++++++ xcodeproj/internal/xcschemes/BUILD | 21 + .../internal/xcschemes/xcscheme_infos.bzl | 684 +++++++ .../internal/xcschemes/xcscheme_labels.bzl | 136 ++ xcodeproj/internal/xcschemes/xcschemes.bzl | 1125 +++++++++++ .../xcschemes/xcschemes_execution.bzl | 433 +++++ 11 files changed, 5878 insertions(+) create mode 100644 test/internal/xcschemes/BUILD create mode 100644 test/internal/xcschemes/info_constructors_tests.bzl create mode 100644 test/internal/xcschemes/infos_from_json_tests.bzl create mode 100644 test/internal/xcschemes/utils.bzl create mode 100644 test/internal/xcschemes/write_schemes_tests.bzl create mode 100644 xcodeproj/internal/xcschemes/BUILD create mode 100644 xcodeproj/internal/xcschemes/xcscheme_infos.bzl create mode 100644 xcodeproj/internal/xcschemes/xcscheme_labels.bzl create mode 100644 xcodeproj/internal/xcschemes/xcschemes.bzl create mode 100644 xcodeproj/internal/xcschemes/xcschemes_execution.bzl diff --git a/test/BUILD b/test/BUILD index ed1e77144d..abe59bfcc9 100644 --- a/test/BUILD +++ b/test/BUILD @@ -14,6 +14,7 @@ test_suite( "//test/internal/target_id", "//test/internal/targets", "//test/internal/xcode_schemes", + "//test/internal/xcschemes", ], ) @@ -30,6 +31,7 @@ bzl_library( "//test/internal/target_id:bzls", "//test/internal/targets:bzls", "//test/internal/xcode_schemes:bzls", + "//test/internal/xcschemes:bzls", ], ) diff --git a/test/internal/xcschemes/BUILD b/test/internal/xcschemes/BUILD new file mode 100644 index 0000000000..0d6f8b5922 --- /dev/null +++ b/test/internal/xcschemes/BUILD @@ -0,0 +1,26 @@ +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") +load(":info_constructors_tests.bzl", "info_constructors_test_suite") +load(":infos_from_json_tests.bzl", "infos_from_json_test_suite") +load(":write_schemes_tests.bzl", "write_schemes_test_suite") + +info_constructors_test_suite(name = "info_constructors") + +infos_from_json_test_suite(name = "infos_from_json") + +write_schemes_test_suite(name = "write_schemes") + +test_suite(name = "xcschemes") + +bzl_library( + name = "bzls", + srcs = glob( + ["*.bzl"], + exclude = ["utils.bzl"], + ), + visibility = ["//test:__pkg__"], +) + +bzl_library( + name = "utils", + srcs = ["utils.bzl"], +) diff --git a/test/internal/xcschemes/info_constructors_tests.bzl b/test/internal/xcschemes/info_constructors_tests.bzl new file mode 100644 index 0000000000..d3cc030a3c --- /dev/null +++ b/test/internal/xcschemes/info_constructors_tests.bzl @@ -0,0 +1,728 @@ +"""Tests for the `xcschemes_infos_testable` module.""" + +load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest") + +# buildifier: disable=bzl-visibility +load( + "//xcodeproj/internal/xcschemes:xcscheme_infos.bzl", + "xcscheme_infos_testable", +) + +def _info_constructors_test_impl(ctx): + env = unittest.begin(ctx) + + # Arrange / Act + + expected_info = json.decode(ctx.attr.expected_info) + info = json.decode(ctx.attr.info) + + # Assert + + asserts.equals( + env, + expected_info, + info, + ) + + return unittest.end(env) + +info_constructors_test = unittest.make( + impl = _info_constructors_test_impl, + # @unsorted-dict-items + attrs = { + # Inputs + "info": attr.string(mandatory = True), + + # Expected + "expected_info": attr.string(mandatory = True), + }, +) + +def info_constructors_test_suite(name): + """Test suite for `xcscheme_infos` constructors (i.e. \ + `xcscheme_infos_testable`). + + Args: + name: The base name to be used in things created by this macro. Also the + name of the test suite. + """ + test_names = [] + + def _add_test( + *, + name, + + # Inputs + info, + + # Expected + expected_info): + test_names.append(name) + info_constructors_test( + name = name, + + # Inputs + info = json.encode(info), + + # Expected + expected_info = json.encode(expected_info), + ) + + # make_arg_env + + _add_test( + name = "{}_make_arg_env_minimal".format(name), + + # Inputs + info = xcscheme_infos_testable.make_arg_env(value = "a\nnew line"), + + # Expected + expected_info = struct( + enabled = "1", + value = "a\nnew line", + ), + ) + + _add_test( + name = "{}_make_arg_env_full".format(name), + + # Inputs + info = xcscheme_infos_testable.make_arg_env( + enabled = "0", + value = "b", + ), + + # Expected + expected_info = struct( + enabled = "0", + value = "b", + ), + ) + + # make_build_target + + _add_test( + name = "{}_make_build_target_minimal".format(name), + + # Inputs + info = xcscheme_infos_testable.make_build_target(id = "an id"), + + # Expected + expected_info = struct( + id = "an id", + post_actions = [], + pre_actions = [], + ), + ) + + _add_test( + name = "{}_make_build_target_full".format(name), + + # Inputs + info = xcscheme_infos_testable.make_build_target( + id = "different id", + post_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = False, + order = "", + script_text = "script", + title = "title", + ), + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "1", + script_text = "s", + title = "t", + ), + ], + pre_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "2", + script_text = "ss", + title = "tt", + ), + xcscheme_infos_testable.make_pre_post_action( + for_build = False, + order = "11", + script_text = "sss", + title = "ttt", + ), + ], + ), + + # Expected + expected_info = struct( + id = "different id", + post_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = False, + order = "", + script_text = "script", + title = "title", + ), + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "1", + script_text = "s", + title = "t", + ), + ], + pre_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "2", + script_text = "ss", + title = "tt", + ), + xcscheme_infos_testable.make_pre_post_action( + for_build = False, + order = "11", + script_text = "sss", + title = "ttt", + ), + ], + ), + ) + + # make_diagnostics + + _add_test( + name = "{}_make_diagnostics_minimal".format(name), + + # Inputs + info = xcscheme_infos_testable.make_diagnostics(), + + # Expected + expected_info = struct( + address_sanitizer = "0", + thread_sanitizer = "0", + undefined_behavior_sanitizer = "0", + ), + ) + + _add_test( + name = "{}_make_diagnostics_full".format(name), + + # Inputs + info = xcscheme_infos_testable.make_diagnostics( + address_sanitizer = "1", + thread_sanitizer = "1", + undefined_behavior_sanitizer = "1", + ), + + # Expected + expected_info = struct( + address_sanitizer = "1", + thread_sanitizer = "1", + undefined_behavior_sanitizer = "1", + ), + ) + + # make_launch_target + + _add_test( + name = "{}_make_launch_target_minimal".format(name), + + # Inputs + info = xcscheme_infos_testable.make_launch_target(), + + # Expected + expected_info = struct( + extension_host = "", + id = "", + post_actions = [], + pre_actions = [], + working_directory = "", + ), + ) + + _add_test( + name = "{}_make_launch_target_full".format(name), + + # Inputs + info = xcscheme_infos_testable.make_launch_target( + extension_host = "host id", + id = "an id", + post_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = False, + order = "", + script_text = "script", + title = "title", + ), + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "1", + script_text = "s", + title = "t", + ), + ], + pre_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "2", + script_text = "ss", + title = "tt", + ), + xcscheme_infos_testable.make_pre_post_action( + for_build = False, + order = "11", + script_text = "sss", + title = "ttt", + ), + ], + working_directory = "a working directory", + ), + + # Expected + expected_info = struct( + extension_host = "host id", + id = "an id", + post_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = False, + order = "", + script_text = "script", + title = "title", + ), + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "1", + script_text = "s", + title = "t", + ), + ], + pre_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "2", + script_text = "ss", + title = "tt", + ), + xcscheme_infos_testable.make_pre_post_action( + for_build = False, + order = "11", + script_text = "sss", + title = "ttt", + ), + ], + working_directory = "a working directory", + ), + ) + + # make_pre_post_action + + _add_test( + name = "{}_make_pre_post_action".format(name), + + # Inputs + info = xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "2", + script_text = "script\ntext", + title = "title\ntext", + ), + + # Expected + expected_info = struct( + for_build = True, + order = "2", + script_text = "script\ntext", + title = "title\ntext", + ), + ) + + # make_profile + + _add_test( + name = "{}_make_profile_minimal".format(name), + + # Inputs + info = xcscheme_infos_testable.make_profile(), + + # Expected + expected_info = struct( + args = None, + build_targets = [], + env = None, + env_include_defaults = "0", + launch_target = xcscheme_infos_testable.make_launch_target(), + use_run_args_and_env = "1", + xcode_configuration = "", + ), + ) + + _add_test( + name = "{}_make_profile_full".format(name), + + # Inputs + info = xcscheme_infos_testable.make_profile( + args = [ + xcscheme_infos_testable.make_arg_env( + enabled = "1", + value = "a\nnew line", + ), + xcscheme_infos_testable.make_arg_env( + enabled = "0", + value = "b", + ), + ], + build_targets = [ + xcscheme_infos_testable.make_build_target("bt 2"), + xcscheme_infos_testable.make_build_target("bt 0"), + ], + env = { + "VAR\n0": xcscheme_infos_testable.make_arg_env("value 0"), + "VAR 1": xcscheme_infos_testable.make_arg_env("value\n1"), + }, + env_include_defaults = "1", + launch_target = xcscheme_infos_testable.make_launch_target("L"), + use_run_args_and_env = "0", + xcode_configuration = "Profile", + ), + + # Expected + expected_info = struct( + args = [ + xcscheme_infos_testable.make_arg_env( + enabled = "1", + value = "a\nnew line", + ), + xcscheme_infos_testable.make_arg_env( + enabled = "0", + value = "b", + ), + ], + build_targets = [ + xcscheme_infos_testable.make_build_target("bt 2"), + xcscheme_infos_testable.make_build_target("bt 0"), + ], + env = { + "VAR\n0": xcscheme_infos_testable.make_arg_env("value 0"), + "VAR 1": xcscheme_infos_testable.make_arg_env("value\n1"), + }, + env_include_defaults = "1", + launch_target = xcscheme_infos_testable.make_launch_target("L"), + use_run_args_and_env = "0", + xcode_configuration = "Profile", + ), + ) + + # make_run + + _add_test( + name = "{}_make_run_minimal".format(name), + + # Inputs + info = xcscheme_infos_testable.make_run(), + + # Expected + expected_info = struct( + args = None, + build_targets = [], + diagnostics = xcscheme_infos_testable.make_diagnostics(), + env = None, + env_include_defaults = "1", + launch_target = xcscheme_infos_testable.make_launch_target(), + xcode_configuration = "", + ), + ) + + _add_test( + name = "{}_make_run_full".format(name), + + # Inputs + info = xcscheme_infos_testable.make_run( + args = [ + xcscheme_infos_testable.make_arg_env( + enabled = "1", + value = "a\nnew line", + ), + xcscheme_infos_testable.make_arg_env( + enabled = "0", + value = "b", + ), + ], + build_targets = [ + xcscheme_infos_testable.make_build_target("bt 2"), + xcscheme_infos_testable.make_build_target("bt 0"), + ], + diagnostics = xcscheme_infos_testable.make_diagnostics( + undefined_behavior_sanitizer = "1", + ), + env = { + "VAR\n0": xcscheme_infos_testable.make_arg_env("value 0"), + "VAR 1": xcscheme_infos_testable.make_arg_env("value\n1"), + }, + env_include_defaults = "0", + launch_target = xcscheme_infos_testable.make_launch_target("L"), + xcode_configuration = "Run", + ), + + # Expected + expected_info = struct( + args = [ + xcscheme_infos_testable.make_arg_env( + enabled = "1", + value = "a\nnew line", + ), + xcscheme_infos_testable.make_arg_env( + enabled = "0", + value = "b", + ), + ], + build_targets = [ + xcscheme_infos_testable.make_build_target("bt 2"), + xcscheme_infos_testable.make_build_target("bt 0"), + ], + diagnostics = xcscheme_infos_testable.make_diagnostics( + undefined_behavior_sanitizer = "1", + ), + env = { + "VAR\n0": xcscheme_infos_testable.make_arg_env("value 0"), + "VAR 1": xcscheme_infos_testable.make_arg_env("value\n1"), + }, + env_include_defaults = "0", + launch_target = xcscheme_infos_testable.make_launch_target( + id = "L", + ), + xcode_configuration = "Run", + ), + ) + + # make_scheme + + _add_test( + name = "{}_make_scheme_minimal".format(name), + + # Inputs + info = xcscheme_infos_testable.make_scheme(name = "a Scheme"), + + # Expected + expected_info = struct( + name = "a Scheme", + profile = xcscheme_infos_testable.make_profile(), + run = xcscheme_infos_testable.make_run(), + test = xcscheme_infos_testable.make_test(), + ), + ) + + _add_test( + name = "{}_make_scheme_full".format(name), + + # Inputs + info = xcscheme_infos_testable.make_scheme( + name = "scheme", + profile = xcscheme_infos_testable.make_profile( + xcode_configuration = "P", + ), + run = xcscheme_infos_testable.make_run( + xcode_configuration = "R", + ), + test = xcscheme_infos_testable.make_test( + xcode_configuration = "R", + ), + ), + + # Expected + expected_info = struct( + name = "scheme", + profile = xcscheme_infos_testable.make_profile( + xcode_configuration = "P", + ), + run = xcscheme_infos_testable.make_run( + xcode_configuration = "R", + ), + test = xcscheme_infos_testable.make_test( + xcode_configuration = "R", + ), + ), + ) + + # make_test + + _add_test( + name = "{}_make_test_minimal".format(name), + + # Inputs + info = xcscheme_infos_testable.make_test(), + + # Expected + expected_info = struct( + args = None, + build_targets = [], + diagnostics = xcscheme_infos_testable.make_diagnostics(), + env = None, + env_include_defaults = "0", + test_targets = [], + use_run_args_and_env = "1", + xcode_configuration = "", + ), + ) + + _add_test( + name = "{}_make_test_full".format(name), + + # Inputs + info = xcscheme_infos_testable.make_test( + args = [ + xcscheme_infos_testable.make_arg_env( + enabled = "1", + value = "a\nnew line", + ), + xcscheme_infos_testable.make_arg_env( + enabled = "0", + value = "b", + ), + ], + build_targets = [ + xcscheme_infos_testable.make_build_target("bt 2"), + xcscheme_infos_testable.make_build_target("bt 0"), + ], + diagnostics = xcscheme_infos_testable.make_diagnostics( + thread_sanitizer = "1", + ), + env = { + "VAR\n0": xcscheme_infos_testable.make_arg_env("value 0"), + "VAR 1": xcscheme_infos_testable.make_arg_env("value\n1"), + }, + env_include_defaults = "1", + test_targets = [ + xcscheme_infos_testable.make_test_target("tt 9"), + xcscheme_infos_testable.make_test_target("tt 0"), + xcscheme_infos_testable.make_test_target("tt 1"), + ], + use_run_args_and_env = "0", + xcode_configuration = "Test", + ), + + # Expected + expected_info = struct( + args = [ + xcscheme_infos_testable.make_arg_env( + enabled = "1", + value = "a\nnew line", + ), + xcscheme_infos_testable.make_arg_env( + enabled = "0", + value = "b", + ), + ], + build_targets = [ + xcscheme_infos_testable.make_build_target("bt 2"), + xcscheme_infos_testable.make_build_target("bt 0"), + ], + diagnostics = xcscheme_infos_testable.make_diagnostics( + thread_sanitizer = "1", + ), + env = { + "VAR\n0": xcscheme_infos_testable.make_arg_env("value 0"), + "VAR 1": xcscheme_infos_testable.make_arg_env("value\n1"), + }, + env_include_defaults = "1", + test_targets = [ + xcscheme_infos_testable.make_test_target("tt 9"), + xcscheme_infos_testable.make_test_target("tt 0"), + xcscheme_infos_testable.make_test_target("tt 1"), + ], + use_run_args_and_env = "0", + xcode_configuration = "Test", + ), + ) + + # make_test_target + + _add_test( + name = "{}_make_test_target_minimal".format(name), + + # Inputs + info = xcscheme_infos_testable.make_test_target("an id"), + + # Expected + expected_info = struct( + enabled = "1", + id = "an id", + post_actions = [], + pre_actions = [], + ), + ) + + _add_test( + name = "{}_make_test_target_full".format(name), + + # Inputs + info = xcscheme_infos_testable.make_test_target( + enabled = "0", + id = "different id", + post_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = False, + order = "", + script_text = "script", + title = "title", + ), + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "1", + script_text = "s", + title = "t", + ), + ], + pre_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "2", + script_text = "ss", + title = "tt", + ), + xcscheme_infos_testable.make_pre_post_action( + for_build = False, + order = "11", + script_text = "sss", + title = "ttt", + ), + ], + ), + + # Expected + expected_info = struct( + enabled = "0", + id = "different id", + post_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = False, + order = "", + script_text = "script", + title = "title", + ), + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "1", + script_text = "s", + title = "t", + ), + ], + pre_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "2", + script_text = "ss", + title = "tt", + ), + xcscheme_infos_testable.make_pre_post_action( + for_build = False, + order = "11", + script_text = "sss", + title = "ttt", + ), + ], + ), + ) + + # Test suite + + native.test_suite( + name = name, + tests = test_names, + ) diff --git a/test/internal/xcschemes/infos_from_json_tests.bzl b/test/internal/xcschemes/infos_from_json_tests.bzl new file mode 100644 index 0000000000..7aabd16d95 --- /dev/null +++ b/test/internal/xcschemes/infos_from_json_tests.bzl @@ -0,0 +1,951 @@ +"""Tests for `xcschemes_infos.from_json`.""" + +load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest") +load(":utils.bzl", "json_to_xcscheme_infos") + +# buildifier: disable=bzl-visibility +load( + "//xcodeproj/internal/xcschemes:xcscheme_infos.bzl", + "xcscheme_infos", + "xcscheme_infos_testable", +) + +# Utility + +def _json_to_top_level_deps(json_str): + return { + target_environment: { + xcode_configuration: { + label: struct( + id = d["id"], + deps = d["deps"], + ) + for label, d in d.items() + } + for xcode_configuration, d in d.items() + } + for target_environment, d in json.decode(json_str).items() + } + +# Tests + +def _infos_from_json_test_impl(ctx): + env = unittest.begin(ctx) + + # Arrange + + expected_infos = json_to_xcscheme_infos(ctx.attr.expected_infos) + + # Act + + infos = xcscheme_infos.from_json( + ctx.attr.json_str, + default_xcode_configuration = ctx.attr.default_xcode_configuration, + top_level_deps = _json_to_top_level_deps(ctx.attr.top_level_deps), + ) + + # Assert + + asserts.equals( + env, + expected_infos, + infos, + "infos", + ) + + return unittest.end(env) + +infos_from_json_test = unittest.make( + impl = _infos_from_json_test_impl, + # @unsorted-dict-items + attrs = { + # Inputs + "default_xcode_configuration": attr.string(mandatory = True), + "json_str": attr.string(mandatory = True), + "top_level_deps": attr.string(mandatory = True), + + # Expected + "expected_infos": attr.string(mandatory = True), + }, +) + +def infos_from_json_test_suite(name): + """Test suite for `xcscheme_infos.from_json`. + + Args: + name: The base name to be used in things created by this macro. Also the + name of the test suite. + """ + test_names = [] + + # buildifier: disable=uninitialized + def _add_test( + *, + name, + + # Inputs + default_xcode_configuration, + json_str, + top_level_deps, + + # Expected + expected_infos): + test_names.append(name) + infos_from_json_test( + name = name, + + # Inputs + default_xcode_configuration = default_xcode_configuration, + json_str = json_str, + top_level_deps = json.encode(top_level_deps), + + # Expected + expected_infos = json.encode(expected_infos), + ) + + top_level_deps = { + "device": { + "custom": { + "bt 4 label": struct( + id = "device bt 4", + deps = {"lib label": "device bt 4 lib"}, + ), + "eh label": struct(id = "device eh", deps = {}), + "lt label": struct( + id = "device lt", + deps = {"lib label": "device lt lib"}, + ), + "tt 1 label": struct(id = "device tt 1", deps = {}), + "tt 2 label": struct( + id = "device tt 2", + deps = {"lib label": "device tt 2 lib"}, + ), + }, + }, + "simulator": { + "custom": { + "bt 1 label": struct(id = "sim bt 1", deps = {}), + "bt 2 label": struct( + id = "sim bt 2", + deps = {"lib label": "sim bt 2 lib"}, + ), + "bt 3 label": struct(id = "sim bt 3", deps = {}), + "bt 4 label": struct( + id = "sim bt 4", + deps = {"lib label": "sim bt 4 lib"}, + ), + "eh label": struct(id = "sim eh", deps = {}), + "lt label": struct( + id = "sim lt", + deps = {"lib label": "sim lt lib"}, + ), + "tt 1 label": struct(id = "sim tt 1", deps = {}), + "tt 2 label": struct( + id = "sim tt 2", + deps = {"lib label": "sim tt 2 lib"}, + ), + }, + }, + } + + full_args = [ + "-a\nnewline", + xcscheme_infos_testable.make_arg_env( + "B", + enabled = "0", + ), + ] + expected_full_args = [ + xcscheme_infos_testable.make_arg_env("-a\nnewline"), + xcscheme_infos_testable.make_arg_env( + "B", + enabled = "0", + ), + ] + + full_env = { + "A": "B", + "ENV\nVAR": xcscheme_infos_testable.make_arg_env( + "1\n2", + enabled = "0", + ), + } + expected_full_env = { + "A": xcscheme_infos_testable.make_arg_env("B"), + "ENV\nVAR": xcscheme_infos_testable.make_arg_env( + "1\n2", + enabled = "0", + ), + } + + full_build_targets = [ + struct( + include = False, + label = "bt 2 label", + library_targets = [ + struct( + label = "lib label", + post_actions = [], + pre_actions = [], + ), + ], + post_actions = ["won't be parsed"], + pre_actions = ["won't be parsed"], + target_environment = "", + ), + "bt 3 label", + struct( + include = True, + label = "bt 4 label", + library_targets = [ + struct( + label = "lib label", + post_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "1", + script_text = "lib\nscript", + title = "lib title", + ), + ], + pre_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = False, + order = "2", + script_text = "lib script", + title = "lib\ntitle", + ), + ], + ), + ], + post_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = False, + order = "", + script_text = "bt 4 script", + title = "bt 4 script title", + ), + ], + pre_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "2", + script_text = "bt 4 script", + title = "bt 4 script title", + ), + ], + target_environment = "device", + ), + ] + expected_full_build_targets = [ + xcscheme_infos_testable.make_build_target( + "sim bt 2 lib", + ), + xcscheme_infos_testable.make_build_target("sim bt 3"), + xcscheme_infos_testable.make_build_target( + id = "device bt 4", + post_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = False, + order = "", + script_text = "bt 4 script", + title = "bt 4 script title", + ), + ], + pre_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "2", + script_text = "bt 4 script", + title = "bt 4 script title", + ), + ], + ), + xcscheme_infos_testable.make_build_target( + id = "device bt 4 lib", + post_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "1", + script_text = "lib\nscript", + title = "lib title", + ), + ], + pre_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = False, + order = "2", + script_text = "lib script", + title = "lib\ntitle", + ), + ], + ), + ] + + full_launch_target = struct( + extension_host = "eh label", + label = "lt label", + library_targets = [ + struct( + label = "lib label", + post_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "", + script_text = "ssss", + title = "ttt", + ), + ], + pre_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "7", + script_text = "s", + title = "tttt", + ), + ], + ), + ], + post_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "7", + script_text = "s", + title = "t", + ), + ], + pre_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = False, + order = "42", + script_text = "sss", + title = "tt", + ), + ], + target_environment = "device", + working_directory = "wd", + ) + expected_full_launch_target = xcscheme_infos_testable.make_launch_target( + extension_host = "device eh", + id = "device lt", + post_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "7", + script_text = "s", + title = "t", + ), + ], + pre_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = False, + order = "42", + script_text = "sss", + title = "tt", + ), + ], + working_directory = "wd", + ) + expected_full_launch_build_targets = [ + xcscheme_infos_testable.make_build_target( + id = "device lt lib", + post_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "", + script_text = "ssss", + title = "ttt", + ), + ], + pre_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "7", + script_text = "s", + title = "tttt", + ), + ], + ), + ] + + # Empty + + _add_test( + name = "{}_empty".format(name), + + # Inputs + default_xcode_configuration = "AppStore", + json_str = json.encode([]), + top_level_deps = {}, + + # Expected + expected_infos = [], + ) + + # Minimal + + _add_test( + name = "{}_minimal".format(name), + + # Inputs + default_xcode_configuration = "AppStore", + json_str = json.encode([ + { + "name": "A scheme", + "profile": None, + "run": None, + "test": None, + }, + { + "name": "another Scheme", + "profile": None, + "run": None, + "test": None, + }, + ]), + top_level_deps = {}, + + # Expected + expected_infos = [ + xcscheme_infos_testable.make_scheme( + name = "A scheme", + ), + xcscheme_infos_testable.make_scheme( + name = "another Scheme", + ), + ], + ) + + # Profile + + _add_test( + name = "{}_profile_fallback_xcode_configuration".format(name), + + # Inputs + default_xcode_configuration = "custom", + json_str = json.encode([ + { + "name": "A scheme", + "profile": struct( + args = "inherit", + build_targets = [ + "bt 1 label", + struct( + include = True, + label = "bt 4 label", + library_targets = [ + struct( + label = "lib label", + post_actions = [], + pre_actions = [], + ), + ], + post_actions = [], + pre_actions = [], + target_environment = "", + ), + ], + env = "inherit", + env_include_defaults = "0", + launch_target = struct( + extension_host = "eh label", + label = "lt label", + library_targets = [ + struct( + label = "lib label", + post_actions = [], + pre_actions = [], + ), + ], + post_actions = [], + pre_actions = [], + target_environment = "", + working_directory = "", + ), + use_run_args_and_env = "1", + xcode_configuration = "", + ), + "run": None, + "test": None, + }, + ]), + top_level_deps = top_level_deps, + + # Expected + expected_infos = [ + xcscheme_infos_testable.make_scheme( + name = "A scheme", + profile = xcscheme_infos_testable.make_profile( + build_targets = [ + xcscheme_infos_testable.make_build_target("sim lt lib"), + xcscheme_infos_testable.make_build_target("sim bt 1"), + xcscheme_infos_testable.make_build_target("sim bt 4"), + xcscheme_infos_testable.make_build_target( + "sim bt 4 lib", + ), + ], + launch_target = xcscheme_infos_testable.make_launch_target( + extension_host = "sim eh", + id = "sim lt", + ), + ), + ), + ], + ) + + _add_test( + name = "{}_profile_full".format(name), + + # Inputs + default_xcode_configuration = "AppStore", + json_str = json.encode([ + { + "name": "A scheme", + "profile": struct( + args = full_args, + build_targets = full_build_targets, + env = full_env, + env_include_defaults = "1", + launch_target = full_launch_target, + use_run_args_and_env = "0", + xcode_configuration = "custom", + ), + "run": None, + "test": None, + }, + ]), + top_level_deps = top_level_deps, + + # Expected + expected_infos = [ + xcscheme_infos_testable.make_scheme( + name = "A scheme", + profile = xcscheme_infos_testable.make_profile( + args = expected_full_args, + build_targets = ( + expected_full_launch_build_targets + + expected_full_build_targets + ), + env = expected_full_env, + env_include_defaults = "1", + launch_target = expected_full_launch_target, + use_run_args_and_env = "0", + xcode_configuration = "custom", + ), + ), + ], + ) + + _add_test( + name = "{}_profile_same_as_run_none".format(name), + + # Inputs + default_xcode_configuration = "AppStore", + json_str = json.encode([ + { + "name": "A scheme", + "profile": "same_as_run", + "run": None, + "test": None, + }, + ]), + top_level_deps = {}, + + # Expected + expected_infos = [ + xcscheme_infos_testable.make_scheme( + name = "A scheme", + profile = xcscheme_infos_testable.make_profile(), + ), + ], + ) + + _add_test( + name = "{}_profile_same_as_run_not_none".format(name), + + # Inputs + default_xcode_configuration = "AppStore", + json_str = json.encode([ + { + "name": "A scheme", + "profile": "same_as_run", + "run": struct( + args = ["-v"], + build_targets = full_build_targets, + diagnostics = xcscheme_infos_testable.make_diagnostics( + address_sanitizer = "1", + ), + env = {"A": "B"}, + env_include_defaults = "1", + launch_target = full_launch_target, + xcode_configuration = "custom", + ), + "test": None, + }, + ]), + top_level_deps = top_level_deps, + + # Expected + expected_infos = [ + xcscheme_infos_testable.make_scheme( + name = "A scheme", + profile = xcscheme_infos_testable.make_profile( + build_targets = ( + expected_full_launch_build_targets + + expected_full_build_targets + ), + env_include_defaults = "0", + launch_target = expected_full_launch_target, + use_run_args_and_env = "1", + ), + run = xcscheme_infos_testable.make_run( + args = [xcscheme_infos_testable.make_arg_env("-v")], + build_targets = ( + expected_full_launch_build_targets + + expected_full_build_targets + ), + diagnostics = xcscheme_infos_testable.make_diagnostics( + address_sanitizer = "1", + ), + env = {"A": xcscheme_infos_testable.make_arg_env("B")}, + env_include_defaults = "1", + launch_target = expected_full_launch_target, + xcode_configuration = "custom", + ), + ), + ], + ) + + # Run + + _add_test( + name = "{}_run_fallback_xcode_configuration".format(name), + + # Inputs + default_xcode_configuration = "custom", + json_str = json.encode([ + { + "name": "A scheme", + "profile": None, + "run": struct( + args = "inherit", + build_targets = [ + "bt 1 label", + struct( + include = True, + label = "bt 4 label", + library_targets = [ + struct( + label = "lib label", + post_actions = [], + pre_actions = [], + ), + ], + post_actions = [], + pre_actions = [], + target_environment = "", + ), + ], + diagnostics = None, + env = "inherit", + env_include_defaults = "1", + launch_target = struct( + extension_host = "eh label", + label = "lt label", + library_targets = [ + struct( + label = "lib label", + post_actions = [], + pre_actions = [], + ), + ], + post_actions = [], + pre_actions = [], + target_environment = "", + working_directory = "", + ), + xcode_configuration = "", + ), + "test": None, + }, + ]), + top_level_deps = top_level_deps, + + # Expected + expected_infos = [ + xcscheme_infos_testable.make_scheme( + name = "A scheme", + run = xcscheme_infos_testable.make_run( + build_targets = [ + xcscheme_infos_testable.make_build_target("sim lt lib"), + xcscheme_infos_testable.make_build_target("sim bt 1"), + xcscheme_infos_testable.make_build_target("sim bt 4"), + xcscheme_infos_testable.make_build_target( + "sim bt 4 lib", + ), + ], + launch_target = xcscheme_infos_testable.make_launch_target( + extension_host = "sim eh", + id = "sim lt", + ), + ), + ), + ], + ) + + _add_test( + name = "{}_run_full".format(name), + + # Inputs + default_xcode_configuration = "AppStore", + json_str = json.encode([ + { + "name": "A scheme", + "profile": None, + "run": struct( + args = full_args, + build_targets = full_build_targets, + diagnostics = struct( + address_sanitizer = "1", + thread_sanitizer = "1", + undefined_behavior_sanitizer = "1", + ), + env = full_env, + env_include_defaults = "0", + launch_target = full_launch_target, + use_run_args_and_env = "0", + xcode_configuration = "custom", + ), + "test": None, + }, + ]), + top_level_deps = top_level_deps, + + # Expected + expected_infos = [ + xcscheme_infos_testable.make_scheme( + name = "A scheme", + run = xcscheme_infos_testable.make_run( + args = expected_full_args, + build_targets = ( + expected_full_launch_build_targets + + expected_full_build_targets + ), + diagnostics = xcscheme_infos_testable.make_diagnostics( + address_sanitizer = "1", + thread_sanitizer = "1", + undefined_behavior_sanitizer = "1", + ), + env = expected_full_env, + env_include_defaults = "0", + launch_target = expected_full_launch_target, + xcode_configuration = "custom", + ), + ), + ], + ) + + # Profile + + _add_test( + name = "{}_test_fallback_xcode_configuration".format(name), + + # Inputs + default_xcode_configuration = "custom", + json_str = json.encode([ + { + "name": "A scheme", + "profile": None, + "run": None, + "test": struct( + args = "inherit", + build_targets = [ + "bt 1 label", + struct( + include = True, + label = "bt 4 label", + library_targets = [ + struct( + label = "lib label", + post_actions = [], + pre_actions = [], + ), + ], + post_actions = [], + pre_actions = [], + target_environment = "", + ), + ], + diagnostics = None, + env = "inherit", + env_include_defaults = "0", + # FIXME: test_targets + test_targets = [], + use_run_args_and_env = "1", + xcode_configuration = "", + ), + }, + ]), + top_level_deps = top_level_deps, + + # Expected + expected_infos = [ + xcscheme_infos_testable.make_scheme( + name = "A scheme", + test = xcscheme_infos_testable.make_test( + build_targets = [ + xcscheme_infos_testable.make_build_target("sim bt 1"), + xcscheme_infos_testable.make_build_target("sim bt 4"), + xcscheme_infos_testable.make_build_target( + "sim bt 4 lib", + ), + ], + # FIXME: test_targets + test_targets = [], + ), + ), + ], + ) + + _add_test( + name = "{}_test_full".format(name), + + # Inputs + default_xcode_configuration = "AppStore", + json_str = json.encode([ + { + "name": "A scheme", + "profile": None, + "run": None, + "test": struct( + args = full_args, + build_targets = full_build_targets, + diagnostics = struct( + address_sanitizer = "1", + thread_sanitizer = "1", + undefined_behavior_sanitizer = "1", + ), + env = full_env, + env_include_defaults = "1", + test_targets = [ + "tt 1 label", + struct( + enabled = "0", + label = "tt 2 label", + library_targets = [ + struct( + label = "lib label", + post_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "144", + script_text = "lib\nscript", + title = "lib title", + ), + ], + pre_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = False, + order = "", + script_text = "lib script", + title = "lib\ntitle", + ), + ], + ), + ], + post_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = False, + order = "7", + script_text = "tt 2 script", + title = "tt 2\nscript title", + ), + ], + pre_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "1", + script_text = "tt\n2 script", + title = "tt 2 script title", + ), + ], + target_environment = "device", + ), + ], + use_run_args_and_env = "0", + xcode_configuration = "custom", + ), + }, + ]), + top_level_deps = top_level_deps, + + # Expected + expected_infos = [ + xcscheme_infos_testable.make_scheme( + name = "A scheme", + test = xcscheme_infos_testable.make_test( + args = expected_full_args, + build_targets = [ + xcscheme_infos_testable.make_build_target( + id = "device tt 2 lib", + post_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "144", + script_text = "lib\nscript", + title = "lib title", + ), + ], + pre_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = False, + order = "", + script_text = "lib script", + title = "lib\ntitle", + ), + ], + ), + ] + expected_full_build_targets, + diagnostics = xcscheme_infos_testable.make_diagnostics( + address_sanitizer = "1", + thread_sanitizer = "1", + undefined_behavior_sanitizer = "1", + ), + env = expected_full_env, + env_include_defaults = "1", + test_targets = [ + xcscheme_infos_testable.make_test_target("sim tt 1"), + xcscheme_infos_testable.make_test_target( + enabled = "0", + id = "device tt 2", + post_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = False, + order = "7", + script_text = "tt 2 script", + title = "tt 2\nscript title", + ), + ], + pre_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "1", + script_text = "tt\n2 script", + title = "tt 2 script title", + ), + ], + ), + ], + use_run_args_and_env = "0", + xcode_configuration = "custom", + ), + ), + ], + ) + + # Test suite + + native.test_suite( + name = name, + tests = test_names, + ) diff --git a/test/internal/xcschemes/utils.bzl b/test/internal/xcschemes/utils.bzl new file mode 100644 index 0000000000..921e213928 --- /dev/null +++ b/test/internal/xcschemes/utils.bzl @@ -0,0 +1,121 @@ +"""Utility test functions for xcshemes tests.""" + +def _dict_of_dicts_to_arg_env_infos(dict_of_dicts): + if dict_of_dicts == None: + return None + return { + k: struct( + enabled = d["enabled"], + value = d["value"], + ) + for k, d in dict_of_dicts.items() + } + +def _dict_to_diagnostics_info(d): + return struct( + address_sanitizer = d["address_sanitizer"], + thread_sanitizer = d["thread_sanitizer"], + undefined_behavior_sanitizer = d["undefined_behavior_sanitizer"], + ) + +def _dict_to_launch_target_info(d): + return struct( + extension_host = d["extension_host"], + id = d["id"], + post_actions = _dicts_to_pre_post_action_infos(d["post_actions"]), + pre_actions = _dicts_to_pre_post_action_infos(d["pre_actions"]), + working_directory = d["working_directory"], + ) + +def _dict_to_profile_info(d): + return struct( + args = _dicts_to_arg_env_infos(d["args"]), + build_targets = _dicts_to_build_target_infos(d["build_targets"]), + env = _dict_of_dicts_to_arg_env_infos(d["env"]), + env_include_defaults = d["env_include_defaults"], + launch_target = _dict_to_launch_target_info(d["launch_target"]), + use_run_args_and_env = d["use_run_args_and_env"], + xcode_configuration = d["xcode_configuration"], + ) + +def _dict_to_run_info(d): + return struct( + args = _dicts_to_arg_env_infos(d["args"]), + build_targets = _dicts_to_build_target_infos(d["build_targets"]), + diagnostics = _dict_to_diagnostics_info(d["diagnostics"]), + env = _dict_of_dicts_to_arg_env_infos(d["env"]), + env_include_defaults = d["env_include_defaults"], + launch_target = _dict_to_launch_target_info(d["launch_target"]), + xcode_configuration = d["xcode_configuration"], + ) + +def _dict_to_test_info(d): + return struct( + args = _dicts_to_arg_env_infos(d["args"]), + build_targets = _dicts_to_build_target_infos(d["build_targets"]), + diagnostics = _dict_to_diagnostics_info(d["diagnostics"]), + env = _dict_of_dicts_to_arg_env_infos(d["env"]), + env_include_defaults = d["env_include_defaults"], + test_targets = _dicts_to_test_target_infos(d["test_targets"]), + use_run_args_and_env = d["use_run_args_and_env"], + xcode_configuration = d["xcode_configuration"], + ) + +def _dict_to_xcscheme_info(d): + return struct( + name = d["name"], + profile = _dict_to_profile_info(d["profile"]), + test = _dict_to_test_info(d["test"]), + run = _dict_to_run_info(d["run"]), + ) + +def _dicts_to_build_target_infos(dicts): + return [ + struct( + id = d["id"], + post_actions = _dicts_to_pre_post_action_infos(d["post_actions"]), + pre_actions = _dicts_to_pre_post_action_infos(d["pre_actions"]), + ) + for d in dicts + ] + +def _dicts_to_arg_env_infos(dicts): + if dicts == None: + return None + return [ + struct( + enabled = d["enabled"], + value = d["value"], + ) + for d in dicts + ] + +def _dicts_to_pre_post_action_infos(dicts): + return [ + struct( + for_build = d["for_build"], + order = d["order"], + script_text = d["script_text"], + title = d["title"], + ) + for d in dicts + ] + +def _dicts_to_test_target_infos(dicts): + return [ + struct( + enabled = d["enabled"], + id = d["id"], + post_actions = _dicts_to_pre_post_action_infos(d["post_actions"]), + pre_actions = _dicts_to_pre_post_action_infos(d["pre_actions"]), + ) + for d in dicts + ] + +# API + +def json_to_xcscheme_infos(json_str): + return [ + _dict_to_xcscheme_info(xcscheme_info_dict) + for xcscheme_info_dict in json.decode(json_str) + ] diff --git a/test/internal/xcschemes/write_schemes_tests.bzl b/test/internal/xcschemes/write_schemes_tests.bzl new file mode 100644 index 0000000000..cbda7345da --- /dev/null +++ b/test/internal/xcschemes/write_schemes_tests.bzl @@ -0,0 +1,1651 @@ +"""Tests for `xcschemes_execution.write_schemes`.""" + +load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest") +load("//test:mock_actions.bzl", "mock_actions") +load(":utils.bzl", "json_to_xcscheme_infos") + +# buildifier: disable=bzl-visibility +load( + "//xcodeproj/internal/xcschemes:xcscheme_infos.bzl", + "xcscheme_infos_testable", +) + +# buildifier: disable=bzl-visibility +load( + "//xcodeproj/internal/xcschemes:xcschemes_execution.bzl", + "xcschemes_execution", +) + +# Utility + +_CUSTOM_SCHEMES_DECLARED_FILE = mock_actions.mock_file( + "a_generator_name_pbxproj_partials/custom_schemes_file", +) +_EXECUTION_ACTIONS_DECLARED_FILE = mock_actions.mock_file( + "a_generator_name_pbxproj_partials/execution_actions_file", +) +_OUTPUT_DECLARED_DIRECTORY = mock_actions.mock_file( + "a_generator_name_pbxproj_partials/xcschemes", +) +_TARGETS_ARGS_ENV_DECLARED_FILE = mock_actions.mock_file( + "a_generator_name_pbxproj_partials/targets_args_env_file", +) +_TRANSITIVE_PREVIEW_TARGETS_DECLARED_FILE = mock_actions.mock_file( + "a_generator_name_pbxproj_partials/transitive_preview_targets_file", +) +_XCSCHEMEMANAGEMENT_DECLARED_FILE = mock_actions.mock_file( + "a_generator_name_pbxproj_partials/xcschememanagement.plist", +) + +def _dict_to_xcode_target(d): + return struct( + id = d["id"], + platform = struct( + apple_platform = struct( + platform_type = d["apple_platform"], + ), + ), + product = struct( + type = d["product_type"], + ), + transitive_dependencies = depset(d["transitive_dependencies"]), + ) + +def _json_to_hosted_targets(json_str): + return [ + _hosted_target( + host = d["host"], + hosted = d["hosted"], + ) + for d in json.decode(json_str) + ] + +def _json_to_targets_env(json_str): + return { + id: [ + (k, v) + for k, v in env + ] + for id, env in json.decode(json_str).items() + } + +def _json_to_xcode_targets_by_id(json_str): + return { + id: _dict_to_xcode_target(xcode_target_dict) + for id, xcode_target_dict in json.decode(json_str).items() + } + +def _hosted_target(*, host, hosted): + return struct( + host = host, + hosted = hosted, + ) + +def _mock_xcode_target( + *, + id, + apple_platform, + product_type, + transitive_dependencies = []): + return struct( + id = id, + apple_platform = apple_platform, + product_type = product_type, + transitive_dependencies = transitive_dependencies, + ) + +# Test + +def _write_schemes_test_impl(ctx): + env = unittest.begin(ctx) + + # Arrange + + actions = mock_actions.create() + + expected_declared_directories = { + _OUTPUT_DECLARED_DIRECTORY: None, + } + expected_declared_files = { + _CUSTOM_SCHEMES_DECLARED_FILE: None, + _EXECUTION_ACTIONS_DECLARED_FILE: None, + _TARGETS_ARGS_ENV_DECLARED_FILE: None, + _XCSCHEMEMANAGEMENT_DECLARED_FILE: None, + } + expected_inputs = ctx.attr.consolidation_maps + [ + _CUSTOM_SCHEMES_DECLARED_FILE, + _EXECUTION_ACTIONS_DECLARED_FILE, + ctx.attr.extension_point_identifiers_file, + _TARGETS_ARGS_ENV_DECLARED_FILE, + ] + expected_outputs = [ + _OUTPUT_DECLARED_DIRECTORY, + _XCSCHEMEMANAGEMENT_DECLARED_FILE, + ] + + include_transitive_preview_targets = ( + ctx.attr.include_transitive_preview_targets + ) + if include_transitive_preview_targets: + expected_declared_files[_TRANSITIVE_PREVIEW_TARGETS_DECLARED_FILE] = ( + None + ) + expected_inputs.append(_TRANSITIVE_PREVIEW_TARGETS_DECLARED_FILE) + + # Act + + ( + output_directory, + xcschememanagement, + ) = xcschemes_execution.write_schemes( + actions = actions.mock, + autogeneration_mode = ctx.attr.autogeneration_mode, + colorize = ctx.attr.colorize, + consolidation_maps = ctx.attr.consolidation_maps, + default_xcode_configuration = ctx.attr.default_xcode_configuration, + extension_point_identifiers_file = ( + ctx.attr.extension_point_identifiers_file + ), + generator_name = "a_generator_name", + hosted_targets = depset( + _json_to_hosted_targets(ctx.attr.hosted_targets), + ), + include_transitive_preview_targets = include_transitive_preview_targets, + install_path = ctx.attr.install_path, + targets_args = ctx.attr.targets_args, + targets_env = _json_to_targets_env(ctx.attr.targets_env), + tool = None, + workspace_directory = ctx.attr.workspace_directory, + xcode_targets = _json_to_xcode_targets_by_id(ctx.attr.xcode_targets), + xcscheme_infos = json_to_xcscheme_infos(ctx.attr.xcscheme_infos), + ) + + # Assert + + asserts.equals( + env, + expected_declared_directories, + actions.declared_directories, + "actions.declare_directory", + ) + + asserts.equals( + env, + expected_declared_files, + actions.declared_files, + "actions.declare_file", + ) + + asserts.equals( + env, + ctx.attr.expected_writes, + actions.writes, + "actions.write", + ) + + asserts.equals( + env, + "@%s", + actions.args_objects[0].captured.use_param_file_args["use_param_file"], + "args[0].use_param_file", + ) + + asserts.equals( + env, + "multiline", + actions.args_objects[0].captured.set_param_file_format_args["format"], + "args[0].param_file_format", + ) + + asserts.equals( + env, + [actions.args_objects[0]], + actions.run_args["arguments"], + "actions.run.arguments", + ) + + asserts.equals( + env, + ctx.attr.expected_args, + actions.args_objects[0].captured.args, + "args[0] arguments", + ) + + asserts.equals( + env, + expected_inputs, + actions.run_args["inputs"], + "actions.run.inputs", + ) + + asserts.equals( + env, + expected_outputs, + actions.run_args["outputs"], + "actions.run.outputs", + ) + + asserts.equals( + env, + _OUTPUT_DECLARED_DIRECTORY, + output_directory, + "output_directory", + ) + asserts.equals( + env, + _XCSCHEMEMANAGEMENT_DECLARED_FILE, + xcschememanagement, + "xcschememanagement", + ) + + return unittest.end(env) + +write_schemes_test = unittest.make( + impl = _write_schemes_test_impl, + # @unsorted-dict-items + attrs = { + # Inputs + "autogeneration_mode": attr.string(mandatory = True), + "colorize": attr.bool(mandatory = True), + "consolidation_maps": attr.string_list(mandatory = True), + "default_xcode_configuration": attr.string(mandatory = True), + "extension_point_identifiers_file": attr.string(mandatory = True), + "hosted_targets": attr.string(mandatory = True), + "include_transitive_preview_targets": attr.bool(mandatory = True), + "install_path": attr.string(mandatory = True), + "targets_args": attr.string_list_dict(mandatory = True), + "targets_env": attr.string(mandatory = True), + "workspace_directory": attr.string(mandatory = True), + "xcode_targets": attr.string(mandatory = True), + "xcscheme_infos": attr.string(mandatory = True), + + # Expected + "expected_args": attr.string_list(mandatory = True), + "expected_writes": attr.string_dict(mandatory = True), + }, +) + +def write_schemes_test_suite(name): + """Test suite for `xcschemes_execution.write_schemes`. + + Args: + name: The base name to be used in things created by this macro. Also the + name of the test suite. + """ + test_names = [] + + def _add_test( + *, + name, + + # Inputs + autogeneration_mode, + colorize = False, + consolidation_maps, + default_xcode_configuration, + extension_point_identifiers_file, + hosted_targets = [], + include_transitive_preview_targets = False, + install_path, + targets_args = {}, + targets_env = {}, + workspace_directory, + xcode_targets, + xcscheme_infos = [], + + # Expected + expected_args, + expected_writes): + test_names.append(name) + write_schemes_test( + name = name, + + # Inputs + autogeneration_mode = autogeneration_mode, + colorize = colorize, + consolidation_maps = consolidation_maps, + default_xcode_configuration = default_xcode_configuration, + extension_point_identifiers_file = extension_point_identifiers_file, + hosted_targets = json.encode(hosted_targets), + include_transitive_preview_targets = ( + include_transitive_preview_targets + ), + install_path = install_path, + targets_args = targets_args, + targets_env = json.encode(targets_env), + workspace_directory = workspace_directory, + xcode_targets = json.encode(xcode_targets), + xcscheme_infos = json.encode(xcscheme_infos), + + # Expected + expected_args = expected_args, + expected_writes = { + file.path: content + for file, content in expected_writes.items() + }, + ) + + # @unsorted-dict-items + matches_xcode_targets = { + "IOS_APP": _mock_xcode_target( + id = "IOS_APP", + apple_platform = "IOS", + product_type = "a", + transitive_dependencies = [ + "IOS_FRAMEWORK", + "WATCHOS_FRAMEWORK", + "IOS_STATIC_LIBRARY", + ], + ), + "MACOS_APP": _mock_xcode_target( + id = "ID_1", + apple_platform = "MACOS", + product_type = "u", + transitive_dependencies = ["IOS_APP"], + ), + "IOS_FRAMEWORK": _mock_xcode_target( + id = "ISO_FRAMEWORK", + apple_platform = "IOS", + product_type = "f", + transitive_dependencies = ["IOS_STATIC_LIBRARY"], + ), + "WATCHOS_FRAMEWORK": _mock_xcode_target( + id = "WATCHOS_FRAMEWORK", + apple_platform = "WATCHOS", + product_type = "f", + ), + "IOS_STATIC_LIBRARY": _mock_xcode_target( + id = "IOS_STATIC_LIBRARY", + apple_platform = "IOS", + product_type = "l", + ), + } + + # @unsorted-dict-items + no_matches_xcode_targets = { + "IOS_APP": _mock_xcode_target( + id = "IOS_APP", + apple_platform = "IOS", + product_type = "a", + transitive_dependencies = [ + "WATCHOS_FRAMEWORK", + "IOS_STATIC_LIBRARY", + ], + ), + "MACOS_APP": _mock_xcode_target( + id = "ID_1", + apple_platform = "MACOS", + product_type = "u", + transitive_dependencies = ["IOS_APP"], + ), + "IOS_FRAMEWORK": _mock_xcode_target( + id = "ISO_FRAMEWORK", + apple_platform = "IOS", + product_type = "f", + transitive_dependencies = ["IOS_STATIC_LIBRARY"], + ), + "WATCHOS_FRAMEWORK": _mock_xcode_target( + id = "WATCHOS_FRAMEWORK", + apple_platform = "WATCHOS", + product_type = "f", + ), + "IOS_STATIC_LIBRARY": _mock_xcode_target( + id = "IOS_STATIC_LIBRARY", + apple_platform = "IOS", + product_type = "l", + ), + } + + no_custom_schemes_content = "\n".join([ + # schemeCount + "0", + ]) + "\n" + + no_target_args_and_env_content = "\n".join([ + # argsCount + "0", + # envCount + "0", + ]) + "\n" + + # Basic + + _add_test( + name = "{}_basic".format(name), + + # Inputs + autogeneration_mode = "none", + colorize = True, + consolidation_maps = [ + "some/consolidation_maps/0", + "some/consolidation_maps/1", + ], + default_xcode_configuration = "Debug", + extension_point_identifiers_file = "a/extension_point_identifiers_file", + install_path = "best/vision.xcodeproj", + workspace_directory = "/Users/TimApple/StarBoard", + xcode_targets = matches_xcode_targets, + + # Expected + expected_args = [ + # outputDirectory + _OUTPUT_DECLARED_DIRECTORY.path, + # schemeManagementOutputPath + _XCSCHEMEMANAGEMENT_DECLARED_FILE.path, + # autogenerationMode + "none", + # defaultXcodeConfiguration + "Debug", + # workspace + "/Users/TimApple/StarBoard", + # installPath + "best/vision.xcodeproj", + # extensionPointIdentifiersFile + "a/extension_point_identifiers_file", + # executionActionsFile + _EXECUTION_ACTIONS_DECLARED_FILE.path, + # targetsArgsEnvFile + _TARGETS_ARGS_ENV_DECLARED_FILE.path, + # customSchemesFile + _CUSTOM_SCHEMES_DECLARED_FILE.path, + # transitivePreviewTargetsFile + "", + # consolidationMaps + "--consolidation-maps", + "some/consolidation_maps/0", + "some/consolidation_maps/1", + # colorize + "--colorize", + ], + expected_writes = { + _CUSTOM_SCHEMES_DECLARED_FILE: no_custom_schemes_content, + _EXECUTION_ACTIONS_DECLARED_FILE: "\n", + _TARGETS_ARGS_ENV_DECLARED_FILE: no_target_args_and_env_content, + }, + ) + + # Custom schemes + + _add_test( + name = "{}_custom_schemes".format(name), + + # Inputs + autogeneration_mode = "auto", + consolidation_maps = [ + "some/consolidation_maps/0", + "some/consolidation_maps/1", + ], + default_xcode_configuration = "AppStore", + extension_point_identifiers_file = "a/extension_point_identifiers_file", + install_path = "best/vision.xcodeproj", + workspace_directory = "/Users/TimApple/StarBoard", + xcode_targets = matches_xcode_targets, + xcscheme_infos = [ + xcscheme_infos_testable.make_scheme(name = "Scheme 2"), + xcscheme_infos_testable.make_scheme( + name = "Scheme 1", + profile = xcscheme_infos_testable.make_profile( + args = [ + xcscheme_infos_testable.make_arg_env( + enabled = "1", + value = "simple value", + ), + xcscheme_infos_testable.make_arg_env( + enabled = "0", + value = "value\nwith\nnewlines", + ), + ], + build_targets = [ + xcscheme_infos_testable.make_build_target( + id = "profile bt", + post_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = False, + order = "2", + script_text = "profile bt post profile", + title = "profile bt post profile title", + ), + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "3", + script_text = "profile\nbt post build", + title = "profile bt post build title", + ), + ], + pre_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = False, + order = "8", + script_text = "profile bt pre profile", + title = "profile bt pre profile title", + ), + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "4", + script_text = "profile bt pre build", + title = "profile bt\npre build title", + ), + ], + ), + ], + env = { + "B": xcscheme_infos_testable.make_arg_env( + enabled = "1", + value = "a", + ), + }, + env_include_defaults = "1", + launch_target = xcscheme_infos_testable.make_launch_target( + extension_host = "profile extension host id", + id = "profile launch id", + post_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = False, + order = "", + script_text = "profile launch post profile", + title = "profile launch post profile title", + ), + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "2", + script_text = "profile\nlaunch post build", + title = "profile launch post build title", + ), + ], + pre_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = False, + order = "1", + script_text = "profile launch pre profile", + title = "profile launch pre profile title", + ), + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "", + script_text = "profile launch pre build", + title = "profile launch\npre build title", + ), + ], + working_directory = "profile working dir", + ), + use_run_args_and_env = "0", + xcode_configuration = "Profile", + ), + run = xcscheme_infos_testable.make_run( + args = [ + xcscheme_infos_testable.make_arg_env( + enabled = "0", + value = "a", + ), + xcscheme_infos_testable.make_arg_env( + enabled = "1", + value = "bb", + ), + ], + build_targets = [ + xcscheme_infos_testable.make_build_target( + id = "run bt", + post_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = False, + order = "9", + script_text = "run bt post run", + title = "run bt post run title", + ), + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "11", + script_text = "run\nbt post build", + title = "run bt post build title", + ), + ], + pre_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = False, + order = "10", + script_text = "run bt pre run", + title = "run bt pre run title", + ), + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "100", + script_text = "run bt pre build", + title = "run bt\npre build title", + ), + ], + ), + ], + diagnostics = xcscheme_infos_testable.make_diagnostics( + address_sanitizer = "1", + thread_sanitizer = "1", + undefined_behavior_sanitizer = "1", + ), + env = { + "A": xcscheme_infos_testable.make_arg_env( + enabled = "0", + value = "value with spaces", + ), + "VAR WITH SPACES": xcscheme_infos_testable.make_arg_env( + enabled = "1", + value = "value\nwith\nnewlines", + ), + }, + env_include_defaults = "0", + launch_target = xcscheme_infos_testable.make_launch_target( + extension_host = "run extension host id", + id = "run launch id", + post_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = False, + order = "", + script_text = "run launch post run", + title = "run launch post run title", + ), + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "2", + script_text = "run\nlaunch post build", + title = "run launch post build title", + ), + ], + pre_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = False, + order = "1", + script_text = "run launch pre run", + title = "run launch pre run title", + ), + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "", + script_text = "run launch pre build", + title = "run launch\npre build title", + ), + ], + working_directory = "run working dir", + ), + xcode_configuration = "Run", + ), + test = xcscheme_infos_testable.make_test( + args = [ + xcscheme_infos_testable.make_arg_env( + enabled = "0", + value = "-v", + ), + ], + build_targets = [ + xcscheme_infos_testable.make_build_target( + id = "test bt 2", + ), + xcscheme_infos_testable.make_build_target( + id = "test bt 1", + post_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = False, + order = "", + script_text = "test bt 1 post test", + title = "test bt 1 post test title", + ), + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "2", + script_text = "test bt 1 post build", + title = "test bt 1 post build title", + ), + ], + pre_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = False, + order = "6", + script_text = "test bt 1 pre test", + title = "test bt 1 pre test title", + ), + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "7", + script_text = "test bt 1 pre build", + title = "test bt 1 pre build title", + ), + ], + ), + ], + diagnostics = xcscheme_infos_testable.make_diagnostics( + address_sanitizer = "1", + thread_sanitizer = "1", + undefined_behavior_sanitizer = "1", + ), + env = { + "VAR\nWITH\nNEWLINES": xcscheme_infos_testable.make_arg_env( + enabled = "0", + value = "simple", + ), + }, + env_include_defaults = "1", + test_targets = [ + xcscheme_infos_testable.make_test_target( + enabled = "0", + id = "test tt 1", + post_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "", + script_text = "test tt 1 post\nbuild", + title = "test tt 1 post build title", + ), + xcscheme_infos_testable.make_pre_post_action( + for_build = False, + order = "42", + script_text = "test tt 1 post test", + title = "test tt 1 post\ntest title", + ), + ], + pre_actions = [ + xcscheme_infos_testable.make_pre_post_action( + for_build = False, + order = "1", + script_text = "test tt 1 pre test", + title = "test tt 1 pre test title", + ), + xcscheme_infos_testable.make_pre_post_action( + for_build = True, + order = "", + script_text = "test tt 1 pre build", + title = "test tt 1 pre build title", + ), + ], + ), + xcscheme_infos_testable.make_test_target( + enabled = "1", + id = "test tt 2", + ), + ], + use_run_args_and_env = "0", + xcode_configuration = "Test", + ), + ), + ], + + # Expected + expected_args = [ + # outputDirectory + _OUTPUT_DECLARED_DIRECTORY.path, + # schemeManagementOutputPath + _XCSCHEMEMANAGEMENT_DECLARED_FILE.path, + # autogenerationMode + "auto", + # defaultXcodeConfiguration + "AppStore", + # workspace + "/Users/TimApple/StarBoard", + # installPath + "best/vision.xcodeproj", + # extensionPointIdentifiersFile + "a/extension_point_identifiers_file", + # executionActionsFile + _EXECUTION_ACTIONS_DECLARED_FILE.path, + # targetsArgsEnvFile + _TARGETS_ARGS_ENV_DECLARED_FILE.path, + # customSchemesFile + _CUSTOM_SCHEMES_DECLARED_FILE.path, + # transitivePreviewTargetsFile + "", + # consolidationMaps + "--consolidation-maps", + "some/consolidation_maps/0", + "some/consolidation_maps/1", + ], + expected_writes = { + _CUSTOM_SCHEMES_DECLARED_FILE: "\n".join([ + # schemeCount + "2", + # - name + "Scheme 2", + # - test - testTargetCount + "0", + # - test - buildTargets count + "0", + # - test - commandLineArguments count + "-1", + # - test - environmentVariables count + "-1", + # - test - environmentVariablesIncludeDefaults + "0", + # - test - useRunArgsAndEnv + "1", + # - test - enableAddressSanitizer + "0", + # - test - enableThreadSanitizer + "0", + # - test - enableUBSanitizer + "0", + # - test - xcodeConfiguration + "", + # - run - buildTargets count + "0", + # - run - commandLineArguments count + "-1", + # - run - environmentVariables count + "-1", + # - run - environmentVariablesIncludeDefaults + "1", + # - run - enableAddressSanitizer + "0", + # - run - enableThreadSanitizer + "0", + # - run - enableUBSanitizer + "0", + # - run - xcodeConfiguration + "", + # - run - launchTarget - id + "", + # - run - launchTarget - extensionHostID + "", + # - run - customWorkingDirectory + "", + # - profile - buildTargets count + "0", + # - profile - commandLineArguments count + "-1", + # - profile - environmentVariables count + "-1", + # - profile - environmentVariablesIncludeDefaults + "0", + # - profile - useRunArgsAndEnv + "1", + # - profile - xcodeConfiguration + "", + # - profile - launchTarget - id + "", + # - profile - launchTarget - extensionHostID + "", + # - profile - customWorkingDirectory + "", + + # - name + "Scheme 1", + # - test - testTargetCount + "2", + # - test - testTargets - id + "test tt 1", + # - test - testTargets - enabled + "0", + # - test - testTargets - id + "test tt 2", + # - test - testTargets - enabled + "1", + # - test - buildTargets count + "2", + # - test - buildTargets id + "test bt 2", + # - test - buildTargets id + "test bt 1", + # - test - commandLineArguments count + "1", + # - test - commandLineArguments - value + "-v", + # - test - commandLineArguments - enabled + "0", + # - test - environmentVariables count + "1", + # - test - environmentVariables - key + "VAR\0WITH\0NEWLINES", + # - test - environmentVariables - value + "simple", + # - test - environmentVariables - enabled + "0", + # - test - environmentVariablesIncludeDefaults + "1", + # - test - useRunArgsAndEnv + "0", + # - test - enableAddressSanitizer + "1", + # - test - enableThreadSanitizer + "1", + # - test - enableUBSanitizer + "1", + # - test - xcodeConfiguration + "Test", + # - run - buildTargets count + "1", + # - run - buildTargets id + "run bt", + # - run - commandLineArguments count + "2", + # - run - commandLineArguments - value + "a", + # - run - commandLineArguments - enabled + "0", + # - run - commandLineArguments - value + "bb", + # - run - commandLineArguments - enabled + "1", + # - run - environmentVariables count + "2", + # - run - environmentVariables - key + "A", + # - run - environmentVariables - value + "value with spaces", + # - run - environmentVariables - enabled + "0", + # - run - environmentVariables - key + "VAR WITH SPACES", + # - run - environmentVariables - value + "value\0with\0newlines", + # - run - environmentVariables - enabled + "1", + # - run - environmentVariablesIncludeDefaults + "0", + # - run - enableAddressSanitizer + "1", + # - run - enableThreadSanitizer + "1", + # - run - enableUBSanitizer + "1", + # - run - xcodeConfiguration + "Run", + # - run - launchTarget - id + "run launch id", + # - run - launchTarget - extensionHostID + "run extension host id", + # - run - customWorkingDirectory + "run working dir", + # - profile - buildTargets count + "1", + # - profile - buildTargets id + "profile bt", + # - profile - commandLineArguments count + "2", + # - profile - commandLineArguments - value + "simple value", + # - profile - commandLineArguments - enabled + "1", + # - profile - commandLineArguments - value + "value\0with\0newlines", + # - profile - commandLineArguments - enabled + "0", + # - profile - environmentVariables count + "1", + # - profile - environmentVariables - key + "B", + # - profile - environmentVariables - value + "a", + # - profile - environmentVariables - enabled + "1", + # - profile - environmentVariablesIncludeDefaults + "1", + # - profile - useRunArgsAndEnv + "0", + # - profile - xcodeConfiguration + "Profile", + # - profile - launchTarget - id + "profile launch id", + # - profile - launchTarget - extensionHostID + "profile extension host id", + # - profile - customWorkingDirectory + "profile working dir", + ]) + "\n", + _EXECUTION_ACTIONS_DECLARED_FILE: "\n".join([ + # schemeName + "Scheme 1", + # action + "test", + # isPreAction + "1", + # title + "test tt 1 pre test title", + # scriptText + "test tt 1 pre test", + # id + "test tt 1", + # order + "1", + + # schemeName + "Scheme 1", + # action + "build", + # isPreAction + "1", + # title + "test tt 1 pre build title", + # scriptText + "test tt 1 pre build", + # id + "test tt 1", + # order + "", + + # schemeName + "Scheme 1", + # action + "build", + # isPreAction + "0", + # title + "test tt 1 post build title", + # scriptText + "test tt 1 post\0build", + # id + "test tt 1", + # order + "", + + # schemeName + "Scheme 1", + # action + "test", + # isPreAction + "0", + # title + "test tt 1 post\0test title", + # scriptText + "test tt 1 post test", + # id + "test tt 1", + # order + "42", + + # schemeName + "Scheme 1", + # action + "test", + # isPreAction + "1", + # title + "test bt 1 pre test title", + # scriptText + "test bt 1 pre test", + # id + "test bt 1", + # order + "6", + + # schemeName + "Scheme 1", + # action + "build", + # isPreAction + "1", + # title + "test bt 1 pre build title", + # scriptText + "test bt 1 pre build", + # id + "test bt 1", + # order + "7", + + # schemeName + "Scheme 1", + # action + "test", + # isPreAction + "0", + # title + "test bt 1 post test title", + # scriptText + "test bt 1 post test", + # id + "test bt 1", + # order + "", + + # schemeName + "Scheme 1", + # action + "build", + # isPreAction + "0", + # title + "test bt 1 post build title", + # scriptText + "test bt 1 post build", + # id + "test bt 1", + # order + "2", + + # schemeName + "Scheme 1", + # action + "run", + # isPreAction + "1", + # title + "run bt pre run title", + # scriptText + "run bt pre run", + # id + "run bt", + # order + "10", + + # schemeName + "Scheme 1", + # action + "build", + # isPreAction + "1", + # title + "run bt\0pre build title", + # scriptText + "run bt pre build", + # id + "run bt", + # order + "100", + + # schemeName + "Scheme 1", + # action + "run", + # isPreAction + "0", + # title + "run bt post run title", + # scriptText + "run bt post run", + # id + "run bt", + # order + "9", + + # schemeName + "Scheme 1", + # action + "build", + # isPreAction + "0", + # title + "run bt post build title", + # scriptText + "run\0bt post build", + # id + "run bt", + # order + "11", + + # schemeName + "Scheme 1", + # action + "run", + # isPreAction + "1", + # title + "run launch pre run title", + # scriptText + "run launch pre run", + # id + "run launch id", + # order + "1", + + # schemeName + "Scheme 1", + # action + "build", + # isPreAction + "1", + # title + "run launch\0pre build title", + # scriptText + "run launch pre build", + # id + "run launch id", + # order + "", + + # schemeName + "Scheme 1", + # action + "run", + # isPreAction + "0", + # title + "run launch post run title", + # scriptText + "run launch post run", + # id + "run launch id", + # order + "", + + # schemeName + "Scheme 1", + # action + "build", + # isPreAction + "0", + # title + "run launch post build title", + # scriptText + "run\0launch post build", + # id + "run launch id", + # order + "2", + + # schemeName + "Scheme 1", + # action + "profile", + # isPreAction + "1", + # title + "profile bt pre profile title", + # scriptText + "profile bt pre profile", + # id + "profile bt", + # order + "8", + + # schemeName + "Scheme 1", + # action + "build", + # isPreAction + "1", + # title + "profile bt\0pre build title", + # scriptText + "profile bt pre build", + # id + "profile bt", + # order + "4", + + # schemeName + "Scheme 1", + # action + "profile", + # isPreAction + "0", + # title + "profile bt post profile title", + # scriptText + "profile bt post profile", + # id + "profile bt", + # order + "2", + + # schemeName + "Scheme 1", + # action + "build", + # isPreAction + "0", + # title + "profile bt post build title", + # scriptText + "profile\0bt post build", + # id + "profile bt", + # order + "3", + + # schemeName + "Scheme 1", + # action + "profile", + # isPreAction + "1", + # title + "profile launch pre profile title", + # scriptText + "profile launch pre profile", + # id + "profile launch id", + # order + "1", + + # schemeName + "Scheme 1", + # action + "build", + # isPreAction + "1", + # title + "profile launch\0pre build title", + # scriptText + "profile launch pre build", + # id + "profile launch id", + # order + "", + + # schemeName + "Scheme 1", + # action + "profile", + # isPreAction + "0", + # title + "profile launch post profile title", + # scriptText + "profile launch post profile", + # id + "profile launch id", + # order + "", + + # schemeName + "Scheme 1", + # action + "build", + # isPreAction + "0", + # title + "profile launch post build title", + # scriptText + "profile\0launch post build", + # id + "profile launch id", + # order + "2", + ]) + "\n", + _TARGETS_ARGS_ENV_DECLARED_FILE: no_target_args_and_env_content, + }, + ) + + # hosted_targets + + _add_test( + name = "{}_hosted_targets".format(name), + + # Inputs + autogeneration_mode = "auto", + consolidation_maps = [ + "some/consolidation_maps/0", + "some/consolidation_maps/1", + ], + default_xcode_configuration = "AppStore", + extension_point_identifiers_file = "a/extension_point_identifiers_file", + hosted_targets = [ + _hosted_target( + host = "IOS_APP_2", + hosted = "IOS_APP_EXTENSION_1", + ), + _hosted_target( + host = "IOS_APP_1", + hosted = "IOS_APP_EXTENSION_1", + ), + _hosted_target( + host = "IOS_APP_2", + hosted = "IOS_APP_EXTENSION_2", + ), + ], + install_path = "best/vision.xcodeproj", + workspace_directory = "/Users/TimApple/StarBoard", + xcode_targets = matches_xcode_targets, + + # Expected + expected_args = [ + # outputDirectory + _OUTPUT_DECLARED_DIRECTORY.path, + # schemeManagementOutputPath + _XCSCHEMEMANAGEMENT_DECLARED_FILE.path, + # autogenerationMode + "auto", + # defaultXcodeConfiguration + "AppStore", + # workspace + "/Users/TimApple/StarBoard", + # installPath + "best/vision.xcodeproj", + # extensionPointIdentifiersFile + "a/extension_point_identifiers_file", + # executionActionsFile + _EXECUTION_ACTIONS_DECLARED_FILE.path, + # targetsArgsEnvFile + _TARGETS_ARGS_ENV_DECLARED_FILE.path, + # customSchemesFile + _CUSTOM_SCHEMES_DECLARED_FILE.path, + # transitivePreviewTargetsFile + "", + # consolidationMaps + "--consolidation-maps", + "some/consolidation_maps/0", + "some/consolidation_maps/1", + # targetAndExtensionHosts + "--target-and-extension-hosts", + "IOS_APP_EXTENSION_1", + "IOS_APP_2", + "IOS_APP_EXTENSION_1", + "IOS_APP_1", + "IOS_APP_EXTENSION_2", + "IOS_APP_2", + ], + expected_writes = { + _CUSTOM_SCHEMES_DECLARED_FILE: no_custom_schemes_content, + _EXECUTION_ACTIONS_DECLARED_FILE: "\n", + _TARGETS_ARGS_ENV_DECLARED_FILE: no_target_args_and_env_content, + }, + ) + + # include_transitive_preview_targets + + _add_test( + name = "{}_include_transitive_preview_targets_no_matches".format(name), + + # Inputs + autogeneration_mode = "auto", + consolidation_maps = [ + "some/consolidation_maps/0", + "some/consolidation_maps/1", + ], + default_xcode_configuration = "AppStore", + extension_point_identifiers_file = "a/extension_point_identifiers_file", + include_transitive_preview_targets = True, + install_path = "best/vision.xcodeproj", + workspace_directory = "/Users/TimApple/StarBoard", + xcode_targets = no_matches_xcode_targets, + + # Expected + expected_args = [ + # outputDirectory + _OUTPUT_DECLARED_DIRECTORY.path, + # schemeManagementOutputPath + _XCSCHEMEMANAGEMENT_DECLARED_FILE.path, + # autogenerationMode + "auto", + # defaultXcodeConfiguration + "AppStore", + # workspace + "/Users/TimApple/StarBoard", + # installPath + "best/vision.xcodeproj", + # extensionPointIdentifiersFile + "a/extension_point_identifiers_file", + # executionActionsFile + _EXECUTION_ACTIONS_DECLARED_FILE.path, + # targetsArgsEnvFile + _TARGETS_ARGS_ENV_DECLARED_FILE.path, + # customSchemesFile + _CUSTOM_SCHEMES_DECLARED_FILE.path, + # transitivePreviewTargetsFile + _TRANSITIVE_PREVIEW_TARGETS_DECLARED_FILE.path, + # consolidationMaps + "--consolidation-maps", + "some/consolidation_maps/0", + "some/consolidation_maps/1", + ], + expected_writes = { + _CUSTOM_SCHEMES_DECLARED_FILE: no_custom_schemes_content, + _EXECUTION_ACTIONS_DECLARED_FILE: "\n", + _TARGETS_ARGS_ENV_DECLARED_FILE: no_target_args_and_env_content, + _TRANSITIVE_PREVIEW_TARGETS_DECLARED_FILE: "\n", + }, + ) + + _add_test( + name = "{}_include_transitive_preview_targets_matches".format(name), + + # Inputs + autogeneration_mode = "auto", + consolidation_maps = [ + "some/consolidation_maps/0", + "some/consolidation_maps/1", + ], + default_xcode_configuration = "AppStore", + extension_point_identifiers_file = "a/extension_point_identifiers_file", + include_transitive_preview_targets = True, + install_path = "best/vision.xcodeproj", + workspace_directory = "/Users/TimApple/StarBoard", + xcode_targets = matches_xcode_targets, + + # Expected + expected_args = [ + # outputDirectory + _OUTPUT_DECLARED_DIRECTORY.path, + # schemeManagementOutputPath + _XCSCHEMEMANAGEMENT_DECLARED_FILE.path, + # autogenerationMode + "auto", + # defaultXcodeConfiguration + "AppStore", + # workspace + "/Users/TimApple/StarBoard", + # installPath + "best/vision.xcodeproj", + # extensionPointIdentifiersFile + "a/extension_point_identifiers_file", + # executionActionsFile + _EXECUTION_ACTIONS_DECLARED_FILE.path, + # targetsArgsEnvFile + _TARGETS_ARGS_ENV_DECLARED_FILE.path, + # customSchemesFile + _CUSTOM_SCHEMES_DECLARED_FILE.path, + # transitivePreviewTargetsFile + _TRANSITIVE_PREVIEW_TARGETS_DECLARED_FILE.path, + # consolidationMaps + "--consolidation-maps", + "some/consolidation_maps/0", + "some/consolidation_maps/1", + ], + expected_writes = { + _CUSTOM_SCHEMES_DECLARED_FILE: no_custom_schemes_content, + _EXECUTION_ACTIONS_DECLARED_FILE: "\n", + _TARGETS_ARGS_ENV_DECLARED_FILE: no_target_args_and_env_content, + _TRANSITIVE_PREVIEW_TARGETS_DECLARED_FILE: "\n".join([ + # id + "IOS_APP", + # buildableReferences + # - count + "1", + # - id + "IOS_FRAMEWORK", + ]) + "\n", + }, + ) + + # Target args and env + + _add_test( + name = "{}_target_args_and_env".format(name), + + # Inputs + autogeneration_mode = "auto", + consolidation_maps = [ + "some/consolidation_maps/0", + "some/consolidation_maps/1", + ], + default_xcode_configuration = "AppStore", + extension_point_identifiers_file = "a/extension_point_identifiers_file", + install_path = "best/vision.xcodeproj", + targets_args = { + "IOS_APP": [ + "--ios_app_arg_0", + "--ios_app_arg with spaces", + ], + "MACOS_APP": [ + "--macos_app_arg\nwith\nnewlines", + ], + }, + targets_env = { + "IOS_TEST": [ + ("VAR1", "VALUE WITH SPACES"), + ("VAR WITH SPACES", "value\nwith\nnewlines"), + ("VAR\nWITH\nNEWLINES", "simple_value"), + ], + }, + workspace_directory = "/Users/TimApple/StarBoard", + xcode_targets = matches_xcode_targets, + + # Expected + expected_args = [ + # outputDirectory + _OUTPUT_DECLARED_DIRECTORY.path, + # schemeManagementOutputPath + _XCSCHEMEMANAGEMENT_DECLARED_FILE.path, + # autogenerationMode + "auto", + # defaultXcodeConfiguration + "AppStore", + # workspace + "/Users/TimApple/StarBoard", + # installPath + "best/vision.xcodeproj", + # extensionPointIdentifiersFile + "a/extension_point_identifiers_file", + # executionActionsFile + _EXECUTION_ACTIONS_DECLARED_FILE.path, + # targetsArgsEnvFile + _TARGETS_ARGS_ENV_DECLARED_FILE.path, + # customSchemesFile + _CUSTOM_SCHEMES_DECLARED_FILE.path, + # transitivePreviewTargetsFile + "", + # consolidationMaps + "--consolidation-maps", + "some/consolidation_maps/0", + "some/consolidation_maps/1", + ], + expected_writes = { + _CUSTOM_SCHEMES_DECLARED_FILE: no_custom_schemes_content, + _EXECUTION_ACTIONS_DECLARED_FILE: "\n", + _TARGETS_ARGS_ENV_DECLARED_FILE: "\n".join([ + # argsCount + "2", + # - id + "IOS_APP", + # - targetArgsCount + "2", + # - - values + "--ios_app_arg_0", + "--ios_app_arg with spaces", + # - id + "MACOS_APP", + # - targetArgsCount + "1", + # - - values + "--macos_app_arg\0with\0newlines", + # envCount + "1", + # - id + "IOS_TEST", + # - targetEnvCount + "3", + # - - key + "VAR1", + # - - value + "VALUE WITH SPACES", + # - - key + "VAR WITH SPACES", + # - - value + "value\0with\0newlines", + # - - key + "VAR\0WITH\0NEWLINES", + # - - value + "simple_value", + ]) + "\n", + }, + ) + + # Test suite + + native.test_suite( + name = name, + tests = test_names, + ) diff --git a/xcodeproj/internal/xcschemes/BUILD b/xcodeproj/internal/xcschemes/BUILD new file mode 100644 index 0000000000..3944e4f139 --- /dev/null +++ b/xcodeproj/internal/xcschemes/BUILD @@ -0,0 +1,21 @@ +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") + +bzl_library( + name = "xcschemes", + srcs = glob(["*.bzl"]), + visibility = ["//:__subpackages__"], +) + +# Release + +filegroup( + name = "release_files", + srcs = glob( + ["**"], + exclude = [ + "**/.*", + ], + ), + tags = ["manual"], + visibility = ["//:__subpackages__"], +) diff --git a/xcodeproj/internal/xcschemes/xcscheme_infos.bzl b/xcodeproj/internal/xcschemes/xcscheme_infos.bzl new file mode 100644 index 0000000000..aab6d270b6 --- /dev/null +++ b/xcodeproj/internal/xcschemes/xcscheme_infos.bzl @@ -0,0 +1,684 @@ +"""Module for parsing macro custom Xcode schemes json in the analysis phase.""" + +load( + "//xcodeproj/internal:memory_efficiency.bzl", + "EMPTY_STRING", + "FALSE_ARG", + "TRUE_ARG", +) + +# Constructors + +def _make_arg_env(value, *, enabled = TRUE_ARG): + return struct( + enabled = enabled, + value = value, + ) + +def _make_build_target(id, *, post_actions = [], pre_actions = []): + return struct( + id = id, + post_actions = post_actions, + pre_actions = pre_actions, + ) + +def _make_diagnostics( + *, + address_sanitizer = FALSE_ARG, + thread_sanitizer = FALSE_ARG, + undefined_behavior_sanitizer = FALSE_ARG): + return struct( + address_sanitizer = address_sanitizer, + thread_sanitizer = thread_sanitizer, + undefined_behavior_sanitizer = undefined_behavior_sanitizer, + ) + +def _make_launch_target( + id = EMPTY_STRING, + *, + extension_host = EMPTY_STRING, + post_actions = [], + pre_actions = [], + working_directory = EMPTY_STRING): + return struct( + extension_host = extension_host, + id = id, + post_actions = post_actions, + pre_actions = pre_actions, + working_directory = working_directory, + ) + +def _make_pre_post_action( + *, + for_build, + order, + script_text, + title): + return struct( + for_build = for_build, + order = order, + script_text = script_text, + title = title, + ) + +def _make_profile( + *, + args = None, + build_targets = [], + env = None, + env_include_defaults = FALSE_ARG, + launch_target = _make_launch_target(), + use_run_args_and_env = TRUE_ARG, + xcode_configuration = EMPTY_STRING): + return struct( + args = args, + build_targets = build_targets, + env = env, + env_include_defaults = env_include_defaults, + launch_target = launch_target, + use_run_args_and_env = use_run_args_and_env, + xcode_configuration = xcode_configuration, + ) + +def _make_run( + *, + args = None, + build_targets = [], + diagnostics = _make_diagnostics(), + env = None, + env_include_defaults = TRUE_ARG, + launch_target = _make_launch_target(), + xcode_configuration = EMPTY_STRING): + return struct( + args = args, + build_targets = build_targets, + diagnostics = diagnostics, + env = env, + env_include_defaults = env_include_defaults, + launch_target = launch_target, + xcode_configuration = xcode_configuration, + ) + +def _make_test( + *, + args = None, + build_targets = [], + diagnostics = _make_diagnostics(), + env = None, + env_include_defaults = FALSE_ARG, + test_targets = [], + use_run_args_and_env = TRUE_ARG, + xcode_configuration = EMPTY_STRING): + return struct( + args = args, + build_targets = build_targets, + diagnostics = diagnostics, + env = env, + env_include_defaults = env_include_defaults, + test_targets = test_targets, + use_run_args_and_env = use_run_args_and_env, + xcode_configuration = xcode_configuration, + ) + +def _make_test_target( + id, + *, + enabled = TRUE_ARG, + post_actions = [], + pre_actions = []): + return struct( + enabled = enabled, + id = id, + post_actions = post_actions, + pre_actions = pre_actions, + ) + +def _make_scheme( + name, + *, + profile = _make_profile(), + run = _make_run(), + test = _make_test()): + return struct( + name = name, + profile = profile, + run = run, + test = test, + ) + +# JSON + +def _arg_env_info_from_dict(arg_env): + if type(arg_env) == "string": + return _make_arg_env( + value = arg_env, + ) + + return _make_arg_env( + enabled = arg_env["enabled"], + value = arg_env["value"], + ) + +def _arg_infos_from_list(args): + if args == "inherit": + return None + return [_arg_env_info_from_dict(arg) for arg in args] + +def _build_target_infos_from_dict( + build_target, + *, + scheme_name, + top_level_deps, + xcode_configuration): + if type(build_target) == "string": + return [ + _make_build_target( + id = _get_top_level_id( + scheme_name = scheme_name, + target_environment = None, + top_level_deps = top_level_deps, + top_level_label = build_target, + xcode_configuration = xcode_configuration, + ), + ), + ] + + target_ids = _get_target_ids( + scheme_name = scheme_name, + target_environment = build_target["target_environment"], + top_level_deps = top_level_deps, + top_level_label = build_target["label"], + xcode_configuration = xcode_configuration, + ) + + if build_target["include"]: + build_targets = [ + _make_build_target( + id = target_ids.id, + post_actions = _pre_post_action_info_from_dicts( + build_target["post_actions"], + ), + pre_actions = _pre_post_action_info_from_dicts( + build_target["pre_actions"], + ), + ), + ] + else: + build_targets = [] + + build_targets.extend([ + _library_target_info_from_dict( + library_target, + scheme_name = scheme_name, + target_ids = target_ids.deps, + ) + for library_target in build_target["library_targets"] + ]) + + return build_targets + +def _diagnostics_info_from_dict(diagnostics): + if not diagnostics: + return _make_diagnostics() + + return _make_diagnostics( + address_sanitizer = diagnostics["address_sanitizer"], + thread_sanitizer = diagnostics["thread_sanitizer"], + undefined_behavior_sanitizer = ( + diagnostics["undefined_behavior_sanitizer"] + ), + ) + +def _env_infos_from_dict(env): + if env == "inherit": + return None + return { + key: _arg_env_info_from_dict(value) + for key, value in env.items() + } + +def _get_library_target_id(label, *, scheme_name, target_ids): + target_id = target_ids.get(label) + if not target_id: + fail( + """\ +Unknown library target in `xcscheme` "{scheme}": {label} + +Is '{label}' an `alias` target? Only actual target labels are support in \ +`xcscheme` definitions. Check that '{label}' is spelled correctly, and if it \ +is, make sure it's a transitive dependency of a top-level target in the \ +`xcodeproj.top_level_targets` attribute. +""".format(label = label, scheme = scheme_name), + ) + + return target_id + +def _get_target_ids( + *, + scheme_name, + target_environment, + top_level_deps, + top_level_label, + xcode_configuration): + if not target_environment: + if "simulator" in top_level_deps: + target_environment = "simulator" + else: + target_environment = "device" + target_ids_by_configuration = top_level_deps.get(target_environment) + if not target_ids_by_configuration: + fail( + """\ +Unknown target environment in `xcscheme` "{scheme}": {env} +""".format(env = target_environment, scheme = scheme_name), + ) + + target_ids_by_label = target_ids_by_configuration.get(xcode_configuration) + if not target_ids_by_label: + fail( + """\ +Unknown Xcode configuration in `xcscheme` "{scheme}": {config} +""".format(config = xcode_configuration, scheme = scheme_name), + ) + + target_ids = target_ids_by_label.get(top_level_label) + if not target_ids: + fail( + """\ +Unknown top-level target in `xcscheme` "{scheme}": {label} + +Is '{label}' an `alias` target? Only actual target labels are support in \ +`xcscheme` definitions. Check that '{label}' is spelled correctly, and if it \ +is, make sure it's in the `xcodeproj.top_level_targets` attribute. +""".format(label = top_level_label, scheme = scheme_name), + ) + + return target_ids + +def _get_top_level_id( + *, + scheme_name, + target_environment, + top_level_deps, + top_level_label, + xcode_configuration): + target_ids = _get_target_ids( + scheme_name = scheme_name, + target_environment = target_environment, + top_level_deps = top_level_deps, + top_level_label = top_level_label, + xcode_configuration = xcode_configuration, + ) + return target_ids.id + +def _launch_target_info_from_dict( + launch_target, + *, + scheme_name, + top_level_deps, + xcode_configuration): + if not launch_target: + return ( + _make_launch_target(), + [], + ) + + if type(launch_target) == "string": + return ( + _make_launch_target( + extension_host = EMPTY_STRING, + id = _get_top_level_id( + target_environment = None, + top_level_deps = top_level_deps, + top_level_label = launch_target, + xcode_configuration = xcode_configuration, + ), + post_actions = [], + pre_actions = [], + working_directory = EMPTY_STRING, + ), + [], + ) + + target_ids = _get_target_ids( + scheme_name = scheme_name, + target_environment = launch_target["target_environment"], + top_level_deps = top_level_deps, + top_level_label = launch_target["label"], + xcode_configuration = xcode_configuration, + ) + + extension_host_label = launch_target["extension_host"] + if extension_host_label: + extension_host = _get_top_level_id( + scheme_name = scheme_name, + target_environment = launch_target["target_environment"], + top_level_deps = top_level_deps, + top_level_label = extension_host_label, + xcode_configuration = xcode_configuration, + ) + else: + extension_host = EMPTY_STRING + + launch_target_info = _make_launch_target( + extension_host = extension_host, + id = target_ids.id, + post_actions = _pre_post_action_info_from_dicts( + launch_target["post_actions"], + ), + pre_actions = _pre_post_action_info_from_dicts( + launch_target["pre_actions"], + ), + working_directory = launch_target["working_directory"], + ) + + library_targets = [ + _library_target_info_from_dict( + library_target, + scheme_name = scheme_name, + target_ids = target_ids.deps, + ) + for library_target in launch_target["library_targets"] + ] + + return (launch_target_info, library_targets) + +def _library_target_info_from_dict( + library_target, + *, + scheme_name, + target_ids): + if type(library_target) == "string": + return _make_build_target( + id = _get_library_target_id( + library_target, + scheme_name = scheme_name, + target_ids = target_ids, + ), + ) + + return _make_build_target( + id = _get_library_target_id( + library_target["label"], + scheme_name = scheme_name, + target_ids = target_ids, + ), + post_actions = _pre_post_action_info_from_dicts( + library_target["post_actions"], + ), + pre_actions = _pre_post_action_info_from_dicts( + library_target["pre_actions"], + ), + ) + +def _pre_post_action_info_from_dict(pre_post_action): + return _make_pre_post_action( + for_build = pre_post_action["for_build"], + order = pre_post_action["order"], + script_text = pre_post_action["script_text"], + title = pre_post_action["title"], + ) + +def _pre_post_action_info_from_dicts(pre_post_actions): + return [ + _pre_post_action_info_from_dict(pre_post_action) + for pre_post_action in pre_post_actions + ] + +def _profile_info_from_dict( + profile, + *, + default_xcode_configuration, + run, + scheme_name, + top_level_deps): + if profile == "same_as_run": + return _make_profile( + build_targets = run.build_targets, + launch_target = run.launch_target, + ) + + if not profile: + return _make_profile() + + xcode_configuration = profile["xcode_configuration"] + + resolving_xcode_configuration = ( + xcode_configuration or + default_xcode_configuration + ) + + (launch_target, build_targets) = _launch_target_info_from_dict( + profile["launch_target"], + scheme_name = scheme_name, + top_level_deps = top_level_deps, + xcode_configuration = resolving_xcode_configuration, + ) + + build_targets.extend([ + info + for build_target in profile["build_targets"] + for info in _build_target_infos_from_dict( + build_target, + scheme_name = scheme_name, + top_level_deps = top_level_deps, + xcode_configuration = resolving_xcode_configuration, + ) + ]) + + return _make_profile( + args = _arg_infos_from_list(profile["args"]), + build_targets = build_targets, + env = _env_infos_from_dict(profile["env"]), + env_include_defaults = profile["env_include_defaults"], + launch_target = launch_target, + use_run_args_and_env = profile["use_run_args_and_env"], + xcode_configuration = xcode_configuration, + ) + +def _run_info_from_dict( + run, + *, + default_xcode_configuration, + scheme_name, + top_level_deps): + if not run: + return _make_run() + + xcode_configuration = run["xcode_configuration"] + resolving_xcode_configuration = ( + xcode_configuration or + default_xcode_configuration + ) + + (launch_target, build_targets) = _launch_target_info_from_dict( + run["launch_target"], + scheme_name = scheme_name, + top_level_deps = top_level_deps, + xcode_configuration = resolving_xcode_configuration, + ) + + build_targets.extend([ + info + for build_target in run["build_targets"] + for info in _build_target_infos_from_dict( + build_target, + scheme_name = scheme_name, + top_level_deps = top_level_deps, + xcode_configuration = resolving_xcode_configuration, + ) + ]) + + return _make_run( + args = _arg_infos_from_list(run["args"]), + build_targets = build_targets, + diagnostics = _diagnostics_info_from_dict(run["diagnostics"]), + env = _env_infos_from_dict(run["env"]), + env_include_defaults = run["env_include_defaults"], + launch_target = launch_target, + xcode_configuration = xcode_configuration, + ) + +def _test_info_from_dict( + test, + *, + default_xcode_configuration, + scheme_name, + top_level_deps): + if not test: + return _make_test() + + xcode_configuration = test["xcode_configuration"] + resolving_xcode_configuration = ( + xcode_configuration or + default_xcode_configuration + ) + + build_targets = [] + test_targets = [] + for test_target in test["test_targets"]: + (test_target, test_build_targets) = _test_target_info_from_dict( + test_target, + scheme_name = scheme_name, + top_level_deps = top_level_deps, + xcode_configuration = resolving_xcode_configuration, + ) + build_targets.extend(test_build_targets) + test_targets.append(test_target) + + build_targets.extend([ + info + for build_target in test["build_targets"] + for info in _build_target_infos_from_dict( + build_target, + scheme_name = scheme_name, + top_level_deps = top_level_deps, + xcode_configuration = resolving_xcode_configuration, + ) + ]) + + return _make_test( + args = _arg_infos_from_list(test["args"]), + build_targets = build_targets, + diagnostics = _diagnostics_info_from_dict(test["diagnostics"]), + env = _env_infos_from_dict(test["env"]), + env_include_defaults = test["env_include_defaults"], + test_targets = test_targets, + use_run_args_and_env = test["use_run_args_and_env"], + xcode_configuration = xcode_configuration, + ) + +def _test_target_info_from_dict( + test_target, + *, + scheme_name, + top_level_deps, + xcode_configuration): + if type(test_target) == "string": + return ( + _make_test_target( + enabled = TRUE_ARG, + id = _get_top_level_id( + scheme_name = scheme_name, + target_environment = None, + top_level_deps = top_level_deps, + top_level_label = test_target, + xcode_configuration = xcode_configuration, + ), + post_actions = [], + pre_actions = [], + ), + [], + ) + + target_ids = _get_target_ids( + scheme_name = scheme_name, + target_environment = test_target["target_environment"], + top_level_deps = top_level_deps, + top_level_label = test_target["label"], + xcode_configuration = xcode_configuration, + ) + + test_target_info = _make_test_target( + enabled = test_target["enabled"], + id = target_ids.id, + post_actions = _pre_post_action_info_from_dicts( + test_target["post_actions"], + ), + pre_actions = _pre_post_action_info_from_dicts( + test_target["pre_actions"], + ), + ) + + library_targets = [ + _library_target_info_from_dict( + library_target, + scheme_name = scheme_name, + target_ids = target_ids.deps, + ) + for library_target in test_target["library_targets"] + ] + + return (test_target_info, library_targets) + +def _scheme_info_from_dict( + scheme, + *, + default_xcode_configuration, + top_level_deps): + name = scheme["name"] + + run = _run_info_from_dict( + scheme["run"], + default_xcode_configuration = default_xcode_configuration, + scheme_name = name, + top_level_deps = top_level_deps, + ) + + return _make_scheme( + name = name, + profile = _profile_info_from_dict( + scheme["profile"], + default_xcode_configuration = default_xcode_configuration, + scheme_name = name, + run = run, + top_level_deps = top_level_deps, + ), + run = run, + test = _test_info_from_dict( + scheme["test"], + default_xcode_configuration = default_xcode_configuration, + scheme_name = name, + top_level_deps = top_level_deps, + ), + ) + +# API + +def _from_json(json_str, *, default_xcode_configuration, top_level_deps): + return [ + _scheme_info_from_dict( + scheme, + default_xcode_configuration = default_xcode_configuration, + top_level_deps = top_level_deps, + ) + for scheme in json.decode(json_str) + ] + +xcscheme_infos = struct( + from_json = _from_json, +) + +# These functions are exposed only for access in unit tests +xcscheme_infos_testable = struct( + make_arg_env = _make_arg_env, + make_build_target = _make_build_target, + make_diagnostics = _make_diagnostics, + make_launch_target = _make_launch_target, + make_pre_post_action = _make_pre_post_action, + make_profile = _make_profile, + make_run = _make_run, + make_scheme = _make_scheme, + make_test = _make_test, + make_test_target = _make_test_target, +) diff --git a/xcodeproj/internal/xcschemes/xcscheme_labels.bzl b/xcodeproj/internal/xcschemes/xcscheme_labels.bzl new file mode 100644 index 0000000000..c1ab8d9429 --- /dev/null +++ b/xcodeproj/internal/xcschemes/xcscheme_labels.bzl @@ -0,0 +1,136 @@ +"""Module for dealing with custom Xcode schemes from the `xcodeproj` macro.""" + +def _resolve_build_target_labels(build_target): + return struct( + extension_host = _resolve_label(build_target.extension_host), + include = build_target.include, + label = _resolve_label(build_target.label), + library_targets = [ + _resolve_library_target_labels(library_target) + for library_target in build_target.library_targets + ], + post_actions = build_target.post_actions, + pre_actions = build_target.pre_actions, + target_environment = build_target.target_environment, + ) + +def _resolve_label(label_str): + if not label_str: + return "" + return str(native.package_relative_label(label_str)) + +def _resolve_labels(schemes): + return [ + _resolve_scheme_labels(scheme) + for scheme in schemes + ] + +def _resolve_launch_target_labels(launch_target): + if not launch_target or type(launch_target) == "string": + return _resolve_label(launch_target) + + return struct( + extension_host = _resolve_label(launch_target.extension_host), + label = _resolve_label(launch_target.label), + library_targets = [ + _resolve_library_target_labels(library_target) + for library_target in launch_target.library_targets + ], + post_actions = launch_target.post_actions, + pre_actions = launch_target.pre_actions, + target_environment = launch_target.target_environment, + working_directory = launch_target.working_directory, + ) + +def _resolve_library_target_labels(library_target): + if type(library_target) == "string": + return _resolve_label(library_target) + + return struct( + label = _resolve_label(library_target.label), + post_actions = library_target.post_actions, + pre_actions = library_target.pre_actions, + ) + +def _resolve_scheme_labels(scheme): + return struct( + name = scheme.name, + profile = _resolve_profile_labels(scheme.profile), + run = _resolve_run_labels(scheme.run), + test = _resolve_test_labels(scheme.test), + ) + +def _resolve_profile_labels(profile): + if not profile or profile == "same_as_run": + return profile + + return struct( + args = profile.args, + build_targets = [ + _resolve_build_target_labels(build_target) + for build_target in profile.build_targets + ], + env = profile.env, + env_include_defaults = profile.env_include_defaults, + launch_target = _resolve_launch_target_labels(profile.launch_target), + use_run_args_and_env = profile.use_run_args_and_env, + xcode_configuration = profile.xcode_configuration, + ) + +def _resolve_run_labels(run): + if not run: + return None + + return struct( + args = run.args, + build_targets = [ + _resolve_build_target_labels(build_target) + for build_target in run.build_targets + ], + diagnostics = run.diagnostics, + env = run.env, + env_include_defaults = run.env_include_defaults, + launch_target = _resolve_launch_target_labels(run.launch_target), + xcode_configuration = run.xcode_configuration, + ) + +def _resolve_test_labels(test): + if not test: + return None + + return struct( + args = test.args, + build_targets = [ + _resolve_build_target_labels(build_target) + for build_target in test.build_targets + ], + diagnostics = test.diagnostics, + env = test.env, + env_include_defaults = test.env_include_defaults, + test_targets = [ + _resolve_test_target_labels(test_target) + for test_target in test.test_targets + ], + use_run_args_and_env = test.use_run_args_and_env, + xcode_configuration = test.xcode_configuration, + ) + +def _resolve_test_target_labels(test_target): + if type(test_target) == "string": + return _resolve_label(test_target) + + return struct( + enabled = test_target.enabled, + label = _resolve_label(test_target.label), + library_targets = [ + _resolve_library_target_labels(library_target) + for library_target in test_target.library_targets + ], + post_actions = test_target.post_actions, + pre_actions = test_target.pre_actions, + target_environment = test_target.target_environment, + ) + +xcscheme_labels = struct( + resolve_labels = _resolve_labels, +) diff --git a/xcodeproj/internal/xcschemes/xcschemes.bzl b/xcodeproj/internal/xcschemes/xcschemes.bzl new file mode 100644 index 0000000000..5f6489f8ec --- /dev/null +++ b/xcodeproj/internal/xcschemes/xcschemes.bzl @@ -0,0 +1,1125 @@ +"""Module for defining custom Xcode schemes (`.xcscheme`s).""" + +load("//xcodeproj/internal:memory_efficiency.bzl", "FALSE_ARG", "TRUE_ARG") + +# Scheme + +def _scheme(name, *, profile = "same_as_run", run = None, test = None): + """Defines a custom scheme. + + Args: + name: Positional. The name of the scheme. + profile: A value returned by [`xcschemes.profile`](#xcschemes.profile), + or the string `"same_as_run"`. + + If `"same_as_run"`, the same targets will be built for the Profile + action as are built for the Run action (defined by + [`xcschemes.run`](#xcschemes.run)). If `None`, `xcschemes.profile()` + will be used, which means no targets will be built for the Profile + action. + run: A value returned by [`xcschemes.run`](#xcschemes.run). + + If `None`, `xcschemes.run()` will be used, which means no targets + will be built for the Run action, except for `build_targets` and + `library_targets` specified in + [`xcschemes.profile`](#xcschemes.profile) and + [`xcschemes.test`](#xcschemes.test). + test: A value returned by [`xcschemes.test`](#xcschemes.test). + + If `None`, `xcschemes.test()` will be used, which means no targets + will be built for the Test action. + """ + if not name: + fail(""" +`name` must be provided to `xcschemes.scheme`. +""") + + return struct( + name = name, + profile = profile, + run = run, + test = test, + ) + +# Actions + +def _profile( + *, + args = "inherit", + build_targets = [], + env = "inherit", + env_include_defaults = True, + launch_target = None, + use_run_args_and_env = None, + xcode_configuration = None): + """Defines the Profile action. + + Args: + args: Command-line arguments to use when profiling the launch target. + + If `"inherit"`, then the arguments will be supplied by the launch + target (e.g. + [`cc_binary.args`](https://bazel.build/reference/be/common-definitions#binary.args)). + Otherwise, the `list` of arguments will be set as provided, and + `None` or `[]` will result in no command-line arguments. + + Each element of the `list` can either be a string or a value + returned by [`xcschemes.arg`](#xcschemes.arg). If an element is a + string, it will be transformed into `xcschemes.arg(element)`. For + example, + ``` + xcschemes.profile( + args = [ + "-arg1", + xcschemes.arg("-arg2", enabled = False), + ], + ) + ``` + will be transformed into: + ``` + xcschemes.profile( + args = [ + xcschemes.arg("-arg1"), + xcschemes.arg("-arg2", enabled = False), + ], + ) + ``` + build_targets: Additional targets to build when profiling. + + Each element of the `list` can be a label string, a value returned + by + [`xcschemes.top_level_build_target`](#xcschemes.top_level_build_target), + or a value returned by + [`xcschemes.top_level_anchor_target`](#xcschemes.top_level_anchor_target). + If an element is a label string, it will be transformed into + `xcschemes.top_level_build_target(label_str)`. For example, + ``` + xcschemes.profile( + build_targets = [ + xcschemes.top_level_anchor_target( + "//App", + … + ), + "//App:Test", + xcschemes.top_level_build_target( + "//CommandLineTool", + … + ), + ], + ) + ``` + will be transformed into: + ``` + xcschemes.profile( + build_targets = [ + xcschemes.top_level_anchor_target( + "//App", + … + ), + xcschemes.top_level_build_target("//App:Test"), + xcschemes.top_level_build_target( + "//CommandLineTool", + … + ), + ], + ) + ``` + env: Environment variables to use when profiling the launch target. + + If set to `"inherit"`, then the environment variables will be + supplied by the launch target (e.g. + [`cc_binary.env`](https://bazel.build/reference/be/common-definitions#binary.env)). + Otherwise, the `dict` of environment variables will be set as + provided, and `None` or `{}` will result in no environment + variables. + + Each value of the `dict` can either be a string or a value returned + by [`xcschemes.env_value`](#xcschemes.env_value). If a value is a + string, it will be transformed into `xcschemes.env_value(value)`. + For example, + ``` + xcschemes.profile( + env = { + "VAR1": "value 1", + "VAR 2": xcschemes.env_value("value2", enabled = False), + }, + ) + ``` + will be transformed into: + ``` + xcschemes.profile( + env = { + "VAR1": xcschemes.env_value("value 1"), + "VAR 2": xcschemes.env_value("value2", enabled = False), + }, + ) + ``` + env_include_defaults: Whether to include the rules_xcodeproj provided + default Bazel environment variables (e.g. + `BUILD_WORKING_DIRECTORY` and `BUILD_WORKSPACE_DIRECTORY`), in + addition to any set by [`env`](#xcschemes.profile-env). + launch_target: The target to launch when profiling. + + Can be `None`, a label string, or a value returned by + [`xcschemes.launch_target`](#xcschemes.launch_target). If a label + string, `xcschemes.launch_target(label_str)` will be used. If + `None`, `xcschemes.launch_target()` will be used, which means no + launch target will be set (i.e. the `Executable` dropdown will be + set to `None`). + use_run_args_and_env: Whether the `Use the Run action's arguments + and environment variables` checkbox is checked. + + If `True`, command-line arguments and environment variables will + still be set as defined by [`args`](#xcschemes.profile-args) and + [`env`](#xcschemes.profile-env), but will be ignored by Xcode unless + you manually uncheck this checkbox in the scheme. If `None`, `True` + will be used if [`args`](#xcschemes.profile-args) and + [`env`](#xcschemes.profile-env) are both `"inherit"`, otherwise + `False` will be used. + xcode_configuration: The name of the Xcode configuration to use to build + the targets referenced in the Profile action (i.e in the + [`build_targets`](#xcschemes.profile-build_targets) and + [`launch_target`](#xcschemes.profile-launch_target) attributes). + + If not set, the value of + [`xcodeproj.default_xcode_configuration`](#xcodeproj-default_xcode_configuration) + is used. + """ + if use_run_args_and_env == None: + use_run_args_and_env = args == "inherit" and env == "inherit" + + return struct( + args = args or [], + build_targets = build_targets or [], + env = env or [], + env_include_defaults = TRUE_ARG if env_include_defaults else FALSE_ARG, + launch_target = launch_target, + use_run_args_and_env = TRUE_ARG if use_run_args_and_env else FALSE_ARG, + xcode_configuration = xcode_configuration or "", + ) + +def _run( + *, + args = "inherit", + build_targets = [], + diagnostics = None, + env = "inherit", + env_include_defaults = True, + launch_target = None, + xcode_configuration = None): + """Defines the Run action. + + Args: + args: Command-line arguments to use when running the launch target. + + If `"inherit"`, then the arguments will be supplied by the launch + target (e.g. + [`cc_binary.args`](https://bazel.build/reference/be/common-definitions#binary.args)). + Otherwise, the `list` of arguments will be set as provided, and + `None` or `[]` will result in no command-line arguments. + + Each element of the `list` can either be a string or a value + returned by [`xcschemes.arg`](#xcschemes.arg). If an element is a + string, it will be transformed into `xcschemes.arg(element)`. For + example, + ``` + xcschemes.run( + args = [ + "-arg1", + xcschemes.arg("-arg2", enabled = False), + ], + ) + ``` + will be transformed into: + ``` + xcschemes.run( + args = [ + xcschemes.arg("-arg1"), + xcschemes.arg("-arg2", enabled = False), + ], + ) + ``` + build_targets: Additional targets to build when running. + + Each element of the `list` can be a label string, a value returned + by + [`xcschemes.top_level_build_target`](#xcschemes.top_level_build_target), + or a value returned by + [`xcschemes.top_level_anchor_target`](#xcschemes.top_level_anchor_target). + If an element is a label string, it will be transformed into + `xcschemes.top_level_build_target(label_str)`. For example, + ``` + xcschemes.run( + build_targets = [ + xcschemes.top_level_anchor_target( + "//App", + … + ), + "//App:Test", + xcschemes.top_level_build_target( + "//CommandLineTool", + … + ), + ], + ) + ``` + will be transformed into: + ``` + xcschemes.run( + build_targets = [ + xcschemes.top_level_anchor_target( + "//App", + … + ), + xcschemes.top_level_build_target("//App:Test"), + xcschemes.top_level_build_target( + "//CommandLineTool", + … + ), + ], + ) + ``` + diagnostics: The diagnostics to enable when running the launch target. + + Can be `None` or a value returned by + [`xcschemes.diagnostics`](#xcschemes.diagnostics). If `None`, + `xcschemes.diagnostics()` will be used, which means no diagnostics + will be enabled. + env: Environment variables to use when running the launch target. + + If set to `"inherit"`, then the environment variables will be + supplied by the launch target (e.g. + [`cc_binary.env`](https://bazel.build/reference/be/common-definitions#binary.env)). + Otherwise, the `dict` of environment variables will be set as + provided, and `None` or `{}` will result in no environment + variables. + + Each value of the `dict` can either be a string or a value returned + by [`xcschemes.env_value`](#xcschemes.env_value). If a value is a + string, it will be transformed into `xcschemes.env_value(value)`. + For example, + ``` + xcschemes.run( + env = { + "VAR1": "value 1", + "VAR 2": xcschemes.env_value("value2", enabled = False), + }, + ) + ``` + will be transformed into: + ``` + xcschemes.run( + env = { + "VAR1": xcschemes.env_value("value 1"), + "VAR 2": xcschemes.env_value("value2", enabled = False), + }, + ) + ``` + env_include_defaults: Whether to include the rules_xcodeproj provided + default Bazel environment variables (e.g. + `BUILD_WORKING_DIRECTORY` and `BUILD_WORKSPACE_DIRECTORY`), in + addition to any set by [`env`](#xcschemes.run-env). + launch_target: The target to launch when running. + + Can be `None`, a label string, or a value returned by + [`xcschemes.launch_target`](#xcschemes.launch_target). If a label + string, `xcschemes.launch_target(label_str)` will be used. If + `None`, `xcschemes.launch_target()` will be used, which means no + launch target will be set (i.e. the `Executable` dropdown will be + set to `None`). + xcode_configuration: The name of the Xcode configuration to use to build + the targets referenced in the Run action (i.e in the + [`build_targets`](#xcschemes.run-build_targets) and + [`launch_target`](#xcschemes.run-launch_target) attributes). + + If not set, the value of + [`xcodeproj.default_xcode_configuration`](#xcodeproj-default_xcode_configuration) + is used. + """ + return struct( + args = args or [], + build_targets = build_targets or [], + diagnostics = diagnostics, + env = env or [], + env_include_defaults = TRUE_ARG if env_include_defaults else FALSE_ARG, + launch_target = launch_target, + xcode_configuration = xcode_configuration or "", + ) + +def _test( + *, + args = "inherit", + build_targets = [], + diagnostics = None, + env = "inherit", + env_include_defaults = True, + test_targets = [], + use_run_args_and_env = None, + xcode_configuration = None): + """Defines the Test action. + + Args: + args: Command-line arguments to use when testing. + + If `"inherit"`, then the arguments will be supplied by the test + targets (e.g. + [`cc_test.args`](https://bazel.build/reference/be/common-definitions#binary.args)), + as long as every test target has the same arguments. Otherwise, the + `list` of arguments will be set as provided, and `None` or `[]` will + result in no command-line arguments. + + Each element of the `list` can either be a string or a value + returned by [`xcschemes.arg`](#xcschemes.arg). If an element is a + string, it will be transformed into `xcschemes.arg(element)`. For + example, + ``` + xcschemes.test( + args = [ + "-arg1", + xcschemes.arg("-arg2", enabled = False), + ], + ) + ``` + will be transformed into: + ``` + xcschemes.test( + args = [ + xcschemes.arg("-arg1"), + xcschemes.arg("-arg2", enabled = False), + ], + ) + ``` + build_targets: Additional targets to build when testing. + + Each element of the `list` can be a label string, a value returned + by + [`xcschemes.top_level_build_target`](#xcschemes.top_level_build_target), + or a value returned by + [`xcschemes.top_level_anchor_target`](#xcschemes.top_level_anchor_target). + If an element is a label string, it will be transformed into + `xcschemes.top_level_build_target(label_str)`. For example, + ``` + xcschemes.test( + build_targets = [ + xcschemes.top_level_anchor_target( + "//App", + … + ), + "//App:Test", + xcschemes.top_level_build_target( + "//CommandLineTool", + … + ), + ], + ) + ``` + will be transformed into: + ``` + xcschemes.test( + build_targets = [ + xcschemes.top_level_anchor_target( + "//App", + … + ), + xcschemes.top_level_build_target("//App:Test"), + xcschemes.top_level_build_target( + "//CommandLineTool", + … + ), + ], + ) + ``` + diagnostics: The diagnostics to enable when testing. + + Can be `None` or a value returned by + [`xcschemes.diagnostics`](#xcschemes.diagnostics). If `None`, + `xcschemes.diagnostics()` will be used, which means no diagnostics + will be enabled. + env: Environment variables to use when testing. + + If set to `"inherit"`, then the environment variables will be + supplied by the test targets (e.g. + [`ios_unit_test.env`](https://github.com/bazelbuild/rules_apple/blob/master/doc/rules-ios.md#ios_unit_test-env)), + as long as every test target has the same environment variables. + Otherwise, the `dict` of environment variables will be set as + provided, and `None` or `{}` will result in no environment + variables. + + Each value of the `dict` can either be a string or a value returned + by [`xcschemes.env_value`](#xcschemes.env_value). If a value is a + string, it will be transformed into `xcschemes.env_value(value)`. + For example, + ``` + xcschemes.test( + env = { + "VAR1": "value 1", + "VAR 2": xcschemes.env_value("value2", enabled = False), + }, + ) + ``` + will be transformed into: + ``` + xcschemes.test( + env = { + "VAR1": xcschemes.env_value("value 1"), + "VAR 2": xcschemes.env_value("value2", enabled = False), + }, + ) + ``` + env_include_defaults: Whether to include the rules_xcodeproj provided + default Bazel environment variables (e.g. + `BUILD_WORKING_DIRECTORY` and `BUILD_WORKSPACE_DIRECTORY`), in + addition to any set by [`env`](#xcschemes.test-env). + test_targets: The test targets to build, and possibly run, when testing. + + Each element of the `list` can be a label string or a value returned + by [`xcschemes.test_target`](#xcschemes.test_target). If an element + is a label string, it will be transformed into + `xcschemes.test_target(label_str)`. For example, + ``` + xcschemes.test( + test_targets = [ + "//App:Test1", + xcschemes.test_target( + "//App:Test2", + … + ), + ], + ) + ``` + will be transformed into: + ``` + xcschemes.test( + test_targets = [ + xcschemes.test_target("//App:Test1"), + xcschemes.test_target( + "//App:Test2", + … + ), + ], + ) + ``` + use_run_args_and_env: Whether the `Use the Run action's arguments + and environment variables` checkbox is checked. + + If `True`, command-line arguments and environment variables will + still be set as defined by [`args`](#xcschemes.test-args) and + [`env`](#xcschemes.test-env), but will be ignored by Xcode unless + you manually uncheck this checkbox in the scheme. If `None`, `True` + will be used if [`args`](#xcschemes.test-args) and + [`env`](#xcschemes.test-env) are both `"inherit"`, otherwise + `False` will be used. + xcode_configuration: The name of the Xcode configuration to use to build + the targets referenced in the Test action (i.e in the + [`build_targets`](#xcschemes.test-build_targets) and + [`test_targets`](#xcschemes.test-test_targets) attributes). + + If not set, the value of + [`xcodeproj.default_xcode_configuration`](#xcodeproj-default_xcode_configuration) + is used. + """ + if use_run_args_and_env == None: + use_run_args_and_env = args == "inherit" and env == "inherit" + + return struct( + args = args or [], + build_targets = build_targets or [], + diagnostics = diagnostics, + env = env or [], + env_include_defaults = TRUE_ARG if env_include_defaults else FALSE_ARG, + test_targets = test_targets or [], + use_run_args_and_env = TRUE_ARG if use_run_args_and_env else FALSE_ARG, + xcode_configuration = xcode_configuration or "", + ) + +# Targets + +def _launch_target( + label, + *, + extension_host = None, + library_targets = [], + post_actions = [], + pre_actions = [], + target_environment = None, + working_directory = None): + """Defines a launch target. + + Args: + label: Positional. The label string of the target to launch when + running. + extension_host: The label string of an extension host for the launch + target. + + If [`label`](#xcschemes.launch_target-label) is an app extension, + this must be set to the label string of a target that bundles the + app extension. Otherwise, this must be `None`. + library_targets: Additional library targets to build when running. + + Library targets must be transitive dependencies of the launch + target. + + Each element of the `list` can be a label string or a value returned + by [`xcschemes.library_target`](#xcschemes.library_target). If an + element is a label string, it will be transformed into + `xcschemes.library_target(label_str)`. For example, + ``` + xcschemes.launch_target( + … + library_targets = [ + "//Modules/Lib1", + xcschemes.library_target( + "//Modules/Lib2", + … + ), + ], + ) + ``` + will be transformed into: + ``` + xcschemes.launch_target( + … + library_targets = [ + xcschemes.library_target("//Modules/Lib1"), + xcschemes.library_target( + "//Modules/Lib2", + … + ), + ], + ) + ``` + post_actions: Post-actions to run when building or running the launch + target. + + Elements of the `list` must be values returned by functions in + [`xcschemes.pre_post_actions`](#xcschemes.pre_post_actions). + pre_actions: Pre-actions to run when building or running the launch + target. + + Elements of the `list` must be values returned by functions in + [`xcschemes.pre_post_actions`](#xcschemes.pre_post_actions). + target_environment: The + [target environment](#top_level_target-target_environments) to use + when determining which version of the launch target + [`label`](#xcschemes.launch_target-label) refers to. + + If not set, the default target environment will be used (i.e. + `"simulator"` if it's one of the available target environments, + otherwise `"device"`). + working_directory: The working directory to use when running the launch + target. + + If not set, the Xcode default working directory will be used (i.e. + some directory in `DerivedData`). + """ + if not label: + fail(""" +`label` must be provided to `xcschemes.launch_target`. +""") + + return struct( + extension_host = extension_host or "", + label = label, + library_targets = library_targets, + post_actions = post_actions, + pre_actions = pre_actions, + target_environment = target_environment, + working_directory = working_directory or "", + ) + +def _library_target(label, *, post_actions = [], pre_actions = []): + """Defines a library target to build. + + Args: + label: Positional. The label string of the library target. + + This must be a library target (i.e. not a top-level target); use + the `build_targets` attribute of + [`profile`](#xcschemes.profile-build_targets), + [`run`](#xcschemes.run-build_targets), or + [`test`](#xcschemes.test-build_targets) to add top-level build + targets. + post_actions: Post-actions to run when building or running the action + this build target is a part of. + + Elements of the `list` must be values returned by functions in + [`xcschemes.pre_post_actions`](#xcschemes.pre_post_actions). + pre_actions: Pre-actions to run when building or running the action + this build target is a part of. + + Elements of the `list` must be values returned by functions in + [`xcschemes.pre_post_actions`](#xcschemes.pre_post_actions). + """ + if not label: + fail(""" +`label` must be provided to `xcschemes.library_target`. +""") + + return struct( + label = label, + post_actions = post_actions, + pre_actions = pre_actions, + ) + +def _test_target( + label, + *, + enabled = True, + library_targets = [], + post_actions = [], + pre_actions = [], + target_environment = None): + """Defines a test target. + + Args: + label: Positional. The label string of the test target. + enabled: Whether the test target is enabled. + + If `True`, the checkbox for the test target will be + checked in the scheme. An unchecked checkbox means Xcode won't + run this test target when testing. + library_targets: Additional library targets to build when testing. + + Library targets must be transitive dependencies of the test target. + They must not be top-level targets; use + [`build_targets`](#xcschemes.test-build_targets) for those. + + Each element of the `list` can be a label string or a value returned + by [`xcschemes.library_target`](#xcschemes.library_target). If an + element is a label string, it will be transformed into + `xcschemes.library_target(label_str)`. For example, + ``` + xcschemes.test_target( + … + library_targets = [ + "//Modules/Lib1", + xcschemes.library_target( + "//Modules/Lib2", + … + ), + ], + ) + ``` + will be transformed into: + ``` + xcschemes.test_target( + … + library_targets = [ + xcschemes.library_target("//Modules/Lib1"), + xcschemes.library_target( + "//Modules/Lib2", + … + ), + ], + ) + ``` + post_actions: Post-actions to run when building or running the test + target. + + Elements of the `list` must be values returned by functions in + [`xcschemes.pre_post_actions`](#xcschemes.pre_post_actions). + pre_actions: Pre-actions to run when building or running the test + target. + + Elements of the `list` must be values returned by functions in + [`xcschemes.pre_post_actions`](#xcschemes.pre_post_actions). + target_environment: The + [target environment](#top_level_target-target_environments) to use + when determining which version of the test target + [`label`](#xcschemes.launch_target-label) refers to. + + If not set, the default target environment will be used (i.e. + `"simulator"` if it's one of the available target environments, + otherwise `"device"`). + """ + if not label: + fail(""" +`label` must be provided to `xcschemes.test_target`. +""") + + return struct( + enabled = TRUE_ARG if enabled else FALSE_ARG, + label = label, + library_targets = library_targets, + post_actions = post_actions, + pre_actions = pre_actions, + target_environment = target_environment, + ) + +def _top_level_anchor_target( + label, + *, + extension_host = None, + library_targets, + target_environment = None): + """Defines a top-level anchor target for library build targets. + + Use this function to define library targets to build, when you don't want + to also build the top-level target they are transitive dependencies of. If + you also want to build the top-level target, use + [`top_level_build_target`](#xcschemes.top_level_build_target-library_targets) + instead. + + Args: + label: Positional. The label string of the top-level target. + + This must be a top-level target (i.e. not a library target); use + the `library_targets` attribute of + [`launch_target`](#xcschemes.launch_target-library_targets), + [`test_target`](#xcschemes.test_target-library_targets), + [`top_level_anchor_target`](#xcschemes.top_level_anchor_target-library_targets), + or + [`top_level_build_target`](#xcschemes.top_level_build_target-library_targets) + to add library build targets. + extension_host: The label string of an extension host for the top-level + target. + + If [`label`](#xcschemes.top_level_build_target-label) is an app + extension, this must be set to the label string of a target that + bundles the app extension. Otherwise, this must be `None`. + library_targets: The library targets to build. + + Library targets must be transitive dependencies of the top-level + anchor target. They must not be top-level targets; instead, set + additional values in the `build_targets` attribute that this + `top_level_build_target` is defined in. + + Each element of the `list` can be a label string or a value returned + by [`xcschemes.library_target`](#xcschemes.library_target). If an + element is a label string, it will be transformed into + `xcschemes.library_target(label_str)`. For example, + ``` + xcschemes.top_level_anchor_target( + … + library_targets = [ + "//Modules/Lib1", + xcschemes.library_target( + "//Modules/Lib2", + … + ), + ], + ) + ``` + will be transformed into: + ``` + xcschemes.top_level_anchor_target( + … + library_targets = [ + xcschemes.library_target("//Modules/Lib1"), + xcschemes.library_target( + "//Modules/Lib2", + … + ), + ], + ) + ``` + target_environment: The + [target environment](#top_level_target-target_environments) to use + when determining which version of the top-level target + [`label`](#xcschemes.top_level_build_target-label) refers to. + + If not set, the default target environment will be used (i.e. + `"simulator"` if it's one of the available target environments, + otherwise `"device"`). + """ + if not label: + fail("""\ +`label` must be provided to `xcscheme.top_level_anchor_target`. +""") + if not library_targets: + fail(""" +`library_targets` must be non-empty for `xcscheme.top_level_anchor_target`. +""") + + return struct( + extension_host = extension_host or "", + include = False, + label = label, + library_targets = library_targets, + post_actions = [], + pre_actions = [], + target_environment = target_environment, + ) + +def _top_level_build_target( + label, + *, + extension_host = None, + library_targets = [], + post_actions = [], + pre_actions = [], + target_environment = None): + """Defines a top-level target to build. + + Use this function to define a top-level target, and optionally transitive + library targets, to build. If you don't want to build the top-level target, + and only want to build the transitive library targets, use + [`top_level_anchor_target`](#xcschemes.top_level_anchor_target-library_targets) + instead. + + Args: + label: Positional. The label string of the top-level target. + + This must be a top-level target (i.e. not a library target); use + the `library_targets` attribute of + [`launch_target`](#xcschemes.launch_target-library_targets), + [`test_target`](#xcschemes.test_target-library_targets), + [`top_level_build_target`](#xcschemes.top_level_build_target-library_targets), + or + [`top_level_anchor_target`](#xcschemes.top_level_anchor_target-library_targets) + to add library build targets. + extension_host: The label string of an extension host for the top-level + target. + + If [`label`](#xcschemes.top_level_build_target-label) is an app + extension, this must be set to the label string of a target that + bundles the app extension. Otherwise, this must be `None`. + library_targets: Additional library targets to build. + + Library targets must be transitive dependencies of the top-level + build target. They must not be top-level targets; instead, set + additional values in the `build_targets` attribute that this + `top_level_build_target` is defined in. + + Each element of the `list` can be a label string or a value returned + by [`xcschemes.library_target`](#xcschemes.library_target). If an + element is a label string, it will be transformed into + `xcschemes.library_target(label_str)`. For example, + ``` + xcschemes.top_level_build_target( + … + library_targets = [ + "//Modules/Lib1", + xcschemes.library_target( + "//Modules/Lib2", + … + ), + ], + ) + ``` + will be transformed into: + ``` + xcschemes.top_level_build_target( + … + library_targets = [ + xcschemes.library_target("//Modules/Lib1"), + xcschemes.library_target( + "//Modules/Lib2", + … + ), + ], + ) + ``` + post_actions: Post-actions to run when building or running the action + this build target is a part of. + + Elements of the `list` must be values returned by functions in + [`xcschemes.pre_post_actions`](#xcschemes.pre_post_actions). + pre_actions: Pre-actions to run when building or running the action + this build target is a part of. + + Elements of the `list` must be values returned by functions in + [`xcschemes.pre_post_actions`](#xcschemes.pre_post_actions). + target_environment: The + [target environment](#top_level_target-target_environments) to use + when determining which version of the top-level target + [`label`](#xcschemes.top_level_build_target-label) refers to. + + If not set, the default target environment will be used (i.e. + `"simulator"` if it's one of the available target environments, + otherwise `"device"`). + """ + if not label: + fail(""" +`label` must be provided to `xcschemes.top_level_build_target`. +""") + + return struct( + extension_host = extension_host or "", + include = True, + label = label, + library_targets = library_targets, + post_actions = post_actions, + pre_actions = pre_actions, + target_environment = target_environment, + ) + +# `pre_post_actions` + +def _build_script(title = "Run Script", *, order = None, script_text): + """Defines a pre-action or post-action script to run when building. + + This action will appear in the Pre-actions or Post-actions section of the + Build section of the scheme. + + Args: + title: The title of the action. + order: The relative order of the action within the section it appears + in. + + If `None`, the action will be added to the end of the section, in + an unspecified but deterministic order. Otherwise, the order should + be an integer. Smaller order values will run before larger order + values. rules_xcodeproj created actions (e.g. "Update .lldbinit and + copy dSYMs") use order values 0, -100, -200, etc. + script_text: The script text. + + The script will be run in Bazel's execution root, so you probably + want to change to the `$SRCROOT` directory in the script. + """ + if not title: + fail(""" +`title` must be provided to `xcschemes.pre_post_actions.build_script`. +""") + if not script_text: + fail(""" +`script_text` must be provided to `xcschemes.pre_post_actions.build_script`. +""") + + return struct( + for_build = True, + order = order, + script_text = script_text, + title = title, + ) + +def _launch_script(title = "Run Script", *, order = None, script_text): + """Defines a pre-action or post-action script to run when running. + + This action will appear in the Pre-actions or Post-actions section of the + Test, Run, or Profile section of the scheme. + + Args: + title: The title of the action. + order: TBD. + script_text: The script text. + + The script will be run in Bazel's execution root, so you probably + want to change to the `$SRCROOT` directory in the script. + """ + if not title: + fail(""" +`title` must be provided to `xcschemes.pre_post_actions.launch_script`. +""") + if not script_text: + fail(""" +`script_text` must be provided to `xcschemes.pre_post_actions.launch_script`. +""") + + return struct( + for_build = False, + order = order, + script_text = script_text, + title = title, + ) + +_pre_post_actions = struct( + build_script = _build_script, + launch_script = _launch_script, +) + +# Other + +def _arg(value, *, enabled = True): + """Defines a command-line argument. + + Args: + value: Positional. The command-line argument. + + Arguments with quotes, spaces, or newlines will be escaped. You + should not use additional quotes around arguments with spaces. If + you include quotes around your argument, those quotes will be part + of the argument. + enabled: Whether the command-line argument is enabled. + + If `True`, the checkbox for the argument will be + checked in the scheme. An unchecked checkbox means Xcode won't + include that argument when running a target. + """ + if not value: + fail(""" +`value` must be provided to `xcschemes.arg`. +""") + + return struct( + enabled = TRUE_ARG if enabled else FALSE_ARG, + value = value, + ) + +def _env_value(value, *, enabled = True): + """Defines an environment variable value. + + Args: + value: Positional. The environment variable value. + + Values with quotes, spaces, or newlines will be escaped. You + should not use additional quotes around values with spaces. If + you include quotes around your value, those quotes will be part + of the value. + enabled: Whether the environment variable is enabled. + + If `True`, the checkbox for the environment variable will be + checked in the scheme. An unchecked checkbox means Xcode won't + include that environment variable when running a target. + """ + if not value: + fail(""" +`value` must be provided to `xcschemes.env_value`. +""") + + return struct( + enabled = TRUE_ARG if enabled else FALSE_ARG, + value = value, + ) + +def _diagnostics( + *, + address_sanitizer = False, + thread_sanitizer = False, + undefined_behavior_sanitizer = False): + """Defines the diagnostics to enable. + + Args: + address_sanitizer: Whether to enable Address Sanitizer. + + If `True`, + [`thread_sanitizer`](#xcschemes.diagnostics-thread_sanitizer) must + be `False. + thread_sanitizer: Whether to enable Thread Sanitizer. + + If `True`, + [`address_sanitizer`](#xcschemes.diagnostics-address_sanitizer) must + be `False. + undefined_behavior_sanitizer: Whether to enable Undefined Behavior + Sanitizer. + """ + if address_sanitizer and thread_sanitizer: + fail(""" +Address Sanitizer cannot be used together with Thread Sanitizer. +""") + + return struct( + address_sanitizer = TRUE_ARG if address_sanitizer else FALSE_ARG, + thread_sanitizer = TRUE_ARG if thread_sanitizer else FALSE_ARG, + undefined_behavior_sanitizer = ( + TRUE_ARG if undefined_behavior_sanitizer else FALSE_ARG + ), + ) + +# API + +xcschemes = struct( + arg = _arg, + diagnostics = _diagnostics, + env_value = _env_value, + launch_target = _launch_target, + library_target = _library_target, + pre_post_actions = _pre_post_actions, + profile = _profile, + run = _run, + scheme = _scheme, + test = _test, + test_target = _test_target, + top_level_anchor_target = _top_level_anchor_target, + top_level_build_target = _top_level_build_target, +) diff --git a/xcodeproj/internal/xcschemes/xcschemes_execution.bzl b/xcodeproj/internal/xcschemes/xcschemes_execution.bzl new file mode 100644 index 0000000000..064de6e760 --- /dev/null +++ b/xcodeproj/internal/xcschemes/xcschemes_execution.bzl @@ -0,0 +1,433 @@ +"""Module for defining custom Xcode schemes (`.xcscheme`s).""" + +load( + "//xcodeproj/internal:memory_efficiency.bzl", + "EMPTY_STRING", + "FALSE_ARG", + "TRUE_ARG", +) +load("//xcodeproj/internal:platforms.bzl", "platforms") + +_XCODE_PREVIEW_PRODUCT_TYPES = { + "A": None, # com.apple.product-type.application.on-demand-install-capable + "B": None, # com.apple.product-type.bundle + "E": None, # com.apple.product-type.extensionkit-extension + "T": None, # com.apple.product-type.tool + "a": None, # com.apple.product-type.application + "e": None, # com.apple.product-type.app-extension + "f": None, # com.apple.product-type.framework + "t": None, # com.apple.product-type.tv-app-extension + "u": None, # com.apple.product-type.bundle.unit-test + "w": None, # com.apple.product-type.application.watchapp2 +} + +_EXECUTION_ACTION_NAME = struct( + build = "build", + profile = "profile", + run = "run", + test = "test", +) + +# enum of flags, mainly to ensure the strings are frozen and reused +_FLAGS = struct( + colorize = "--colorize", + consolidation_maps = "--consolidation-maps", + target_and_extension_hosts = "--target-and-extension-hosts", +) + +def _hosted_target(hosted_target): + return [hosted_target.hosted, hosted_target.host] + +def _is_same_platform_xcode_preview_target(*, platform, xcode_target): + if not xcode_target: + return False + if not platforms.is_same_type(platform, xcode_target.platform): + return False + return xcode_target.product.type in _XCODE_PREVIEW_PRODUCT_TYPES + +def _null_newlines(str): + return str.replace("\n", "\0") + +# API + +def _write_schemes( + *, + actions, + autogeneration_mode, + colorize, + consolidation_maps, + default_xcode_configuration, + extension_point_identifiers_file, + generator_name, + hosted_targets, + include_transitive_preview_targets, + install_path, + targets_args, + targets_env, + tool, + workspace_directory, + xcode_targets, + xcscheme_infos): + """Creates the `.xcscheme` `File`s for a project. + + Args: + actions: `ctx.actions`. + autogeneration_mode: Specifies how Xcode schemes are automatically + generated. + colorize: A `bool` indicating whether to colorize the output. + consolidation_maps: A `list` of `File`s containing target consolidation + maps. + default_xcode_configuration: The name of the the Xcode configuration to + use when building, if not overridden by custom schemes. + extension_point_identifiers_file: A `File` that contains a JSON + representation of `[TargetID: ExtensionPointIdentifier]`. + generator_name: The name of the `xcodeproj` generator target. + hosted_targets: A `depset` of `struct`s with `host` and `hosted` fields. + The `host` field is the target ID of the hosting target. The + `hosted` field is the target ID of the hosted target. + include_transitive_preview_targets: Whether to adjust schemes to + explicitly include transitive dependencies that are able to run + Xcode Previews. + install_path: The workspace relative path to where the final + `.xcodeproj` will be written. + targets_args: A `dict` mapping `xcode_target.id` to `list` of + command-line arguments. + targets_env: A `dict` mapping `xcode_target.id` to `list` of environment + variable `tuple`s. + tool: The executable that will generate the output files. + workspace_directory: The absolute path to the Bazel workspace + directory. + xcode_targets: A `dict` mapping `xcode_target.id` to `xcode_target`s. + xcscheme_infos: A `list` of `struct`s as returned` by + `xcscheme_infos.from_json`. + + Returns: + A `tuple` with two elements: + + * A `File` for the directory containing the `.xcscheme`s. + * The `xcschememanagement.plist` `File`. + """ + + output = actions.declare_directory( + "{}_pbxproj_partials/xcschemes".format(generator_name), + ) + xcschememanagement = actions.declare_file( + "{}_pbxproj_partials/xcschememanagement.plist".format(generator_name), + ) + + execution_actions_file = actions.declare_file( + "{}_pbxproj_partials/execution_actions_file".format(generator_name), + ) + targets_args_env_file = actions.declare_file( + "{}_pbxproj_partials/targets_args_env_file".format(generator_name), + ) + custom_schemes_file = actions.declare_file( + "{}_pbxproj_partials/custom_schemes_file".format(generator_name), + ) + + inputs = consolidation_maps + [ + custom_schemes_file, + execution_actions_file, + extension_point_identifiers_file, + targets_args_env_file, + ] + + args = actions.args() + args.use_param_file("@%s") + args.set_param_file_format("multiline") + + execution_actions_args = actions.args() + execution_actions_args.set_param_file_format("multiline") + + targets_args_env_args = actions.args() + targets_args_env_args.set_param_file_format("multiline") + + custom_scheme_args = actions.args() + custom_scheme_args.set_param_file_format("multiline") + + # outputDirectory + args.add(output.path) + + # schemeManagementOutputPath + args.add(xcschememanagement) + + # autogenerationMode + args.add(autogeneration_mode) + + # defaultXcodeConfiguration + args.add(default_xcode_configuration) + + # workspace + args.add(workspace_directory) + + # installPath + args.add(install_path) + + # extensionPointIdentifiersFile + args.add(extension_point_identifiers_file) + + # executionActionsFile + args.add(execution_actions_file) + + # targetsArgsEnvFile + args.add(targets_args_env_file) + + # customSchemesFile + args.add(custom_schemes_file) + + # transitivePreviewTargetsFile + if include_transitive_preview_targets: + transitive_preview_targets_file = actions.declare_file( + "{}_pbxproj_partials/transitive_preview_targets_file".format( + generator_name, + ), + ) + inputs.append(transitive_preview_targets_file) + args.add(transitive_preview_targets_file) + + transitive_preview_targets_args = actions.args() + transitive_preview_targets_args.set_param_file_format("multiline") + + for xcode_target in xcode_targets.values(): + if xcode_target.product.type in _XCODE_PREVIEW_PRODUCT_TYPES: + ids = [ + id + for id in xcode_target.transitive_dependencies.to_list() + if _is_same_platform_xcode_preview_target( + platform = xcode_target.platform, + xcode_target = xcode_targets.get(id), + ) + ] + if ids: + transitive_preview_targets_args.add(xcode_target.id) + transitive_preview_targets_args.add(len(ids)) + transitive_preview_targets_args.add_all(ids) + + actions.write( + transitive_preview_targets_file, + transitive_preview_targets_args, + ) + else: + args.add(EMPTY_STRING) + + # consolidationMaps + args.add_all(_FLAGS.consolidation_maps, consolidation_maps) + + # targetAndExtensionHosts + args.add_all( + _FLAGS.target_and_extension_hosts, + hosted_targets, + map_each = _hosted_target, + ) + + # TargetArgsAndEnv + + targets_args_env_args.add(len(targets_args)) + for id, target_args in targets_args.items(): + targets_args_env_args.add(id) + targets_args_env_args.add(len(target_args)) + targets_args_env_args.add_all(target_args, map_each = _null_newlines) + + targets_args_env_args.add(len(targets_env)) + for id, target_env in targets_env.items(): + targets_args_env_args.add(id) + targets_args_env_args.add(len(target_env)) + for key, value in target_env: + targets_args_env_args.add_all( + [key, value], + map_each = _null_newlines, + ) + + # CreateCustomSchemeInfos + + def _add_args(args): + if args == None: + custom_scheme_args.add(-1) + return + custom_scheme_args.add(len(args)) + for arg in args: + custom_scheme_args.add_all([arg.value], map_each = _null_newlines) + custom_scheme_args.add(arg.enabled) + + # buildifier: disable=uninitialized + def _add_build_targets(build_targets, *, action_name, scheme_name): + custom_scheme_args.add(len(build_targets)) + for build_target in build_targets: + custom_scheme_args.add(build_target.id) + _add_execution_actions( + build_target, + action_name = action_name, + scheme_name = scheme_name, + ) + + def _add_diagnostics(diagnostics): + custom_scheme_args.add(diagnostics.address_sanitizer) + custom_scheme_args.add(diagnostics.thread_sanitizer) + custom_scheme_args.add(diagnostics.undefined_behavior_sanitizer) + + def _add_env(env): + if env == None: + custom_scheme_args.add(-1) + return + + custom_scheme_args.add(len(env)) + + # buildifier: disable=uninitialized + for key, env in env.items(): + custom_scheme_args.add_all( + [key, env.value], + map_each = _null_newlines, + ) + custom_scheme_args.add(env.enabled) + + # buildifier: disable=uninitialized + def _add_execution_action( + action, + *, + action_name, + id, + is_pre_action, + scheme_name): + execution_actions_args.add(scheme_name) + execution_actions_args.add( + _EXECUTION_ACTION_NAME.build if action.for_build else action_name, + ) + execution_actions_args.add(is_pre_action) + execution_actions_args.add_all( + [action.title, action.script_text], + map_each = _null_newlines, + ) + execution_actions_args.add(id) + execution_actions_args.add(action.order or "") + + # buildifier: disable=uninitialized + def _add_execution_actions(target, *, action_name, scheme_name): + # buildifier: disable=uninitialized + for action in target.pre_actions: + _add_execution_action( + action, + action_name = action_name, + id = target.id, + is_pre_action = TRUE_ARG, + scheme_name = scheme_name, + ) + + # buildifier: disable=uninitialized + for action in target.post_actions: + _add_execution_action( + action, + action_name = action_name, + id = target.id, + is_pre_action = FALSE_ARG, + scheme_name = scheme_name, + ) + + # buildifier: disable=uninitialized + def _add_launch_target(launch_target, *, action_name, scheme_name): + custom_scheme_args.add(launch_target.id) + custom_scheme_args.add(launch_target.extension_host) + custom_scheme_args.add(launch_target.working_directory) + _add_execution_actions( + launch_target, + action_name = action_name, + scheme_name = scheme_name, + ) + + custom_scheme_args.add(len(xcscheme_infos)) + for info in xcscheme_infos: + scheme_name = info.name + custom_scheme_args.add(scheme_name) + + # Test + + custom_scheme_args.add(len(info.test.test_targets)) + for test_target in info.test.test_targets: + custom_scheme_args.add(test_target.id) + custom_scheme_args.add(test_target.enabled) + _add_execution_actions( + test_target, + action_name = _EXECUTION_ACTION_NAME.test, + scheme_name = scheme_name, + ) + + _add_build_targets( + info.test.build_targets, + action_name = _EXECUTION_ACTION_NAME.test, + scheme_name = scheme_name, + ) + + _add_args(info.test.args) + _add_env(info.test.env) + custom_scheme_args.add(info.test.env_include_defaults) + custom_scheme_args.add(info.test.use_run_args_and_env) + _add_diagnostics(info.test.diagnostics) + custom_scheme_args.add(info.test.xcode_configuration) + + # Run + + _add_build_targets( + info.run.build_targets, + action_name = _EXECUTION_ACTION_NAME.run, + scheme_name = scheme_name, + ) + + _add_args(info.run.args) + _add_env(info.run.env) + custom_scheme_args.add(info.run.env_include_defaults) + _add_diagnostics(info.run.diagnostics) + custom_scheme_args.add(info.run.xcode_configuration) + + _add_launch_target( + info.run.launch_target, + action_name = _EXECUTION_ACTION_NAME.run, + scheme_name = scheme_name, + ) + + # Profile + + _add_build_targets( + info.profile.build_targets, + action_name = _EXECUTION_ACTION_NAME.profile, + scheme_name = scheme_name, + ) + + _add_args(info.profile.args) + _add_env(info.profile.env) + custom_scheme_args.add(info.profile.env_include_defaults) + custom_scheme_args.add(info.profile.use_run_args_and_env) + custom_scheme_args.add(info.profile.xcode_configuration) + + _add_launch_target( + info.profile.launch_target, + action_name = _EXECUTION_ACTION_NAME.profile, + scheme_name = scheme_name, + ) + + # colorize + if colorize: + args.add(_FLAGS.colorize) + + actions.write(execution_actions_file, execution_actions_args) + actions.write(targets_args_env_file, targets_args_env_args) + actions.write(custom_schemes_file, custom_scheme_args) + + actions.run( + arguments = [args], + executable = tool, + inputs = inputs, + outputs = [output, xcschememanagement], + progress_message = "Creating '.xcschemes' for {}".format(install_path), + mnemonic = "WriteXCSchemes", + execution_requirements = { + # Lots of files to read and write, so lets have some speed + "no-sandbox": "1", + }, + ) + + return (output, xcschememanagement) + +# API + +xcschemes_execution = struct( + write_schemes = _write_schemes, +)