diff --git a/CHANGELOG.md b/CHANGELOG.md index c45df2f84..1fddbbd52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,11 +48,12 @@ END_UNRELEASED_TEMPLATE ### New -* TBD +* Added support for pre- and post-actions on autogenerated schemes, including explicit build post-action failure handling: [#3307](https://github.com/MobileNativeFoundation/rules_xcodeproj/pull/3307) +* Added `run_build_post_actions_on_failure` to custom scheme `xcschemes.run(...)` configuration for explicit build post-action failure handling: [#3307](https://github.com/MobileNativeFoundation/rules_xcodeproj/pull/3307) ### Adjusted -* TBD +* We now resolve `bazel_env` earlier to reduce analysis cache invalidation when environment values are resolved dynamically: [#3305](https://github.com/MobileNativeFoundation/rules_xcodeproj/pull/3305) ### Fixed diff --git a/docs/bazel.md b/docs/bazel.md index 415b9037e..179dc0051 100755 --- a/docs/bazel.md +++ b/docs/bazel.md @@ -107,7 +107,7 @@ xcodeproj( | project_name | Optional. The name to use for the `.xcodeproj` file.

If not specified, the value of the `name` argument is used. | `None` | | project_options | Optional. A value returned by `project_options`. | `None` | | scheme_autogeneration_mode | Optional. Specifies how Xcode schemes are automatically generated:

| `"auto"` | -| scheme_autogeneration_config | Optional. A value returned by [`xcschemes.autogeneration_config`](#xcschemes.autogeneration_config).

Allows further configuration of `scheme_autogeneration_mode`. | `{}` | +| scheme_autogeneration_config | Optional. A value returned by [`xcschemes.autogeneration_config`](#xcschemes.autogeneration_config).

Allows further configuration of `scheme_autogeneration_mode`. | `None` | | target_name_mode | Optional. Specifies how Xcode targets names are represented:

| `"auto"` | | top_level_targets | A `list` of a list of top-level targets.

Each target can be specified as either a `Label` (or label-like `string`), a value returned by `top_level_target`, or a value returned by `top_level_targets`. | none | | tvos_device_cpus | Optional. The value to use for `--tvos_cpus` when building the transitive dependencies of the targets specified in the `top_level_targets` argument with the `"device"` `target_environment`.

**Warning:** Changing this value will affect the Starlark transition hash of all transitive dependencies of the targets specified in the `top_level_targets` argument with the `"device"` `target_environment`, even if they aren't tvOS targets. | `"arm64"` | @@ -235,6 +235,61 @@ Defines a command-line argument. | literal_string | Whether `value` should be interpreted as a literal string.

If `True`, any spaces will be escaped. This means that `value` will be passed to the launch target as a single string. If `False`, any spaces will not be escaped. This is useful to group multiple arguments under a single checkbox in Xcode. | `True` | + + +## xcschemes.autogeneration.profile + +
+load("@rules_xcodeproj//xcodeproj/internal/docs:xcschemes.bzl", "xcschemes")
+
+xcschemes.autogeneration.profile(*, post_actions, pre_actions)
+
+ +Creates a value for the `profile` argument of `xcschemes.autogeneration_config`. + +**PARAMETERS** + + +| Name | Description | Default Value | +| :------------- | :------------- | :------------- | +| post_actions | Profile post-actions for autogenerated schemes.

Defaults to `[]`. | `[]` | +| pre_actions | Profile pre-actions for autogenerated schemes.

Defaults to `[]`. | `[]` | + +**RETURNS** + +An opaque value for the + [`profile`](user-content-xcschemes.autogeneration_config-profile) + argument of `xcschemes.autogeneration_config`. + + + + +## xcschemes.autogeneration.run + +
+load("@rules_xcodeproj//xcodeproj/internal/docs:xcschemes.bzl", "xcschemes")
+
+xcschemes.autogeneration.run(*, post_actions, pre_actions, run_build_post_actions_on_failure)
+
+ +Creates a value for the `run` argument of `xcschemes.autogeneration_config`. + +**PARAMETERS** + + +| Name | Description | Default Value | +| :------------- | :------------- | :------------- | +| post_actions | Build and run post-actions for autogenerated schemes.

Defaults to `[]`. | `[]` | +| pre_actions | Build and run pre-actions for autogenerated schemes.

Defaults to `[]`. | `[]` | +| run_build_post_actions_on_failure | Whether autogenerated build post-actions should run even when the build fails. | `False` | + +**RETURNS** + +An opaque value for the + [`run`](user-content-xcschemes.autogeneration_config-run) + argument of `xcschemes.autogeneration_config`. + + ## xcschemes.autogeneration.test @@ -242,7 +297,7 @@ Defines a command-line argument.
 load("@rules_xcodeproj//xcodeproj/internal/docs:xcschemes.bzl", "xcschemes")
 
-xcschemes.autogeneration.test(*, options)
+xcschemes.autogeneration.test(*, options, post_actions, pre_actions)
 
Creates a value for the `test` argument of `xcschemes.autogeneration_config`. @@ -253,6 +308,8 @@ Creates a value for the `test` argument of `xcschemes.autogeneration_config`. | Name | Description | Default Value | | :------------- | :------------- | :------------- | | options | Test options for autogeneration.

Defaults to `None`. | `None` | +| post_actions | Test post-actions for autogenerated schemes.

Defaults to `[]`. | `[]` | +| pre_actions | Test pre-actions for autogenerated schemes.

Defaults to `[]`. | `[]` | **RETURNS** @@ -268,7 +325,7 @@ An opaque value for the
 load("@rules_xcodeproj//xcodeproj/internal/docs:xcschemes.bzl", "xcschemes")
 
-xcschemes.autogeneration_config(*, scheme_name_exclude_patterns, test)
+xcschemes.autogeneration_config(*, profile, run, scheme_name_exclude_patterns, test)
 
Creates a value for the [`scheme_autogeneration_config`](xcodeproj-scheme_autogeneration_config) attribute of `xcodeproj`. @@ -278,6 +335,8 @@ Creates a value for the [`scheme_autogeneration_config`](xcodeproj-scheme_autoge | Name | Description | Default Value | | :------------- | :------------- | :------------- | +| profile | Options to use for the profile action.

Example:

xcodeproj(
    ...
    scheme_autogeneration_config = xcschemes.autogeneration_config(
        profile = xcschemes.autogeneration.profile(
            pre_actions = [
                xcschemes.pre_post_actions.launch_script(
                    title = "Profile Start",
                    script_text = "echo profile",
                ),
            ],
        ),
    ),
)
| `None` | +| run | Options to use for the build and run actions.

Example:

xcodeproj(
    ...
    scheme_autogeneration_config = xcschemes.autogeneration_config(
        run = xcschemes.autogeneration.run(
            pre_actions = [
                xcschemes.pre_post_actions.build_script(
                    title = "Build Start",
                    script_text = "echo build start",
                ),
                xcschemes.pre_post_actions.launch_script(
                    title = "Run Start",
                    script_text = "echo run",
                ),
            ],
        ),
    ),
)
| `None` | | scheme_name_exclude_patterns | A `list` of regex patterns used to skip creating matching autogenerated schemes.

Example:

xcodeproj(
    ...
    scheme_name_exclude_patterns = xcschemes.autogeneration_config(
        scheme_name_exclude_patterns = [
            ".*somePattern.*",
            "^AnotherPattern.*",
        ],
    ),
)
| `None` | | test | Options to use for the test action.

Example:

xcodeproj(
    ...
    scheme_autogeneration_config = xcschemes.autogeneration_config(
        test = xcschemes.autogeneration.test(
            options = xcschemes.test_options(
                app_language = "en",
                app_region = "US",
                code_coverage = False,
            )
        )
    )
)
| `None` | @@ -496,7 +555,7 @@ Defines the Profile action. load("@rules_xcodeproj//xcodeproj/internal/docs:xcschemes.bzl", "xcschemes") xcschemes.run(*, args, build_targets, diagnostics, env, env_include_defaults, launch_target, - storekit_configuration, xcode_configuration) + run_build_post_actions_on_failure, storekit_configuration, xcode_configuration) Defines the Run action. @@ -512,6 +571,7 @@ Defines the Run action. | 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),
    },
)
| `"inherit"` | | 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). This does not apply to [`xcschemes.launch_path`](#xcschemes.launch_path)s. | `True` | | launch_target | The target to launch when running.

Can be `None`, a label string, a value returned by [`xcschemes.launch_target`](#xcschemes.launch_target), or a value returned by [`xcschemes.launch_path`](#xcschemes.launch_path). 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`). | `None` | +| run_build_post_actions_on_failure | Whether build post-actions should run even when the scheme's `BuildAction` fails. | `False` | | storekit_configuration | A StoreKit configuration file for use with [StoreKit Testing](https://developer.apple.com/documentation/xcode/setting-up-storekit-testing-in-xcode).

Can be `None`, or a label string referring to a single configuration file. | `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. | `None` | diff --git a/test/internal/xcschemes/info_constructors_tests.bzl b/test/internal/xcschemes/info_constructors_tests.bzl index 29301adc9..ea6d6db7d 100644 --- a/test/internal/xcschemes/info_constructors_tests.bzl +++ b/test/internal/xcschemes/info_constructors_tests.bzl @@ -592,6 +592,7 @@ def info_constructors_test_suite(name): env = None, env_include_defaults = "1", launch_target = xcscheme_infos_testable.make_launch_target(), + run_build_post_actions_on_failure = "0", storekit_configuration = "", xcode_configuration = "", ), @@ -625,6 +626,7 @@ def info_constructors_test_suite(name): }, env_include_defaults = "0", launch_target = xcscheme_infos_testable.make_launch_target("L"), + run_build_post_actions_on_failure = "1", storekit_configuration = "", xcode_configuration = "Run", ), @@ -656,6 +658,7 @@ def info_constructors_test_suite(name): launch_target = xcscheme_infos_testable.make_launch_target( id = "L", ), + run_build_post_actions_on_failure = "1", storekit_configuration = "", xcode_configuration = "Run", ), @@ -689,6 +692,7 @@ def info_constructors_test_suite(name): ), run = xcscheme_infos_testable.make_run( xcode_configuration = "R", + run_build_post_actions_on_failure = "1", ), test = xcscheme_infos_testable.make_test( xcode_configuration = "R", @@ -703,6 +707,7 @@ def info_constructors_test_suite(name): ), run = xcscheme_infos_testable.make_run( xcode_configuration = "R", + run_build_post_actions_on_failure = "1", ), test = xcscheme_infos_testable.make_test( xcode_configuration = "R", diff --git a/test/internal/xcschemes/infos_from_json_tests.bzl b/test/internal/xcschemes/infos_from_json_tests.bzl index ba85de718..2db1cae88 100644 --- a/test/internal/xcschemes/infos_from_json_tests.bzl +++ b/test/internal/xcschemes/infos_from_json_tests.bzl @@ -613,6 +613,7 @@ def infos_from_json_test_suite(name): env = {"A": "B"}, env_include_defaults = "1", launch_target = full_launch_target, + run_build_post_actions_on_failure = "0", storekit_configuration = "", xcode_configuration = "custom", ), @@ -704,6 +705,7 @@ def infos_from_json_test_suite(name): target_environment = "", working_directory = "", ), + run_build_post_actions_on_failure = "0", storekit_configuration = "", xcode_configuration = "", ), @@ -807,6 +809,7 @@ def infos_from_json_test_suite(name): ], working_directory = "wd", ), + run_build_post_actions_on_failure = "0", storekit_configuration = "", xcode_configuration = "custom", ), @@ -867,6 +870,7 @@ def infos_from_json_test_suite(name): env = full_env, env_include_defaults = "0", launch_target = full_launch_target, + run_build_post_actions_on_failure = "0", use_run_args_and_env = "0", storekit_configuration = "//test/internal/xcschemes:fixture.storekit", xcode_configuration = "custom", diff --git a/test/internal/xcschemes/utils.bzl b/test/internal/xcschemes/utils.bzl index 759885740..06d2caf35 100644 --- a/test/internal/xcschemes/utils.bzl +++ b/test/internal/xcschemes/utils.bzl @@ -65,6 +65,7 @@ def _dict_to_run_info(d): env = _dict_of_dicts_to_env_infos(d["env"]), env_include_defaults = d["env_include_defaults"], launch_target = _dict_to_launch_target_info(d["launch_target"]), + run_build_post_actions_on_failure = d["run_build_post_actions_on_failure"], storekit_configuration = d["storekit_configuration"], xcode_configuration = d["xcode_configuration"], ) diff --git a/test/internal/xcschemes/write_schemes_tests.bzl b/test/internal/xcschemes/write_schemes_tests.bzl index 452b95a32..352baec39 100644 --- a/test/internal/xcschemes/write_schemes_tests.bzl +++ b/test/internal/xcschemes/write_schemes_tests.bzl @@ -20,6 +20,9 @@ load( # Utility +_AUTOGENERATION_CONFIG_DECLARED_FILE = mock_actions.mock_file( + "a_generator_name-autogeneration-config-file", +) _CUSTOM_SCHEMES_DECLARED_FILE = mock_actions.mock_file( "a_generator_name_pbxproj_partials/custom_schemes_file", ) @@ -73,13 +76,14 @@ def _write_schemes_test_impl(ctx): _OUTPUT_DECLARED_DIRECTORY: None, } expected_declared_files = { + _AUTOGENERATION_CONFIG_DECLARED_FILE: None, _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 + [ - ctx.attr.autogeneration_config_file, + _AUTOGENERATION_CONFIG_DECLARED_FILE, _CUSTOM_SCHEMES_DECLARED_FILE, _EXECUTION_ACTIONS_DECLARED_FILE, ctx.attr.extension_point_identifiers_file, @@ -98,7 +102,7 @@ def _write_schemes_test_impl(ctx): ) = xcschemes_execution.write_schemes( actions = actions.mock, autogeneration_mode = ctx.attr.autogeneration_mode, - autogeneration_config_file = ctx.attr.autogeneration_config_file, + autogeneration_config = json.decode(ctx.attr.autogeneration_config), colorize = ctx.attr.colorize, consolidation_maps = ctx.attr.consolidation_maps, default_xcode_configuration = ctx.attr.default_xcode_configuration, @@ -203,7 +207,7 @@ write_schemes_test = unittest.make( attrs = { # Inputs "autogeneration_mode": attr.string(mandatory = True), - "autogeneration_config_file": attr.string(mandatory = True), + "autogeneration_config": attr.string(mandatory = True), "colorize": attr.bool(mandatory = True), "consolidation_maps": attr.string_list(mandatory = True), "default_xcode_configuration": attr.string(mandatory = True), @@ -236,7 +240,19 @@ def write_schemes_test_suite(name): # Inputs autogeneration_mode, - autogeneration_config_file, + autogeneration_config = { + "build_post_actions": [], + "build_pre_actions": [], + "build_run_post_actions_on_failure": ["0"], + "profile_post_actions": [], + "profile_pre_actions": [], + "run_post_actions": [], + "run_pre_actions": [], + "scheme_name_exclude_patterns": [], + "test_options": ["", "", "0"], + "test_post_actions": [], + "test_pre_actions": [], + }, colorize = False, consolidation_maps, default_xcode_configuration, @@ -257,7 +273,7 @@ def write_schemes_test_suite(name): # Inputs autogeneration_mode = autogeneration_mode, - autogeneration_config_file = autogeneration_config_file, + autogeneration_config = json.encode(autogeneration_config), colorize = colorize, consolidation_maps = consolidation_maps, default_xcode_configuration = default_xcode_configuration, @@ -282,6 +298,35 @@ def write_schemes_test_suite(name): "0", ]) + "\n" + no_autogeneration_config_content = "\n".join([ + # appLanguage + "", + # appRegion + "", + # codeCoverage + "0", + # buildPreActionsCount + "0", + # buildPostActionsCount + "0", + # buildRunPostActionsOnFailure + "0", + # profilePreActionsCount + "0", + # profilePostActionsCount + "0", + # runPreActionsCount + "0", + # runPostActionsCount + "0", + # testPreActionsCount + "0", + # testPostActionsCount + "0", + # schemeNameExcludePatterns + "", + ]) + "\n" + no_target_args_and_env_content = "\n".join([ # argsCount "0", @@ -296,7 +341,29 @@ def write_schemes_test_suite(name): # Inputs autogeneration_mode = "none", - autogeneration_config_file = "some/autogeneration-config-file", + autogeneration_config = { + "build_post_actions": [], + "build_pre_actions": [], + "build_run_post_actions_on_failure": ["1"], + "profile_post_actions": [], + "profile_pre_actions": [], + "run_post_actions": [], + "run_pre_actions": [ + "Run Start (DevX & \"Logs\")", + "echo \"\"\n", + "-100", + ], + "scheme_name_exclude_patterns": [ + "^App$", + ], + "test_options": [ + "en", + "US", + "1", + ], + "test_post_actions": [], + "test_pre_actions": [], + }, colorize = True, consolidation_maps = [ "some/consolidation_maps/0", @@ -316,7 +383,7 @@ def write_schemes_test_suite(name): # autogenerationMode "none", # autogenerationConfigFile - "some/autogeneration-config-file", + _AUTOGENERATION_CONFIG_DECLARED_FILE.path, # defaultXcodeConfiguration "Debug", # workspace @@ -339,6 +406,41 @@ def write_schemes_test_suite(name): "--colorize", ], expected_writes = { + _AUTOGENERATION_CONFIG_DECLARED_FILE: "\n".join([ + # appLanguage + "en", + # appRegion + "US", + # codeCoverage + "1", + # buildPreActionsCount + "0", + # buildPostActionsCount + "0", + # buildRunPostActionsOnFailure + "1", + # profilePreActionsCount + "0", + # profilePostActionsCount + "0", + # runPreActionsCount + "1", + # runPreActions - title + "Run Start (DevX & \"Logs\")", + # runPreActions - scriptText + "echo \"\"\0", + # runPreActions - order + "-100", + # runPostActionsCount + "0", + # testPreActionsCount + "0", + # testPostActionsCount + "0", + # schemeNameExcludePatterns + "^App$", + "", + ]) + "\n", _CUSTOM_SCHEMES_DECLARED_FILE: no_custom_schemes_content, _EXECUTION_ACTIONS_DECLARED_FILE: "\n", _TARGETS_ARGS_ENV_DECLARED_FILE: no_target_args_and_env_content, @@ -352,7 +454,6 @@ def write_schemes_test_suite(name): # Inputs autogeneration_mode = "auto", - autogeneration_config_file = "some/autogeneration-config-file", consolidation_maps = [ "some/consolidation_maps/0", "some/consolidation_maps/1", @@ -552,6 +653,7 @@ def write_schemes_test_suite(name): ], working_directory = "run working dir", ), + run_build_post_actions_on_failure = "1", storekit_configuration = "StoreKitConfig", xcode_configuration = "Run", ), @@ -678,7 +780,7 @@ def write_schemes_test_suite(name): # autogenerationMode "auto", # autogenerationConfigFile - "some/autogeneration-config-file", + _AUTOGENERATION_CONFIG_DECLARED_FILE.path, # defaultXcodeConfiguration "AppStore", # workspace @@ -699,6 +801,9 @@ def write_schemes_test_suite(name): "some/consolidation_maps/1", ], expected_writes = { + _AUTOGENERATION_CONFIG_DECLARED_FILE: ( + no_autogeneration_config_content + ), _EXECUTION_ACTIONS_DECLARED_FILE: "\n".join([ # schemeName "Scheme 1", @@ -1126,6 +1231,8 @@ def write_schemes_test_suite(name): "", # - run - customWorkingDirectory "", + # - run - runBuildPostActionsOnFailure + "0", # - profile - buildTargets "", # - profile - commandLineArguments count @@ -1256,6 +1363,8 @@ def write_schemes_test_suite(name): "run extension host id", # - run - customWorkingDirectory "run working dir", + # - run - runBuildPostActionsOnFailure + "1", # - profile - buildTargets "profile bt", "", @@ -1362,6 +1471,8 @@ def write_schemes_test_suite(name): "/Foo/Bar.app", # - run - customWorkingDirectory "", + # - run - runBuildPostActionsOnFailure + "0", # - profile - buildTargets "", # - profile - commandLineArguments count @@ -1393,7 +1504,6 @@ def write_schemes_test_suite(name): # Inputs autogeneration_mode = "auto", - autogeneration_config_file = "some/autogeneration-config-file", consolidation_maps = [ "some/consolidation_maps/0", "some/consolidation_maps/1", @@ -1426,7 +1536,7 @@ def write_schemes_test_suite(name): # autogenerationMode "auto", # autogenerationConfigFile - "some/autogeneration-config-file", + _AUTOGENERATION_CONFIG_DECLARED_FILE.path, # defaultXcodeConfiguration "AppStore", # workspace @@ -1455,6 +1565,9 @@ def write_schemes_test_suite(name): "IOS_APP_2", ], expected_writes = { + _AUTOGENERATION_CONFIG_DECLARED_FILE: ( + no_autogeneration_config_content + ), _CUSTOM_SCHEMES_DECLARED_FILE: no_custom_schemes_content, _EXECUTION_ACTIONS_DECLARED_FILE: "\n", _TARGETS_ARGS_ENV_DECLARED_FILE: no_target_args_and_env_content, @@ -1468,7 +1581,6 @@ def write_schemes_test_suite(name): # Inputs autogeneration_mode = "auto", - autogeneration_config_file = "some/autogeneration-config-file", consolidation_maps = [ "some/consolidation_maps/0", "some/consolidation_maps/1", @@ -1503,7 +1615,7 @@ def write_schemes_test_suite(name): # autogenerationMode "auto", # autogenerationConfigFile - "some/autogeneration-config-file", + _AUTOGENERATION_CONFIG_DECLARED_FILE.path, # defaultXcodeConfiguration "AppStore", # workspace @@ -1524,6 +1636,9 @@ def write_schemes_test_suite(name): "some/consolidation_maps/1", ], expected_writes = { + _AUTOGENERATION_CONFIG_DECLARED_FILE: ( + no_autogeneration_config_content + ), _CUSTOM_SCHEMES_DECLARED_FILE: no_custom_schemes_content, _EXECUTION_ACTIONS_DECLARED_FILE: "\n", _TARGETS_ARGS_ENV_DECLARED_FILE: "\n".join([ diff --git a/tools/generators/lib/XCScheme/src/CreateBuildAction.swift b/tools/generators/lib/XCScheme/src/CreateBuildAction.swift index 00d2d8290..43db2ab98 100644 --- a/tools/generators/lib/XCScheme/src/CreateBuildAction.swift +++ b/tools/generators/lib/XCScheme/src/CreateBuildAction.swift @@ -12,12 +12,14 @@ public struct CreateBuildAction { public func callAsFunction( entries: [BuildActionEntry], postActions: [ExecutionAction], - preActions: [ExecutionAction] + preActions: [ExecutionAction], + runPostActionsOnFailure: Bool = false ) -> String { return callable( /*entries:*/ entries, /*postActions:*/ postActions, - /*preActions:*/ preActions + /*preActions:*/ preActions, + /*runPostActionsOnFailure:*/ runPostActionsOnFailure ) } } @@ -55,19 +57,27 @@ extension CreateBuildAction { public typealias Callable = ( _ entries: [BuildActionEntry], _ postActions: [ExecutionAction], - _ preActions: [ExecutionAction] + _ preActions: [ExecutionAction], + _ runPostActionsOnFailure: Bool ) -> String public static func defaultCallable( entries: [BuildActionEntry], postActions: [ExecutionAction], - preActions: [ExecutionAction] + preActions: [ExecutionAction], + runPostActionsOnFailure: Bool ) -> String { + let runPostActionsOnFailureAttribute = runPostActionsOnFailure ? + #""" + + runPostActionsOnFailure = "YES" +"""# : "" + // 3 spaces for indentation is intentional return #""" + buildImplicitDependencies = "NO"\#(runPostActionsOnFailureAttribute)> \#(preActions.preActionsString)\# \#(postActions.postActionsString)\# diff --git a/tools/generators/lib/XCScheme/test/CreateBuildActionTests.swift b/tools/generators/lib/XCScheme/test/CreateBuildActionTests.swift index 2b1cd25db..b84b03cb6 100644 --- a/tools/generators/lib/XCScheme/test/CreateBuildActionTests.swift +++ b/tools/generators/lib/XCScheme/test/CreateBuildActionTests.swift @@ -74,16 +74,46 @@ final class CreateBuildActionTests: XCTestCase { XCTAssertNoDifference(action, expectedAction) } + + func test_runPostActionsOnFailure() { + // Arrange + + let entries: [BuildActionEntry] = [] + + let expectedAction = #""" + + + + + +"""# + + // Act + + let action = createBuildActionWithDefaults( + entries: entries, + runPostActionsOnFailure: true + ) + + // Assert + + XCTAssertNoDifference(action, expectedAction) + } } private func createBuildActionWithDefaults( entries: [BuildActionEntry], postActions: [ExecutionAction] = [], - preActions: [ExecutionAction] = [] + preActions: [ExecutionAction] = [], + runPostActionsOnFailure: Bool = false ) -> String { return CreateBuildAction.defaultCallable( entries: entries, postActions: postActions, - preActions: preActions + preActions: preActions, + runPostActionsOnFailure: runPostActionsOnFailure ) } diff --git a/tools/generators/xcschemes/src/Generator/AutogenerationConfig.swift b/tools/generators/xcschemes/src/Generator/AutogenerationConfig.swift new file mode 100644 index 000000000..87de56066 --- /dev/null +++ b/tools/generators/xcschemes/src/Generator/AutogenerationConfig.swift @@ -0,0 +1,153 @@ +import Foundation +import ToolCommon + +struct AutogenerationConfig { + struct Action: Equatable { + let title: String + let scriptText: String + let order: Int? + } + + let appLanguage: String? + let appRegion: String? + let buildPreActions: [Action] + let buildPostActions: [Action] + let buildRunPostActionsOnFailure: Bool + let codeCoverage: Bool + let profilePreActions: [Action] + let profilePostActions: [Action] + let runPreActions: [Action] + let runPostActions: [Action] + let testPreActions: [Action] + let testPostActions: [Action] + let schemeNameExcludePatterns: [String] + + static func parse( + from url: URL + ) async throws -> Self { + var rawArgs = ArraySlice(try await url.allLines.collect()) + + let appLanguage = try rawArgs.consumeArg( + "app-language", + as: String?.self, + in: url + ) + let appRegion = try rawArgs.consumeArg( + "app-region", + as: String?.self, + in: url + ) + let codeCoverage = try rawArgs.consumeArg( + "code-coverage", + as: Bool.self, + in: url + ) + let buildPreActions = try rawArgs.consumeBuildActions( + "build-pre-actions", + in: url + ) + let buildPostActions = try rawArgs.consumeBuildActions( + "build-post-actions", + in: url + ) + let buildRunPostActionsOnFailure = try rawArgs.consumeArg( + "build-run-post-actions-on-failure", + as: Bool.self, + in: url + ) + let profilePreActions = try rawArgs.consumeBuildActions( + "profile-pre-actions", + in: url + ) + let profilePostActions = try rawArgs.consumeBuildActions( + "profile-post-actions", + in: url + ) + let runPreActions = try rawArgs.consumeBuildActions( + "run-pre-actions", + in: url + ) + let runPostActions = try rawArgs.consumeBuildActions( + "run-post-actions", + in: url + ) + let testPreActions = try rawArgs.consumeBuildActions( + "test-pre-actions", + in: url + ) + let testPostActions = try rawArgs.consumeBuildActions( + "test-post-actions", + in: url + ) + let schemeNameExcludePatterns = try rawArgs.consumeArgs( + "scheme-name-exclude-patterns", + in: url + ) + + return AutogenerationConfig( + appLanguage: appLanguage, + appRegion: appRegion, + buildPreActions: buildPreActions, + buildPostActions: buildPostActions, + buildRunPostActionsOnFailure: buildRunPostActionsOnFailure, + codeCoverage: codeCoverage, + profilePreActions: profilePreActions, + profilePostActions: profilePostActions, + runPreActions: runPreActions, + runPostActions: runPostActions, + testPreActions: testPreActions, + testPostActions: testPostActions, + schemeNameExcludePatterns: schemeNameExcludePatterns + ) + } +} + +private extension ArraySlice where Element == String { + mutating func consumeBuildActions( + _ namePrefix: String, + in url: URL, + file: StaticString = #filePath, + line: UInt = #line + ) throws -> [AutogenerationConfig.Action] { + let count = try consumeArg( + "\(namePrefix)-count", + as: Int.self, + in: url, + file: file, + line: line + ) + + var buildActions: [AutogenerationConfig.Action] = [] + for _ in (0.. Self { - var rawArgs = ArraySlice(try await url.allLines.collect()) - - let appLanguage = try rawArgs.consumeArg( - "app-language", - as: String?.self, - in: url - ) - let appRegion = try rawArgs.consumeArg( - "app-region", - as: String?.self, - in: url - ) - let codeCoverage = try rawArgs.consumeArg( - "code-coverage", - as: Bool.self, - in: url - ) - let schemeNameExcludePatterns = try rawArgs.consumeArgs( - "scheme-name-exclude-patterns", - in: url - ) - - return AutogenerationConfigArguments( - appLanguage: appLanguage, - appRegion: appRegion, - codeCoverage: codeCoverage, - schemeNameExcludePatterns: schemeNameExcludePatterns - ) - } -} diff --git a/tools/generators/xcschemes/src/Generator/CreateAutomaticSchemeInfo.swift b/tools/generators/xcschemes/src/Generator/CreateAutomaticSchemeInfo.swift index 1b3320473..8f8dc6596 100644 --- a/tools/generators/xcschemes/src/Generator/CreateAutomaticSchemeInfo.swift +++ b/tools/generators/xcschemes/src/Generator/CreateAutomaticSchemeInfo.swift @@ -16,19 +16,38 @@ extension Generator { /// Creates a `SchemeInfo` for an automatically generated scheme. func callAsFunction( + buildPostActions: [AutogenerationConfig.Action], + buildPreActions: [AutogenerationConfig.Action], + buildRunPostActionsOnFailure: Bool, + profilePostActions: [AutogenerationConfig.Action], + profilePreActions: [AutogenerationConfig.Action], commandLineArguments: [CommandLineArgument], customSchemeNames: Set, environmentVariables: [EnvironmentVariable], extensionHost: Target?, + runPostActions: [AutogenerationConfig.Action], + runPreActions: [AutogenerationConfig.Action], target: Target, + testPostActions: [AutogenerationConfig.Action], + testPreActions: [AutogenerationConfig.Action], testOptions: SchemeInfo.Test.Options? ) throws -> SchemeInfo? { return try callable( + /*buildPostActions:*/ buildPostActions, + /*buildPreActions:*/ buildPreActions, + /*buildRunPostActionsOnFailure:*/ + buildRunPostActionsOnFailure, + /*profilePostActions:*/ profilePostActions, + /*profilePreActions:*/ profilePreActions, /*commandLineArguments:*/ commandLineArguments, /*customSchemeNames:*/ customSchemeNames, /*environmentVariables:*/ environmentVariables, /*extensionHost:*/ extensionHost, + /*runPostActions:*/ runPostActions, + /*runPreActions:*/ runPreActions, /*target:*/ target, + /*testPostActions:*/ testPostActions, + /*testPreActions:*/ testPreActions, /*testOptions:*/ testOptions ) } @@ -39,20 +58,38 @@ extension Generator { extension Generator.CreateAutomaticSchemeInfo { typealias Callable = ( + _ buildPostActions: [AutogenerationConfig.Action], + _ buildPreActions: [AutogenerationConfig.Action], + _ buildRunPostActionsOnFailure: Bool, + _ profilePostActions: [AutogenerationConfig.Action], + _ profilePreActions: [AutogenerationConfig.Action], _ commandLineArguments: [CommandLineArgument], _ customSchemeNames: Set, _ environmentVariables: [EnvironmentVariable], _ extensionHost: Target?, + _ runPostActions: [AutogenerationConfig.Action], + _ runPreActions: [AutogenerationConfig.Action], _ target: Target, + _ testPostActions: [AutogenerationConfig.Action], + _ testPreActions: [AutogenerationConfig.Action], _ testOptions: SchemeInfo.Test.Options? ) throws -> SchemeInfo? static func defaultCallable( + buildPostActions: [AutogenerationConfig.Action], + buildPreActions: [AutogenerationConfig.Action], + buildRunPostActionsOnFailure: Bool, + profilePostActions: [AutogenerationConfig.Action], + profilePreActions: [AutogenerationConfig.Action], commandLineArguments: [CommandLineArgument], customSchemeNames: Set, environmentVariables: [EnvironmentVariable], extensionHost: Target?, + runPostActions: [AutogenerationConfig.Action], + runPreActions: [AutogenerationConfig.Action], target: Target, + testPostActions: [AutogenerationConfig.Action], + testPreActions: [AutogenerationConfig.Action], testOptions: SchemeInfo.Test.Options? ) throws -> SchemeInfo? { let baseSchemeName = target.buildableReference.blueprintName.schemeName @@ -111,6 +148,83 @@ extension Generator.CreateAutomaticSchemeInfo { .defaultEnvironmentVariables + environmentVariables } + let buildExecutionActions = buildPreActions.map { + SchemeInfo.ExecutionAction( + title: $0.title, + scriptText: $0.scriptText, + action: .build, + isPreAction: true, + target: target, + order: $0.order + ) + } + buildPostActions.map { + SchemeInfo.ExecutionAction( + title: $0.title, + scriptText: $0.scriptText, + action: .build, + isPreAction: false, + target: target, + order: $0.order + ) + } + let runExecutionActions = runPreActions.map { + SchemeInfo.ExecutionAction( + title: $0.title, + scriptText: $0.scriptText, + action: .run, + isPreAction: true, + target: target, + order: $0.order + ) + } + runPostActions.map { + SchemeInfo.ExecutionAction( + title: $0.title, + scriptText: $0.scriptText, + action: .run, + isPreAction: false, + target: target, + order: $0.order + ) + } + let testExecutionActions = testPreActions.map { + SchemeInfo.ExecutionAction( + title: $0.title, + scriptText: $0.scriptText, + action: .test, + isPreAction: true, + target: target, + order: $0.order + ) + } + testPostActions.map { + SchemeInfo.ExecutionAction( + title: $0.title, + scriptText: $0.scriptText, + action: .test, + isPreAction: false, + target: target, + order: $0.order + ) + } + let profileExecutionActions = profilePreActions.map { + SchemeInfo.ExecutionAction( + title: $0.title, + scriptText: $0.scriptText, + action: .profile, + isPreAction: true, + target: target, + order: $0.order + ) + } + profilePostActions.map { + SchemeInfo.ExecutionAction( + title: $0.title, + scriptText: $0.scriptText, + action: .profile, + isPreAction: false, + target: target, + order: $0.order + ) + } + return SchemeInfo( name: name, test: .init( @@ -139,6 +253,8 @@ extension Generator.CreateAutomaticSchemeInfo { enableThreadPerformanceChecker: false, environmentVariables: runEnvironmentVariables, launchTarget: launchTarget, + runBuildPostActionsOnFailure: + buildRunPostActionsOnFailure, storeKitConfiguration: nil, xcodeConfiguration: nil ), @@ -151,7 +267,11 @@ extension Generator.CreateAutomaticSchemeInfo { useRunArgsAndEnv: true, xcodeConfiguration: nil ), - executionActions: [] + executionActions: + buildExecutionActions + + runExecutionActions + + testExecutionActions + + profileExecutionActions ) } } diff --git a/tools/generators/xcschemes/src/Generator/CreateAutomaticSchemeInfos.swift b/tools/generators/xcschemes/src/Generator/CreateAutomaticSchemeInfos.swift index 3596259a3..62452f356 100644 --- a/tools/generators/xcschemes/src/Generator/CreateAutomaticSchemeInfos.swift +++ b/tools/generators/xcschemes/src/Generator/CreateAutomaticSchemeInfos.swift @@ -26,24 +26,43 @@ extension Generator { /// Creates `SchemeInfo`s for automatically generated schemes. func callAsFunction( autogenerationMode: AutogenerationMode, + buildPostActions: [AutogenerationConfig.Action], + buildPreActions: [AutogenerationConfig.Action], + buildRunPostActionsOnFailure: Bool, + profilePostActions: [AutogenerationConfig.Action], + profilePreActions: [AutogenerationConfig.Action], commandLineArguments: [TargetID: [CommandLineArgument]], customSchemeNames: Set, environmentVariables: [TargetID: [EnvironmentVariable]], extensionHostIDs: [TargetID: [TargetID]], + runPostActions: [AutogenerationConfig.Action], + runPreActions: [AutogenerationConfig.Action], targets: [Target], targetsByID: [TargetID: Target], targetsByKey: [Target.Key: Target], + testPostActions: [AutogenerationConfig.Action], + testPreActions: [AutogenerationConfig.Action], testOptions: SchemeInfo.Test.Options? ) throws -> [SchemeInfo] { return try callable( /*autogenerationMode:*/ autogenerationMode, + /*buildPostActions:*/ buildPostActions, + /*buildPreActions:*/ buildPreActions, + /*buildRunPostActionsOnFailure:*/ + buildRunPostActionsOnFailure, + /*profilePostActions:*/ profilePostActions, + /*profilePreActions:*/ profilePreActions, /*commandLineArguments:*/ commandLineArguments, /*customSchemeNames:*/ customSchemeNames, /*environmentVariables:*/ environmentVariables, /*extensionHostIDs:*/ extensionHostIDs, + /*runPostActions:*/ runPostActions, + /*runPreActions:*/ runPreActions, /*targets:*/ targets, /*targetsByID:*/ targetsByID, /*targetsByKey:*/ targetsByKey, + /*testPostActions:*/ testPostActions, + /*testPreActions:*/ testPreActions, /*createTargetAutomaticSchemeInfos:*/ createTargetAutomaticSchemeInfos, /*testOptions:*/ testOptions @@ -57,13 +76,22 @@ extension Generator { extension Generator.CreateAutomaticSchemeInfos { typealias Callable = ( _ autogenerationMode: AutogenerationMode, + _ buildPostActions: [AutogenerationConfig.Action], + _ buildPreActions: [AutogenerationConfig.Action], + _ buildRunPostActionsOnFailure: Bool, + _ profilePostActions: [AutogenerationConfig.Action], + _ profilePreActions: [AutogenerationConfig.Action], _ commandLineArguments: [TargetID: [CommandLineArgument]], _ customSchemeNames: Set, _ environmentVariables: [TargetID: [EnvironmentVariable]], _ extensionHostIDs: [TargetID: [TargetID]], + _ runPostActions: [AutogenerationConfig.Action], + _ runPreActions: [AutogenerationConfig.Action], _ targets: [Target], _ targetsByID: [TargetID: Target], _ targetsByKey: [Target.Key: Target], + _ testPostActions: [AutogenerationConfig.Action], + _ testPreActions: [AutogenerationConfig.Action], _ createTargetAutomaticSchemeInfos: Generator.CreateTargetAutomaticSchemeInfos, _ testOptions: SchemeInfo.Test.Options? @@ -71,13 +99,22 @@ extension Generator.CreateAutomaticSchemeInfos { static func defaultCallable( autogenerationMode: AutogenerationMode, + buildPostActions: [AutogenerationConfig.Action], + buildPreActions: [AutogenerationConfig.Action], + buildRunPostActionsOnFailure: Bool, + profilePostActions: [AutogenerationConfig.Action], + profilePreActions: [AutogenerationConfig.Action], commandLineArguments: [TargetID: [CommandLineArgument]], customSchemeNames: Set, environmentVariables: [TargetID: [EnvironmentVariable]], extensionHostIDs: [TargetID: [TargetID]], + runPostActions: [AutogenerationConfig.Action], + runPreActions: [AutogenerationConfig.Action], targets: [Target], targetsByID: [TargetID: Target], targetsByKey: [Target.Key: Target], + testPostActions: [AutogenerationConfig.Action], + testPreActions: [AutogenerationConfig.Action], createTargetAutomaticSchemeInfos: Generator.CreateTargetAutomaticSchemeInfos, testOptions: SchemeInfo.Test.Options? @@ -116,13 +153,23 @@ extension Generator.CreateAutomaticSchemeInfos { let id = target.key.sortedIds.first! return try createTargetAutomaticSchemeInfos( + buildPostActions: buildPostActions, + buildPreActions: buildPreActions, + buildRunPostActionsOnFailure: + buildRunPostActionsOnFailure, + profilePostActions: profilePostActions, + profilePreActions: profilePreActions, commandLineArguments: commandLineArguments[id, default: []], customSchemeNames: customSchemeNames, environmentVariables: environmentVariables[id, default: []], extensionHostIDs: extensionHostIDs, + runPostActions: runPostActions, + runPreActions: runPreActions, target: target, targetsByID: targetsByID, targetsByKey: targetsByKey, + testPostActions: testPostActions, + testPreActions: testPreActions, testOptions: testOptions ) } diff --git a/tools/generators/xcschemes/src/Generator/CreateCustomSchemeInfos.swift b/tools/generators/xcschemes/src/Generator/CreateCustomSchemeInfos.swift index a7d38ac9b..ee0738553 100644 --- a/tools/generators/xcschemes/src/Generator/CreateCustomSchemeInfos.swift +++ b/tools/generators/xcschemes/src/Generator/CreateCustomSchemeInfos.swift @@ -543,6 +543,11 @@ set as: String?.self, in: url ) + let runBuildPostActionsOnFailure = try consumeArg( + "run-build-post-actions-on-failure", + as: Bool.self, + in: url + ) if let launchTarget, launchTarget.canExpandMacros && environmentVariablesIncludeDefaults @@ -564,6 +569,8 @@ set enableThreadPerformanceChecker: enableThreadPerformanceChecker, environmentVariables: environmentVariables, launchTarget: launchTarget, + runBuildPostActionsOnFailure: + runBuildPostActionsOnFailure, storeKitConfiguration: storeKitConfiguration, xcodeConfiguration: xcodeConfiguration ) diff --git a/tools/generators/xcschemes/src/Generator/CreateScheme.swift b/tools/generators/xcschemes/src/Generator/CreateScheme.swift index 860e1ab6d..7ce19009a 100644 --- a/tools/generators/xcschemes/src/Generator/CreateScheme.swift +++ b/tools/generators/xcschemes/src/Generator/CreateScheme.swift @@ -366,7 +366,9 @@ extension Generator.CreateScheme { .map(\.action), preActions: buildPreActions .sorted(by: compareExecutionActions) - .map(\.action) + .map(\.action), + runPostActionsOnFailure: + schemeInfo.run.runBuildPostActionsOnFailure ), testAction: createTestAction( appLanguage: schemeInfo.test.options?.appLanguage, diff --git a/tools/generators/xcschemes/src/Generator/CreateTargetAutomaticSchemeInfo.swift b/tools/generators/xcschemes/src/Generator/CreateTargetAutomaticSchemeInfo.swift index 3aa07587e..26c08ab33 100644 --- a/tools/generators/xcschemes/src/Generator/CreateTargetAutomaticSchemeInfo.swift +++ b/tools/generators/xcschemes/src/Generator/CreateTargetAutomaticSchemeInfo.swift @@ -23,23 +23,42 @@ extension Generator { /// Creates `SchemeInfo`s for a target's automatically generated /// schemes. func callAsFunction( + buildPostActions: [AutogenerationConfig.Action], + buildPreActions: [AutogenerationConfig.Action], + buildRunPostActionsOnFailure: Bool, + profilePostActions: [AutogenerationConfig.Action], + profilePreActions: [AutogenerationConfig.Action], commandLineArguments: [CommandLineArgument], customSchemeNames: Set, environmentVariables: [EnvironmentVariable], extensionHostIDs: [TargetID: [TargetID]], + runPostActions: [AutogenerationConfig.Action], + runPreActions: [AutogenerationConfig.Action], target: Target, targetsByID: [TargetID: Target], targetsByKey: [Target.Key: Target], + testPostActions: [AutogenerationConfig.Action], + testPreActions: [AutogenerationConfig.Action], testOptions: SchemeInfo.Test.Options? ) throws -> [SchemeInfo] { return try callable( + /*buildPostActions:*/ buildPostActions, + /*buildPreActions:*/ buildPreActions, + /*buildRunPostActionsOnFailure:*/ + buildRunPostActionsOnFailure, + /*profilePostActions:*/ profilePostActions, + /*profilePreActions:*/ profilePreActions, /*commandLineArguments:*/ commandLineArguments, /*customSchemeNames:*/ customSchemeNames, /*environmentVariables:*/ environmentVariables, /*extensionHostIDs:*/ extensionHostIDs, + /*runPostActions:*/ runPostActions, + /*runPreActions:*/ runPreActions, /*target:*/ target, /*targetsByID:*/ targetsByID, /*targetsByKey:*/ targetsByKey, + /*testPostActions:*/ testPostActions, + /*testPreActions:*/ testPreActions, /*testOptions:*/ testOptions, /*createAutomaticSchemeInfo:*/ createAutomaticSchemeInfo ) @@ -51,25 +70,43 @@ extension Generator { extension Generator.CreateTargetAutomaticSchemeInfos { typealias Callable = ( + _ buildPostActions: [AutogenerationConfig.Action], + _ buildPreActions: [AutogenerationConfig.Action], + _ buildRunPostActionsOnFailure: Bool, + _ profilePostActions: [AutogenerationConfig.Action], + _ profilePreActions: [AutogenerationConfig.Action], _ commandLineArguments: [CommandLineArgument], _ customSchemeNames: Set, _ environmentVariables: [EnvironmentVariable], _ extensionHostIDs: [TargetID: [TargetID]], + _ runPostActions: [AutogenerationConfig.Action], + _ runPreActions: [AutogenerationConfig.Action], _ target: Target, _ targetsByID: [TargetID: Target], _ targetsByKey: [Target.Key: Target], + _ testPostActions: [AutogenerationConfig.Action], + _ testPreActions: [AutogenerationConfig.Action], _ testOptions: SchemeInfo.Test.Options?, _ createAutomaticSchemeInfo: Generator.CreateAutomaticSchemeInfo ) throws -> [SchemeInfo] static func defaultCallable( + buildPostActions: [AutogenerationConfig.Action], + buildPreActions: [AutogenerationConfig.Action], + buildRunPostActionsOnFailure: Bool, + profilePostActions: [AutogenerationConfig.Action], + profilePreActions: [AutogenerationConfig.Action], commandLineArguments: [CommandLineArgument], customSchemeNames: Set, environmentVariables: [EnvironmentVariable], extensionHostIDs: [TargetID: [TargetID]], + runPostActions: [AutogenerationConfig.Action], + runPreActions: [AutogenerationConfig.Action], target: Target, targetsByID: [TargetID: Target], targetsByKey: [Target.Key: Target], + testPostActions: [AutogenerationConfig.Action], + testPreActions: [AutogenerationConfig.Action], testOptions: SchemeInfo.Test.Options?, createAutomaticSchemeInfo: Generator.CreateAutomaticSchemeInfo ) throws -> [SchemeInfo] { @@ -93,11 +130,21 @@ extension Generator.CreateTargetAutomaticSchemeInfos { if extensionHostKeys.isEmpty { guard let schemeInfo = try createAutomaticSchemeInfo( + buildPostActions: buildPostActions, + buildPreActions: buildPreActions, + buildRunPostActionsOnFailure: + buildRunPostActionsOnFailure, + profilePostActions: profilePostActions, + profilePreActions: profilePreActions, commandLineArguments: commandLineArguments, customSchemeNames: customSchemeNames, environmentVariables: environmentVariables, extensionHost: nil, + runPostActions: runPostActions, + runPreActions: runPreActions, target: target, + testPostActions: testPostActions, + testPreActions: testPreActions, testOptions: testOptions ) else { return [] @@ -106,11 +153,21 @@ extension Generator.CreateTargetAutomaticSchemeInfos { } else { return try extensionHostKeys.compactMap { key in return try createAutomaticSchemeInfo( + buildPostActions: buildPostActions, + buildPreActions: buildPreActions, + buildRunPostActionsOnFailure: + buildRunPostActionsOnFailure, + profilePostActions: profilePostActions, + profilePreActions: profilePreActions, commandLineArguments: commandLineArguments, customSchemeNames: customSchemeNames, environmentVariables: environmentVariables, extensionHost: targetsByKey[key]!, + runPostActions: runPostActions, + runPreActions: runPreActions, target: target, + testPostActions: testPostActions, + testPreActions: testPreActions, testOptions: testOptions ) } diff --git a/tools/generators/xcschemes/src/Generator/Generator.swift b/tools/generators/xcschemes/src/Generator/Generator.swift index 95a7ba1ef..729f66186 100644 --- a/tools/generators/xcschemes/src/Generator/Generator.swift +++ b/tools/generators/xcschemes/src/Generator/Generator.swift @@ -36,7 +36,7 @@ struct Generator { .readTargetArgsAndEnvFile(arguments.targetsArgsEnvFile) let extensionHostIDs = arguments.calculateExtensionHostIDs() - let autogenerationConfigArguments = try await AutogenerationConfigArguments.parse( + let autogenerationConfig = try await AutogenerationConfig.parse( from: arguments.autogenerationConfigFile ) @@ -61,23 +61,33 @@ struct Generator { let automaticSchemeInfos = try environment.createAutomaticSchemeInfos( autogenerationMode: arguments.autogenerationMode, + buildPostActions: autogenerationConfig.buildPostActions, + buildPreActions: autogenerationConfig.buildPreActions, + buildRunPostActionsOnFailure: + autogenerationConfig.buildRunPostActionsOnFailure, + profilePostActions: autogenerationConfig.profilePostActions, + profilePreActions: autogenerationConfig.profilePreActions, commandLineArguments: commandLineArguments, customSchemeNames: Set(customSchemeInfos.map(\.name)), environmentVariables: environmentVariables, extensionHostIDs: extensionHostIDs, + runPostActions: autogenerationConfig.runPostActions, + runPreActions: autogenerationConfig.runPreActions, targets: targets, targetsByID: targetsByID, targetsByKey: targetsByKey, + testPostActions: autogenerationConfig.testPostActions, + testPreActions: autogenerationConfig.testPreActions, testOptions: .init( - appLanguage: autogenerationConfigArguments.appLanguage, - appRegion: autogenerationConfigArguments.appRegion, - codeCoverage: autogenerationConfigArguments.codeCoverage + appLanguage: autogenerationConfig.appLanguage, + appRegion: autogenerationConfig.appRegion, + codeCoverage: autogenerationConfig.codeCoverage ) ) let filteredAutomaticSchemeInfos = try automaticSchemeInfos.filter { scheme in // Apply scheme auto-generation exclude patterns - for pattern in autogenerationConfigArguments.schemeNameExcludePatterns { + for pattern in autogenerationConfig.schemeNameExcludePatterns { do { let regex = try Regex(pattern) let matches = scheme.name.matches(of: regex) diff --git a/tools/generators/xcschemes/src/Generator/GeneratorArguments.swift b/tools/generators/xcschemes/src/Generator/GeneratorArguments.swift index 53fcedb49..d605daa71 100644 --- a/tools/generators/xcschemes/src/Generator/GeneratorArguments.swift +++ b/tools/generators/xcschemes/src/Generator/GeneratorArguments.swift @@ -35,7 +35,7 @@ schemes are provided. var autogenerationMode: AutogenerationMode @Argument( - help: "Path to a file containing `AutogenerationConfigArguments` inputs.", + help: "Path to a file containing `AutogenerationConfig` inputs.", transform: { URL(fileURLWithPath: $0, isDirectory: false) } ) var autogenerationConfigFile: URL diff --git a/tools/generators/xcschemes/src/Generator/SchemeInfo.swift b/tools/generators/xcschemes/src/Generator/SchemeInfo.swift index dce3f91d5..2312958b8 100644 --- a/tools/generators/xcschemes/src/Generator/SchemeInfo.swift +++ b/tools/generators/xcschemes/src/Generator/SchemeInfo.swift @@ -71,6 +71,7 @@ struct SchemeInfo: Equatable { let enableThreadPerformanceChecker: Bool let environmentVariables: [EnvironmentVariable] let launchTarget: LaunchTarget? + let runBuildPostActionsOnFailure: Bool let storeKitConfiguration: String? let xcodeConfiguration: String? } diff --git a/tools/generators/xcschemes/test/CreateAutomaticSchemeInfo+Testing.swift b/tools/generators/xcschemes/test/CreateAutomaticSchemeInfo+Testing.swift index c780447e7..9b3a3779e 100644 --- a/tools/generators/xcschemes/test/CreateAutomaticSchemeInfo+Testing.swift +++ b/tools/generators/xcschemes/test/CreateAutomaticSchemeInfo+Testing.swift @@ -8,11 +8,20 @@ import XCScheme extension Generator.CreateAutomaticSchemeInfo { final class MockTracker { struct Called: Equatable { + let buildPostActions: [AutogenerationConfig.Action] + let buildPreActions: [AutogenerationConfig.Action] + let buildRunPostActionsOnFailure: Bool + let profilePostActions: [AutogenerationConfig.Action] + let profilePreActions: [AutogenerationConfig.Action] let commandLineArguments: [CommandLineArgument] let customSchemeNames: Set let environmentVariables: [EnvironmentVariable] let extensionHost: Target? + let runPostActions: [AutogenerationConfig.Action] + let runPreActions: [AutogenerationConfig.Action] let target: Target + let testPostActions: [AutogenerationConfig.Action] + let testPreActions: [AutogenerationConfig.Action] let testOptions: SchemeInfo.Test.Options? } @@ -39,18 +48,37 @@ extension Generator.CreateAutomaticSchemeInfo { let mocked = Self( callable: { + buildPostActions, + buildPreActions, + buildRunPostActionsOnFailure, + profilePostActions, + profilePreActions, commandLineArguments, customSchemeNames, environmentVariables, extensionHost, + runPostActions, + runPreActions, target, + testPostActions, + testPreActions, testOptions in mockTracker.called.append(.init( + buildPostActions: buildPostActions, + buildPreActions: buildPreActions, + buildRunPostActionsOnFailure: + buildRunPostActionsOnFailure, + profilePostActions: profilePostActions, + profilePreActions: profilePreActions, commandLineArguments: commandLineArguments, customSchemeNames: customSchemeNames, environmentVariables: environmentVariables, extensionHost: extensionHost, + runPostActions: runPostActions, + runPreActions: runPreActions, target: target, + testPostActions: testPostActions, + testPreActions: testPreActions, testOptions: testOptions )) return mockTracker.nextResult() diff --git a/tools/generators/xcschemes/test/CreateAutomaticSchemeInfoTests.swift b/tools/generators/xcschemes/test/CreateAutomaticSchemeInfoTests.swift index 02eb5b8c5..354990735 100644 --- a/tools/generators/xcschemes/test/CreateAutomaticSchemeInfoTests.swift +++ b/tools/generators/xcschemes/test/CreateAutomaticSchemeInfoTests.swift @@ -155,6 +155,7 @@ final class CreateAutomaticSchemeInfoTests: XCTestCase { primary: launchable, extensionHost: nil ), + runBuildPostActionsOnFailure: false, storeKitConfiguration: nil, xcodeConfiguration: nil ), @@ -238,6 +239,7 @@ final class CreateAutomaticSchemeInfoTests: XCTestCase { primary: launchable, extensionHost: extensionHost ), + runBuildPostActionsOnFailure: false, storeKitConfiguration: nil, xcodeConfiguration: nil ), @@ -317,6 +319,7 @@ final class CreateAutomaticSchemeInfoTests: XCTestCase { primary: launchable, extensionHost: nil ), + runBuildPostActionsOnFailure: false, storeKitConfiguration: nil, xcodeConfiguration: nil ), @@ -396,6 +399,7 @@ final class CreateAutomaticSchemeInfoTests: XCTestCase { primary: launchable, extensionHost: nil ), + runBuildPostActionsOnFailure: false, storeKitConfiguration: nil, xcodeConfiguration: nil ), @@ -469,6 +473,7 @@ final class CreateAutomaticSchemeInfoTests: XCTestCase { enableThreadPerformanceChecker: false, environmentVariables: baseEnvironmentVariables, launchTarget: nil, + runBuildPostActionsOnFailure: false, storeKitConfiguration: nil, xcodeConfiguration: nil ), @@ -538,6 +543,7 @@ final class CreateAutomaticSchemeInfoTests: XCTestCase { enableThreadPerformanceChecker: false, environmentVariables: [], launchTarget: nil, + runBuildPostActionsOnFailure: false, storeKitConfiguration: nil, xcodeConfiguration: nil ), @@ -610,6 +616,7 @@ final class CreateAutomaticSchemeInfoTests: XCTestCase { enableThreadPerformanceChecker: false, environmentVariables: [], launchTarget: nil, + runBuildPostActionsOnFailure: false, storeKitConfiguration: nil, xcodeConfiguration: nil ), @@ -683,6 +690,7 @@ final class CreateAutomaticSchemeInfoTests: XCTestCase { enableThreadPerformanceChecker: false, environmentVariables: [], launchTarget: nil, + runBuildPostActionsOnFailure: false, storeKitConfiguration: nil, xcodeConfiguration: nil ), @@ -755,6 +763,7 @@ final class CreateAutomaticSchemeInfoTests: XCTestCase { enableThreadPerformanceChecker: false, environmentVariables: [], launchTarget: nil, + runBuildPostActionsOnFailure: false, storeKitConfiguration: nil, xcodeConfiguration: nil ), @@ -785,6 +794,210 @@ final class CreateAutomaticSchemeInfoTests: XCTestCase { XCTAssertNoDifference(schemeInfo, expectedSchemeInfo) } + + func test_buildActions() throws { + // Arrange + + let launchable = Target( + key: "Launchable", + productType: .application, + buildableReference: .init( + blueprintIdentifier: "BLUEPRINT_IDENTIFIER_Launchable", + buildableName: "BUILDABLE_NAME_Launchable", + blueprintName: "BLUEPRINT_NAME_Launchable", + referencedContainer: "REFERENCED_CONTAINER_Launchable" + ) + ) + let buildPreActions: [AutogenerationConfig.Action] = [ + .init( + title: "Build Start", + scriptText: "echo start\n", + order: -200 + ), + ] + let buildPostActions: [AutogenerationConfig.Action] = [ + .init( + title: "Build End", + scriptText: "echo end\n", + order: 100 + ), + ] + let runPreActions: [AutogenerationConfig.Action] = [ + .init( + title: #"Run Start (DevX & "Logs")"#, + scriptText: "echo \"\"\n", + order: -100 + ), + ] + let runPostActions: [AutogenerationConfig.Action] = [ + .init( + title: #"Run End (DevX & "Logs")"#, + scriptText: "echo \"\"\n", + order: 200 + ), + ] + let testPreActions: [AutogenerationConfig.Action] = [ + .init( + title: "Test Start", + scriptText: "echo test-start\n", + order: -50 + ), + ] + let testPostActions: [AutogenerationConfig.Action] = [ + .init( + title: "Test End", + scriptText: "echo test-end\n", + order: 50 + ), + ] + let profilePreActions: [AutogenerationConfig.Action] = [ + .init( + title: "Profile Start", + scriptText: "echo profile-start\n", + order: -75 + ), + ] + let profilePostActions: [AutogenerationConfig.Action] = [ + .init( + title: "Profile End", + scriptText: "echo profile-end\n", + order: 75 + ), + ] + + let expectedSchemeInfo = SchemeInfo( + name: "BLUEPRINT_NAME_Launchable", + test: .init( + buildTargets: [], + commandLineArguments: [], + enableAddressSanitizer: false, + enableThreadSanitizer: false, + enableUBSanitizer: false, + enableMainThreadChecker: false, + enableThreadPerformanceChecker: false, + environmentVariables: [], + options: nil, + testTargets: [], + useRunArgsAndEnv: true, + xcodeConfiguration: nil + ), + run: .init( + buildTargets: [], + commandLineArguments: [], + customWorkingDirectory: nil, + enableAddressSanitizer: false, + enableThreadSanitizer: false, + enableUBSanitizer: false, + enableMainThreadChecker: false, + enableThreadPerformanceChecker: false, + environmentVariables: baseEnvironmentVariables, + launchTarget: .target( + primary: launchable, + extensionHost: nil + ), + runBuildPostActionsOnFailure: true, + storeKitConfiguration: nil, + xcodeConfiguration: nil + ), + profile: .init( + buildTargets: [], + commandLineArguments: [], + customWorkingDirectory: nil, + environmentVariables: [], + launchTarget: .target( + primary: launchable, + extensionHost: nil + ), + useRunArgsAndEnv: true, + xcodeConfiguration: nil + ), + executionActions: [ + .init( + title: "Build Start", + scriptText: "echo start\n", + action: .build, + isPreAction: true, + target: launchable, + order: -200 + ), + .init( + title: "Build End", + scriptText: "echo end\n", + action: .build, + isPreAction: false, + target: launchable, + order: 100 + ), + .init( + title: #"Run Start (DevX & "Logs")"#, + scriptText: "echo \"\"\n", + action: .run, + isPreAction: true, + target: launchable, + order: -100 + ), + .init( + title: #"Run End (DevX & "Logs")"#, + scriptText: "echo \"\"\n", + action: .run, + isPreAction: false, + target: launchable, + order: 200 + ), + .init( + title: "Test Start", + scriptText: "echo test-start\n", + action: .test, + isPreAction: true, + target: launchable, + order: -50 + ), + .init( + title: "Test End", + scriptText: "echo test-end\n", + action: .test, + isPreAction: false, + target: launchable, + order: 50 + ), + .init( + title: "Profile Start", + scriptText: "echo profile-start\n", + action: .profile, + isPreAction: true, + target: launchable, + order: -75 + ), + .init( + title: "Profile End", + scriptText: "echo profile-end\n", + action: .profile, + isPreAction: false, + target: launchable, + order: 75 + ), + ] + ) + + // Act + + let schemeInfo = try createAutomaticSchemeInfoWithDefaults( + buildPostActions: buildPostActions, + buildPreActions: buildPreActions, + buildRunPostActionsOnFailure: true, + profilePostActions: profilePostActions, + profilePreActions: profilePreActions, + runPostActions: runPostActions, + runPreActions: runPreActions, + target: launchable, + testPostActions: testPostActions, + testPreActions: testPreActions + ) + + // Assert + + XCTAssertNoDifference(schemeInfo, expectedSchemeInfo) + } } private let baseEnvironmentVariables: [EnvironmentVariable] = [ @@ -796,19 +1009,37 @@ private let baseEnvironmentVariables: [EnvironmentVariable] = [ ] private func createAutomaticSchemeInfoWithDefaults( + buildPostActions: [AutogenerationConfig.Action] = [], + buildPreActions: [AutogenerationConfig.Action] = [], + buildRunPostActionsOnFailure: Bool = false, + profilePostActions: [AutogenerationConfig.Action] = [], + profilePreActions: [AutogenerationConfig.Action] = [], commandLineArguments: [CommandLineArgument] = [], customSchemeNames: Set = [], environmentVariables: [EnvironmentVariable] = [], extensionHost: Target? = nil, + runPostActions: [AutogenerationConfig.Action] = [], + runPreActions: [AutogenerationConfig.Action] = [], target: Target, + testPostActions: [AutogenerationConfig.Action] = [], + testPreActions: [AutogenerationConfig.Action] = [], testOptions: SchemeInfo.Test.Options? = nil ) throws -> SchemeInfo? { return try Generator.CreateAutomaticSchemeInfo.defaultCallable( + buildPostActions: buildPostActions, + buildPreActions: buildPreActions, + buildRunPostActionsOnFailure: buildRunPostActionsOnFailure, + profilePostActions: profilePostActions, + profilePreActions: profilePreActions, commandLineArguments: commandLineArguments, customSchemeNames: customSchemeNames, environmentVariables: environmentVariables, extensionHost: extensionHost, + runPostActions: runPostActions, + runPreActions: runPreActions, target: target, + testPostActions: testPostActions, + testPreActions: testPreActions, testOptions: testOptions ) } diff --git a/tools/generators/xcschemes/test/CreateAutomaticSchemeInfosTests.swift b/tools/generators/xcschemes/test/CreateAutomaticSchemeInfosTests.swift index 1cee69d30..def98d157 100644 --- a/tools/generators/xcschemes/test/CreateAutomaticSchemeInfosTests.swift +++ b/tools/generators/xcschemes/test/CreateAutomaticSchemeInfosTests.swift @@ -167,6 +167,30 @@ final class CreateAutomaticSchemeInfosTests: XCTestCase { func test_createTargetAutomaticSchemeInfos() throws { // Arrange + let buildPostActions: [AutogenerationConfig.Action] = [ + .init(title: "Build End", scriptText: "echo end\n", order: 100), + ] + let buildPreActions: [AutogenerationConfig.Action] = [ + .init(title: "Build Start", scriptText: "echo start\n", order: -200), + ] + let profilePostActions: [AutogenerationConfig.Action] = [ + .init(title: "Profile End", scriptText: "echo profile-end\n", order: 75), + ] + let profilePreActions: [AutogenerationConfig.Action] = [ + .init(title: "Profile Start", scriptText: "echo profile-start\n", order: -75), + ] + let runPostActions: [AutogenerationConfig.Action] = [ + .init(title: "Run End", scriptText: "echo run-end\n", order: 200), + ] + let runPreActions: [AutogenerationConfig.Action] = [ + .init(title: "Run Start", scriptText: "echo run-start\n", order: -100), + ] + let testPostActions: [AutogenerationConfig.Action] = [ + .init(title: "Test End", scriptText: "echo test-end\n", order: 50), + ] + let testPreActions: [AutogenerationConfig.Action] = [ + .init(title: "Test Start", scriptText: "echo test-start\n", order: -50), + ] let commandLineArguments: [TargetID: [CommandLineArgument]] = [ "A": [ .init(value: "-v", isEnabled: true), @@ -210,16 +234,30 @@ final class CreateAutomaticSchemeInfosTests: XCTestCase { Generator.CreateTargetAutomaticSchemeInfos.MockTracker.Called ] = [ .init( + buildPostActions: buildPostActions, + buildPreActions: buildPreActions, + buildRunPostActionsOnFailure: true, + profilePostActions: profilePostActions, + profilePreActions: profilePreActions, commandLineArguments: [], customSchemeNames: customSchemeNames, environmentVariables: [], extensionHostIDs: extensionHostIDs, + runPostActions: runPostActions, + runPreActions: runPreActions, target: .mock(key: "C", productType: .application), + testPostActions: testPostActions, + testPreActions: testPreActions, targetsByID: targetsByID, targetsByKey: targetsByKey, testOptions: nil ), .init( + buildPostActions: buildPostActions, + buildPreActions: buildPreActions, + buildRunPostActionsOnFailure: true, + profilePostActions: profilePostActions, + profilePreActions: profilePreActions, commandLineArguments: [ .init(value: "-v", isEnabled: true), .init(value: "version", isEnabled: false), @@ -228,12 +266,21 @@ final class CreateAutomaticSchemeInfosTests: XCTestCase { customSchemeNames: customSchemeNames, environmentVariables: [], extensionHostIDs: extensionHostIDs, + runPostActions: runPostActions, + runPreActions: runPreActions, target: .mock(key: "A", productType: .messagesExtension), + testPostActions: testPostActions, + testPreActions: testPreActions, targetsByID: targetsByID, targetsByKey: targetsByKey, testOptions: nil ), .init( + buildPostActions: buildPostActions, + buildPreActions: buildPreActions, + buildRunPostActionsOnFailure: true, + profilePostActions: profilePostActions, + profilePreActions: profilePreActions, commandLineArguments: [], customSchemeNames: customSchemeNames, environmentVariables: [ @@ -241,7 +288,11 @@ final class CreateAutomaticSchemeInfosTests: XCTestCase { .init(key: "ENV VAR", value: "1", isEnabled: true), ], extensionHostIDs: extensionHostIDs, + runPostActions: runPostActions, + runPreActions: runPreActions, target: .mock(key: "B", productType: .appExtension), + testPostActions: testPostActions, + testPreActions: testPreActions, targetsByID: targetsByID, targetsByKey: targetsByKey, testOptions: nil @@ -266,11 +317,20 @@ final class CreateAutomaticSchemeInfosTests: XCTestCase { // Act let schemeInfos = try createAutomaticSchemeInfosWithDefaults( + buildPostActions: buildPostActions, + buildPreActions: buildPreActions, + buildRunPostActionsOnFailure: true, + profilePostActions: profilePostActions, + profilePreActions: profilePreActions, commandLineArguments: commandLineArguments, customSchemeNames: customSchemeNames, environmentVariables: environmentVariables, extensionHostIDs: extensionHostIDs, + runPostActions: runPostActions, + runPreActions: runPreActions, targets: targets, + testPostActions: testPostActions, + testPreActions: testPreActions, targetsByID: targetsByID, targetsByKey: targetsByKey, createTargetAutomaticSchemeInfos: @@ -289,11 +349,20 @@ final class CreateAutomaticSchemeInfosTests: XCTestCase { private func createAutomaticSchemeInfosWithDefaults( autogenerationMode: AutogenerationMode = .all, + buildPostActions: [AutogenerationConfig.Action] = [], + buildPreActions: [AutogenerationConfig.Action] = [], + buildRunPostActionsOnFailure: Bool = false, + profilePostActions: [AutogenerationConfig.Action] = [], + profilePreActions: [AutogenerationConfig.Action] = [], commandLineArguments: [TargetID: [CommandLineArgument]] = [:], customSchemeNames: Set = [], environmentVariables: [TargetID: [EnvironmentVariable]] = [:], extensionHostIDs: [TargetID : [TargetID]] = [:], + runPostActions: [AutogenerationConfig.Action] = [], + runPreActions: [AutogenerationConfig.Action] = [], targets: [Target], + testPostActions: [AutogenerationConfig.Action] = [], + testPreActions: [AutogenerationConfig.Action] = [], targetsByID: [TargetID : Target] = [:], targetsByKey: [Target.Key : Target] = [:], createTargetAutomaticSchemeInfos: Generator.CreateTargetAutomaticSchemeInfos, @@ -301,13 +370,22 @@ private func createAutomaticSchemeInfosWithDefaults( ) throws -> [SchemeInfo] { return try Generator.CreateAutomaticSchemeInfos.defaultCallable( autogenerationMode: autogenerationMode, + buildPostActions: buildPostActions, + buildPreActions: buildPreActions, + buildRunPostActionsOnFailure: buildRunPostActionsOnFailure, + profilePostActions: profilePostActions, + profilePreActions: profilePreActions, commandLineArguments: commandLineArguments, customSchemeNames: customSchemeNames, environmentVariables: environmentVariables, extensionHostIDs: extensionHostIDs, + runPostActions: runPostActions, + runPreActions: runPreActions, targets: targets, targetsByID: targetsByID, targetsByKey: targetsByKey, + testPostActions: testPostActions, + testPreActions: testPreActions, createTargetAutomaticSchemeInfos: createTargetAutomaticSchemeInfos, testOptions: testOptions ) diff --git a/tools/generators/xcschemes/test/CreateCustomSchemeInfosTests.swift b/tools/generators/xcschemes/test/CreateCustomSchemeInfosTests.swift index 8a44de8b7..8e4a06c0c 100644 --- a/tools/generators/xcschemes/test/CreateCustomSchemeInfosTests.swift +++ b/tools/generators/xcschemes/test/CreateCustomSchemeInfosTests.swift @@ -1,3 +1,4 @@ +import Foundation import PBXProj import XCScheme import XCTest @@ -5,6 +6,142 @@ import XCTest @testable import xcschemes final class CreateCustomSchemeInfosTests: XCTestCase { + // MARK: - defaultCallable + + func test_defaultCallable_parsesCustomSchemeInfo() async throws { + let parsed = try await createCustomSchemeInfos() + + XCTAssertEqual( + parsed.schemeInfos, + [ + .mock( + name: "Scheme", + test: .mock( + buildTargets: [parsed.testBuildTarget], + commandLineArguments: [ + .init( + value: "--test-target", + isEnabled: false, + isLiteralString: true + ), + ], + enableAddressSanitizer: true, + enableThreadSanitizer: false, + enableUBSanitizer: true, + enableMainThreadChecker: false, + enableThreadPerformanceChecker: true, + environmentVariables: .defaultEnvironmentVariables + [ + .init(key: "TEST_ENV", value: "2", isEnabled: false), + ], + options: .init( + appLanguage: "en", + appRegion: "US", + codeCoverage: true + ), + testTargets: [ + .init(target: parsed.testTarget, isEnabled: true), + ], + useRunArgsAndEnv: false, + xcodeConfiguration: "TestCfg" + ), + run: .mock( + buildTargets: [parsed.runBuildTarget], + commandLineArguments: [ + .init( + value: "--run-target", + isEnabled: true, + isLiteralString: true + ), + ], + customWorkingDirectory: "/tmp/run", + enableMainThreadChecker: true, + enableThreadPerformanceChecker: false, + enableAddressSanitizer: false, + enableThreadSanitizer: true, + enableUBSanitizer: false, + environmentVariables: .defaultEnvironmentVariables + [ + .init(key: "RUN_ENV", value: "1", isEnabled: true), + ], + launchTarget: .target( + primary: parsed.runTarget, + extensionHost: parsed.extensionHost + ), + runBuildPostActionsOnFailure: false, + storeKitConfiguration: parsed.expectedStoreKitConfiguration, + xcodeConfiguration: "RunCfg" + ), + profile: .mock( + commandLineArguments: [ + .init( + value: "profiling\narg", + isEnabled: true, + isLiteralString: true + ), + ], + customWorkingDirectory: "/tmp/profile", + environmentVariables: [ + .init( + key: "PROFILE_KEY", + value: "profile\nvalue", + isEnabled: true + ), + ], + launchTarget: .path("/tmp/Profile.app"), + useRunArgsAndEnv: false, + xcodeConfiguration: "ProfileCfg" + ), + executionActions: [ + .init( + title: "Build Start\nTitle", + scriptText: "echo build\nstart", + action: .build, + isPreAction: true, + target: parsed.runTarget, + order: -10 + ), + .init( + title: "Run End", + scriptText: "echo run end", + action: .run, + isPreAction: false, + target: parsed.runTarget, + order: 10 + ), + .init( + title: "Test Start", + scriptText: "echo test start", + action: .test, + isPreAction: true, + target: parsed.testTarget, + order: nil + ), + .init( + title: "Profile End", + scriptText: "echo profile end", + action: .profile, + isPreAction: false, + target: parsed.runTarget, + order: 20 + ), + ] + ), + ] + ) + } + + func test_defaultCallable_parsesRunBuildPostActionsOnFailure() async throws { + let parsed = try await createCustomSchemeInfos( + runBuildPostActionsOnFailure: true + ) + + XCTAssertEqual( + parsed.schemeInfos.map(\.run.runBuildPostActionsOnFailure), + [true] + ) + } + + // MARK: - mergingEnvironmentVariables + func test_merging_environment_variables() throws { let targets: [SchemeInfo.TestTarget] = [ .init( @@ -77,6 +214,8 @@ final class CreateCustomSchemeInfosTests: XCTestCase { ) } + // MARK: - URL.relativize + func test_url_relativize() { typealias TestCase = (dest: URL, source: URL, expected: String?) let testCases: [TestCase] = [ @@ -105,3 +244,231 @@ final class CreateCustomSchemeInfosTests: XCTestCase { } } } + +private struct ParsedCustomSchemeInfos { + let expectedStoreKitConfiguration: String? + let schemeInfos: [SchemeInfo] + let extensionHost: Target + let runBuildTarget: Target + let runTarget: Target + let testBuildTarget: Target + let testTarget: Target +} + +private func createCustomSchemeInfos( + runBuildPostActionsOnFailure: Bool = false +) async throws -> ParsedCustomSchemeInfos { + let directory = FileManager.default.temporaryDirectory + .appendingPathComponent(UUID().uuidString, isDirectory: true) + try FileManager.default.createDirectory( + at: directory, + withIntermediateDirectories: true + ) + defer { try? FileManager.default.removeItem(at: directory) } + + let schemesDirectory = directory.appendingPathComponent( + "Project.xcodeproj/xcshareddata/xcschemes", + isDirectory: true + ) + try FileManager.default.createDirectory( + at: schemesDirectory, + withIntermediateDirectories: true + ) + + let runTarget = makeTarget( + "run_extension", + productType: .appExtension + ) + let extensionHost = makeTarget( + "host_app", + productType: .application + ) + let runBuildTarget = makeTarget("run_build_target") + let testBuildTarget = makeTarget("test_build_target") + let testTarget = makeTarget( + "test_target", + productType: .unitTestBundle + ) + let targetsByID = Dictionary( + uniqueKeysWithValues: [ + runTarget, + extensionHost, + runBuildTarget, + testBuildTarget, + testTarget, + ].map { ($0.key.sortedIds.first!, $0) } + ) + + let customSchemesFile = directory.appendingPathComponent( + "custom_schemes_file" + ) + try writeLines([ + "1", + "Scheme", + "1", + "test_target", + "1", + "test_build_target", + "", + "-1", + "-1", + "1", + "0", + "1", + "0", + "1", + "0", + "1", + "en", + "US", + "1", + "TestCfg", + "run_build_target", + "", + "-1", + "-1", + "1", + "0", + "1", + "0", + "1", + "0", + "Configs/Fixture.storekit", + "RunCfg", + "0", + "run_extension", + "host_app", + "/tmp/run", + runBuildPostActionsOnFailure ? "1" : "0", + "", + "1", + "profiling\0arg", + "1", + "1", + "1", + "PROFILE_KEY", + "profile\0value", + "1", + "0", + "0", + "ProfileCfg", + "1", + "/tmp/Profile.app", + "/tmp/profile", + ], to: customSchemesFile) + + let executionActionsFile = directory.appendingPathComponent( + "execution_actions_file" + ) + try writeLines([ + "Scheme", + "build", + "1", + "Build Start\0Title", + "echo build\0start", + "run_extension", + "-10", + "Scheme", + "run", + "0", + "Run End", + "echo run end", + "run_extension", + "10", + "Scheme", + "test", + "1", + "Test Start", + "echo test start", + "test_target", + "", + "Scheme", + "profile", + "0", + "Profile End", + "echo profile end", + "run_extension", + "20", + ], to: executionActionsFile) + + let expectedStoreKitConfiguration = URL( + filePath: "Configs/Fixture.storekit", + relativeTo: directory + ).relativize(from: schemesDirectory) + + let schemeInfos = try await Generator.CreateCustomSchemeInfos + .defaultCallable( + commandLineArguments: [ + runTarget.key.sortedIds.first!: [ + .init( + value: "--run-target", + isEnabled: true, + isLiteralString: true + ), + ], + testTarget.key.sortedIds.first!: [ + .init( + value: "--test-target", + isEnabled: false, + isLiteralString: true + ), + ], + ], + customSchemesFile: customSchemesFile, + environmentVariables: [ + runTarget.key.sortedIds.first!: [ + .init(key: "RUN_ENV", value: "1", isEnabled: true), + ], + testTarget.key.sortedIds.first!: [ + .init(key: "TEST_ENV", value: "2", isEnabled: false), + ], + ], + executionActionsFile: executionActionsFile, + extensionHostIDs: [ + runTarget.key.sortedIds.first!: [ + extensionHost.key.sortedIds.first!, + ], + ], + schemesDirectory: schemesDirectory, + targetsByID: targetsByID, + workspace: directory + ) + + return ParsedCustomSchemeInfos( + expectedStoreKitConfiguration: expectedStoreKitConfiguration, + schemeInfos: schemeInfos, + extensionHost: extensionHost, + runBuildTarget: runBuildTarget, + runTarget: runTarget, + testBuildTarget: testBuildTarget, + testTarget: testTarget + ) +} + +private func makeTarget( + _ id: String, + productType: PBXProductType = .staticLibrary +) -> Target { + let targetID = TargetID(id) + return Target( + key: .init([targetID]), + productType: productType, + buildableReference: .init( + blueprintIdentifier: "\(id)_blueprintIdentifier", + buildableName: "\(id)_buildableName", + blueprintName: id, + referencedContainer: "container:/tmp/\(id).xcodeproj" + ) + ) +} + +private func writeLines( + _ lines: [String], + to url: URL +) throws { + try (lines.joined(separator: "\n") + "\n").write( + to: url, + atomically: true, + encoding: .utf8 + ) +} diff --git a/tools/generators/xcschemes/test/CreateSchemeTests.swift b/tools/generators/xcschemes/test/CreateSchemeTests.swift index 5a1850b9c..ab9a4737f 100644 --- a/tools/generators/xcschemes/test/CreateSchemeTests.swift +++ b/tools/generators/xcschemes/test/CreateSchemeTests.swift @@ -6,6 +6,107 @@ import XCTest @testable import xcschemes final class CreateSchemeTests: XCTestCase { + func test_buildAction_includesAutogeneratedBuildActions() throws { + // Arrange + + let target = Target.mock( + key: "App", + productType: .application, + buildableReference: buildableReference( + name: "App", + referencedContainer: "container:/tmp/App.xcodeproj" + ) + ) + let expectedBuildAction = CreateBuildAction.defaultCallable( + entries: [ + .init( + buildableReference: target.buildableReference, + buildFor: [.running, .profiling, .analyzing] + ), + ], + postActions: [ + .init( + title: #"Build End (DevX & "Logs")"#, + escapedScriptText: "echo \"\"\n".schemeXmlEscaped, + expandVariablesBasedOn: target.buildableReference + ), + ], + preActions: [ + .init( + title: #"Build Start (DevX & "Logs")"#, + escapedScriptText: "echo \"\"\n".schemeXmlEscaped, + expandVariablesBasedOn: target.buildableReference + ), + .init( + title: "Initialize Bazel Build Output Groups File", + escapedScriptText: #""" +mkdir -p "${BUILD_MARKER_FILE%/*}" +touch "$BUILD_MARKER_FILE" + +"""#.schemeXmlEscaped, + expandVariablesBasedOn: target.buildableReference + ), + .init( + title: "Prepare BazelDependencies", + escapedScriptText: #""" +mkdir -p "$PROJECT_DIR" + +if [[ "${ENABLE_ADDRESS_SANITIZER:-}" == "YES" || \ + "${ENABLE_THREAD_SANITIZER:-}" == "YES" || \ + "${ENABLE_UNDEFINED_BEHAVIOR_SANITIZER:-}" == "YES" ]] +then + # TODO: Support custom toolchains once clang.sh supports them + cd "$INTERNAL_DIR" || exit 1 + ln -shfF "$DEVELOPER_DIR/Toolchains/XcodeDefault.xctoolchain/usr/lib" lib +fi + +"""#.schemeXmlEscaped, + expandVariablesBasedOn: target.buildableReference + ), + ], + runPostActionsOnFailure: true + ) + + // Act + + let scheme = try createSchemeWithDefaults( + schemeInfo: .mock( + name: "Scheme", + run: .mock( + launchTarget: .target(primary: target, extensionHost: nil), + runBuildPostActionsOnFailure: true + ), + profile: .mock( + launchTarget: .target(primary: target, extensionHost: nil) + ), + executionActions: [ + .init( + title: #"Build Start (DevX & "Logs")"#, + scriptText: "echo \"\"\n", + action: .build, + isPreAction: true, + target: target, + order: -200 + ), + .init( + title: #"Build End (DevX & "Logs")"#, + scriptText: "echo \"\"\n", + action: .build, + isPreAction: false, + target: target, + order: 100 + ), + ] + ) + ) + + let buildAction = try XCTUnwrap(extractBuildAction(from: scheme)) + + // Assert + + XCTAssertNoDifference(buildAction, expectedBuildAction) + } + func test_testAction_usesFirstTestableForCustomLLDBInitFile() throws { // Arrange @@ -144,6 +245,181 @@ final class CreateSchemeTests: XCTestCase { XCTAssertNoDifference(launchAction, expectedLaunchAction) } + + func test_runTestAndProfileActions_includeAutogeneratedActions() throws { + // Arrange + + let target = Target.mock( + key: "App", + productType: .application, + buildableReference: buildableReference( + name: "App", + referencedContainer: "container:/tmp/App.xcodeproj" + ) + ) + let runPreAction = ExecutionAction( + title: #"Run Start (DevX & "Logs")"#, + escapedScriptText: "echo \"\"\n".schemeXmlEscaped, + expandVariablesBasedOn: target.buildableReference + ) + let runPostAction = ExecutionAction( + title: #"Run End (DevX & "Logs")"#, + escapedScriptText: "echo \"\"\n".schemeXmlEscaped, + expandVariablesBasedOn: target.buildableReference + ) + let testPreAction = ExecutionAction( + title: #"Test Start (DevX & "Logs")"#, + escapedScriptText: "echo \"\"\n".schemeXmlEscaped, + expandVariablesBasedOn: target.buildableReference + ) + let testPostAction = ExecutionAction( + title: #"Test End (DevX & "Logs")"#, + escapedScriptText: "echo \"\"\n".schemeXmlEscaped, + expandVariablesBasedOn: target.buildableReference + ) + let profilePreAction = ExecutionAction( + title: #"Profile Start (DevX & "Logs")"#, + escapedScriptText: "echo \"\"\n".schemeXmlEscaped, + expandVariablesBasedOn: target.buildableReference + ) + let profilePostAction = ExecutionAction( + title: #"Profile End (DevX & "Logs")"#, + escapedScriptText: "echo \"\"\n".schemeXmlEscaped, + expandVariablesBasedOn: target.buildableReference + ) + let expectedLaunchAction = CreateLaunchAction.defaultCallable( + buildConfiguration: "Debug", + commandLineArguments: [], + customLLDBInitFile: "/tmp/App.xcodeproj/rules_xcodeproj/bazel.lldbinit", + customWorkingDirectory: nil, + enableAddressSanitizer: false, + enableThreadSanitizer: false, + enableUBSanitizer: false, + enableMainThreadChecker: false, + enableThreadPerformanceChecker: false, + environmentVariables: [], + postActions: [runPostAction], + preActions: [ + runPreAction, + updateLldbInitAndCopyDSYMsAction( + buildableReference: target.buildableReference + ), + ], + runnable: .plain(buildableReference: target.buildableReference), + storeKitConfiguration: nil + ) + let expectedTestAction = CreateTestAction.defaultCallable( + appLanguage: nil, + appRegion: nil, + codeCoverage: false, + buildConfiguration: "Debug", + commandLineArguments: [], + customLLDBInitFile: nil, + enableAddressSanitizer: false, + enableThreadSanitizer: false, + enableUBSanitizer: false, + enableMainThreadChecker: false, + enableThreadPerformanceChecker: false, + environmentVariables: [], + expandVariablesBasedOn: nil, + postActions: [testPostAction], + preActions: [testPreAction], + testables: [], + useLaunchSchemeArgsEnv: true + ) + let expectedProfileAction = CreateProfileAction.defaultCallable( + buildConfiguration: "Debug", + commandLineArguments: [], + customLLDBInitFile: "/tmp/App.xcodeproj/rules_xcodeproj/bazel.lldbinit", + customWorkingDirectory: nil, + environmentVariables: [], + postActions: [profilePostAction], + preActions: [ + profilePreAction, + updateLldbInitAndCopyDSYMsAction( + buildableReference: target.buildableReference + ), + ], + useLaunchSchemeArgsEnv: true, + runnable: .plain(buildableReference: target.buildableReference) + ) + + // Act + + let scheme = try createSchemeWithDefaults( + schemeInfo: .mock( + name: "Scheme", + test: .mock(), + run: .mock( + launchTarget: .target(primary: target, extensionHost: nil), + runBuildPostActionsOnFailure: true + ), + profile: .mock( + launchTarget: .target(primary: target, extensionHost: nil) + ), + executionActions: [ + .init( + title: #"Run Start (DevX & "Logs")"#, + scriptText: "echo \"\"\n", + action: .run, + isPreAction: true, + target: target, + order: -10 + ), + .init( + title: #"Run End (DevX & "Logs")"#, + scriptText: "echo \"\"\n", + action: .run, + isPreAction: false, + target: target, + order: 10 + ), + .init( + title: #"Test Start (DevX & "Logs")"#, + scriptText: "echo \"\"\n", + action: .test, + isPreAction: true, + target: target, + order: -20 + ), + .init( + title: #"Test End (DevX & "Logs")"#, + scriptText: "echo \"\"\n", + action: .test, + isPreAction: false, + target: target, + order: 20 + ), + .init( + title: #"Profile Start (DevX & "Logs")"#, + scriptText: "echo \"\"\n", + action: .profile, + isPreAction: true, + target: target, + order: -30 + ), + .init( + title: #"Profile End (DevX & "Logs")"#, + scriptText: "echo \"\"\n", + action: .profile, + isPreAction: false, + target: target, + order: 30 + ), + ] + ) + ) + + let launchAction = try XCTUnwrap(extractLaunchAction(from: scheme)) + let testAction = try XCTUnwrap(extractTestAction(from: scheme)) + let profileAction = try XCTUnwrap(extractProfileAction(from: scheme)) + + // Assert + + XCTAssertNoDifference(launchAction, expectedLaunchAction) + XCTAssertNoDifference(testAction, expectedTestAction) + XCTAssertNoDifference(profileAction, expectedProfileAction) + } } private func createSchemeWithDefaults( @@ -167,6 +443,10 @@ private func extractTestAction(from scheme: String) -> String? { extractElement(named: "TestAction", from: scheme) } +private func extractBuildAction(from scheme: String) -> String? { + extractElement(named: "BuildAction", from: scheme) +} + private func extractProfileAction(from scheme: String) -> String? { extractElement(named: "ProfileAction", from: scheme) } diff --git a/tools/generators/xcschemes/test/CreateTargetAutomaticSchemeInfos+Testing.swift b/tools/generators/xcschemes/test/CreateTargetAutomaticSchemeInfos+Testing.swift index ed0098fd3..8a3598392 100644 --- a/tools/generators/xcschemes/test/CreateTargetAutomaticSchemeInfos+Testing.swift +++ b/tools/generators/xcschemes/test/CreateTargetAutomaticSchemeInfos+Testing.swift @@ -8,11 +8,20 @@ import XCScheme extension Generator.CreateTargetAutomaticSchemeInfos { final class MockTracker { struct Called: Equatable { + let buildPostActions: [AutogenerationConfig.Action] + let buildPreActions: [AutogenerationConfig.Action] + let buildRunPostActionsOnFailure: Bool + let profilePostActions: [AutogenerationConfig.Action] + let profilePreActions: [AutogenerationConfig.Action] let commandLineArguments: [CommandLineArgument] let customSchemeNames: Set let environmentVariables: [EnvironmentVariable] let extensionHostIDs: [TargetID: [TargetID]] + let runPostActions: [AutogenerationConfig.Action] + let runPreActions: [AutogenerationConfig.Action] let target: Target + let testPostActions: [AutogenerationConfig.Action] + let testPreActions: [AutogenerationConfig.Action] let targetsByID: [TargetID: Target] let targetsByKey: [Target.Key: Target] let testOptions: SchemeInfo.Test.Options? @@ -43,21 +52,40 @@ extension Generator.CreateTargetAutomaticSchemeInfos { createAutomaticSchemeInfo: Generator.Stubs.createAutomaticSchemeInfo, callable: { + buildPostActions, + buildPreActions, + buildRunPostActionsOnFailure, + profilePostActions, + profilePreActions, commandLineArguments, customSchemeNames, environmentVariables, extensionHostIDs, + runPostActions, + runPreActions, target, targetsByID, targetsByKey, + testPostActions, + testPreActions, testOptions, _ in mockTracker.called.append(.init( + buildPostActions: buildPostActions, + buildPreActions: buildPreActions, + buildRunPostActionsOnFailure: + buildRunPostActionsOnFailure, + profilePostActions: profilePostActions, + profilePreActions: profilePreActions, commandLineArguments: commandLineArguments, customSchemeNames: customSchemeNames, environmentVariables: environmentVariables, extensionHostIDs: extensionHostIDs, + runPostActions: runPostActions, + runPreActions: runPreActions, target: target, + testPostActions: testPostActions, + testPreActions: testPreActions, targetsByID: targetsByID, targetsByKey: targetsByKey, testOptions: testOptions diff --git a/tools/generators/xcschemes/test/SchemeInfo+Testing.swift b/tools/generators/xcschemes/test/SchemeInfo+Testing.swift index 35df0ce70..1be140fc2 100644 --- a/tools/generators/xcschemes/test/SchemeInfo+Testing.swift +++ b/tools/generators/xcschemes/test/SchemeInfo+Testing.swift @@ -54,6 +54,7 @@ extension SchemeInfo.Run { enableUBSanitizer: Bool = false, environmentVariables: [EnvironmentVariable] = [], launchTarget: SchemeInfo.LaunchTarget? = nil, + runBuildPostActionsOnFailure: Bool = false, storeKitConfiguration: String? = nil, xcodeConfiguration: String? = nil ) -> Self { @@ -68,6 +69,8 @@ extension SchemeInfo.Run { enableThreadPerformanceChecker: enableThreadPerformanceChecker, environmentVariables: environmentVariables, launchTarget: launchTarget, + runBuildPostActionsOnFailure: + runBuildPostActionsOnFailure, storeKitConfiguration: storeKitConfiguration, xcodeConfiguration: xcodeConfiguration ) diff --git a/xcodeproj/internal/xcodeproj_rule.bzl b/xcodeproj/internal/xcodeproj_rule.bzl index 7aa25315d..9116e99c3 100644 --- a/xcodeproj/internal/xcodeproj_rule.bzl +++ b/xcodeproj/internal/xcodeproj_rule.bzl @@ -203,27 +203,6 @@ https://github.com/MobileNativeFoundation/rules_xcodeproj/issues/new?template=bu # Actions -def _write_autogeneration_config_file( - actions, - config, - name): - autogeneration_config_file = actions.declare_file( - "{}-autogeneration-config-file".format(name), - ) - - args = actions.args() - args.set_param_file_format("multiline") - - args.add_all(config.get("test_options", ["", "", False])) - args.add_all( - config.get("scheme_name_exclude_patterns", []), - omit_if_empty = False, - terminate_with = "", - ) - actions.write(autogeneration_config_file, args) - - return autogeneration_config_file - def _write_bazel_integration_files( *, actions, @@ -547,16 +526,10 @@ def _write_schemes( top_level_deps = top_level_deps, ) - autogeneration_config_file = _write_autogeneration_config_file( - actions = actions, - config = autogeneration_config, - name = name, - ) - return xcschemes_execution.write_schemes( actions = actions, autogeneration_mode = autogeneration_mode, - autogeneration_config_file = autogeneration_config_file, + autogeneration_config = autogeneration_config, default_xcode_configuration = default_xcode_configuration, colorize = colorize, consolidation_maps = consolidation_maps, diff --git a/xcodeproj/internal/xcschemes/xcscheme_infos.bzl b/xcodeproj/internal/xcschemes/xcscheme_infos.bzl index 9aca1d8f1..232a24130 100644 --- a/xcodeproj/internal/xcschemes/xcscheme_infos.bzl +++ b/xcodeproj/internal/xcschemes/xcscheme_infos.bzl @@ -145,6 +145,7 @@ def _make_run( env = None, env_include_defaults = TRUE_ARG, launch_target = _make_launch_target(), + run_build_post_actions_on_failure = FALSE_ARG, storekit_configuration = EMPTY_STRING, xcode_configuration = EMPTY_STRING): return struct( @@ -154,6 +155,7 @@ def _make_run( env = env, env_include_defaults = env_include_defaults, launch_target = launch_target, + run_build_post_actions_on_failure = run_build_post_actions_on_failure, storekit_configuration = storekit_configuration, xcode_configuration = xcode_configuration, ) @@ -636,6 +638,9 @@ def _run_info_from_dict( env = _env_infos_from_dict(run["env"]), env_include_defaults = run["env_include_defaults"], launch_target = launch_target, + run_build_post_actions_on_failure = ( + run["run_build_post_actions_on_failure"] + ), storekit_configuration = _storekit_configuration_info( run["storekit_configuration"], storekit_configurations_map, diff --git a/xcodeproj/internal/xcschemes/xcscheme_labels.bzl b/xcodeproj/internal/xcschemes/xcscheme_labels.bzl index 0962fac64..20deea00b 100644 --- a/xcodeproj/internal/xcschemes/xcscheme_labels.bzl +++ b/xcodeproj/internal/xcschemes/xcscheme_labels.bzl @@ -111,6 +111,9 @@ def _resolve_run_labels(run): env = run.env, env_include_defaults = run.env_include_defaults, launch_target = _resolve_launch_target_labels(run.launch_target), + run_build_post_actions_on_failure = ( + run.run_build_post_actions_on_failure + ), storekit_configuration = _resolve_label(run.storekit_configuration), xcode_configuration = run.xcode_configuration, ) diff --git a/xcodeproj/internal/xcschemes/xcschemes_execution.bzl b/xcodeproj/internal/xcschemes/xcschemes_execution.bzl index 0a76bb6b6..133ca307d 100644 --- a/xcodeproj/internal/xcschemes/xcschemes_execution.bzl +++ b/xcodeproj/internal/xcschemes/xcschemes_execution.bzl @@ -32,7 +32,7 @@ def _write_schemes( *, actions, autogeneration_mode, - autogeneration_config_file, + autogeneration_config, colorize, consolidation_maps, default_xcode_configuration, @@ -51,7 +51,8 @@ def _write_schemes( actions: `ctx.actions`. autogeneration_mode: Specifies how Xcode schemes are automatically generated. - autogeneration_config_file: A `File` containing `AutogenerationConfigArguments` inputs. + autogeneration_config: A `dict` of + `AutogenerationConfig` inputs. colorize: A `bool` indicating whether to colorize the output. consolidation_maps: A `list` of `File`s containing target consolidation maps. @@ -98,6 +99,9 @@ def _write_schemes( custom_schemes_file = actions.declare_file( "{}_pbxproj_partials/custom_schemes_file".format(generator_name), ) + autogeneration_config_file = actions.declare_file( + "{}-autogeneration-config-file".format(generator_name), + ) inputs = consolidation_maps + [ autogeneration_config_file, @@ -120,6 +124,9 @@ def _write_schemes( custom_scheme_args = actions.args() custom_scheme_args.set_param_file_format("multiline") + autogeneration_config_args = actions.args() + autogeneration_config_args.set_param_file_format("multiline") + # outputDirectory args.add_all([output], expand_directories = False) @@ -181,6 +188,44 @@ def _write_schemes( map_each = _null_newlines, ) + # AutogenerationConfig + + autogeneration_config_args.add_all( + autogeneration_config["test_options"], + ) + for pre_post_actions_key in ( + "build_pre_actions", + "build_post_actions", + ): + pre_post_actions = autogeneration_config[pre_post_actions_key] + autogeneration_config_args.add(len(pre_post_actions) // 3) + autogeneration_config_args.add_all( + pre_post_actions, + map_each = _null_newlines, + ) + autogeneration_config_args.add_all( + autogeneration_config["build_run_post_actions_on_failure"], + ) + for pre_post_actions_key in ( + "profile_pre_actions", + "profile_post_actions", + "run_pre_actions", + "run_post_actions", + "test_pre_actions", + "test_post_actions", + ): + pre_post_actions = autogeneration_config[pre_post_actions_key] + autogeneration_config_args.add(len(pre_post_actions) // 3) + autogeneration_config_args.add_all( + pre_post_actions, + map_each = _null_newlines, + ) + autogeneration_config_args.add_all( + autogeneration_config["scheme_name_exclude_patterns"], + omit_if_empty = False, + terminate_with = "", + ) + # CreateCustomSchemeInfos def _add_args(args): @@ -351,6 +396,8 @@ def _write_schemes( scheme_name = scheme_name, ) + custom_scheme_args.add(info.run.run_build_post_actions_on_failure) + # Profile _add_build_targets( @@ -378,6 +425,7 @@ def _write_schemes( 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.write(autogeneration_config_file, autogeneration_config_args) actions.run( arguments = [args], diff --git a/xcodeproj/xcodeproj.bzl b/xcodeproj/xcodeproj.bzl index ad60147a0..76f6a13a5 100644 --- a/xcodeproj/xcodeproj.bzl +++ b/xcodeproj/xcodeproj.bzl @@ -5,6 +5,7 @@ load( _default_project_options = "project_options", ) load("//xcodeproj:top_level_target.bzl", "top_level_target") +load("//xcodeproj:xcschemes.bzl", _xcschemes = "xcschemes") load("//xcodeproj/internal:bazel_labels.bzl", "bazel_labels") load("//xcodeproj/internal:xcodeproj_runner.bzl", "xcodeproj_runner") load( @@ -40,7 +41,7 @@ def xcodeproj( project_name = None, project_options = None, scheme_autogeneration_mode = "auto", - scheme_autogeneration_config = {}, + scheme_autogeneration_config = None, target_name_mode = "auto", top_level_targets, tvos_device_cpus = "arm64", @@ -374,6 +375,8 @@ def xcodeproj( project_name = name if not project_options: project_options = _default_project_options() + if not scheme_autogeneration_config: + scheme_autogeneration_config = _xcschemes.autogeneration_config() if not xcode_configurations: xcode_configurations = {"Debug": {}} diff --git a/xcodeproj/xcschemes.bzl b/xcodeproj/xcschemes.bzl index 1590e1edb..76f5cb01e 100644 --- a/xcodeproj/xcschemes.bzl +++ b/xcodeproj/xcschemes.bzl @@ -212,6 +212,7 @@ def _run( env = "inherit", env_include_defaults = True, launch_target = None, + run_build_post_actions_on_failure = False, storekit_configuration = None, xcode_configuration = None): """Defines the Run action. @@ -336,6 +337,8 @@ def _run( `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`). + run_build_post_actions_on_failure: Whether build post-actions should + run even when the scheme's `BuildAction` fails. storekit_configuration: A StoreKit configuration file for use with [StoreKit Testing](https://developer.apple.com/documentation/xcode/setting-up-storekit-testing-in-xcode). @@ -356,6 +359,9 @@ def _run( env = env or {}, env_include_defaults = TRUE_ARG if env_include_defaults else FALSE_ARG, launch_target = launch_target, + run_build_post_actions_on_failure = ( + TRUE_ARG if run_build_post_actions_on_failure else FALSE_ARG + ), storekit_configuration = storekit_configuration, xcode_configuration = xcode_configuration or "", ) @@ -1232,28 +1238,158 @@ def _test_options( ), ) -def _autogeneration_test(*, options = None): +def _autogeneration_profile(*, post_actions = [], pre_actions = []): + """Creates a value for the `profile` argument of `xcschemes.autogeneration_config`. + + Args: + post_actions: Profile post-actions for autogenerated schemes. + + Defaults to `[]`. + pre_actions: Profile pre-actions for autogenerated schemes. + + Defaults to `[]`. + + Returns: + An opaque value for the + [`profile`](user-content-xcschemes.autogeneration_config-profile) + argument of `xcschemes.autogeneration_config`. + """ + for action in post_actions or []: + if action.for_build: + fail(""" +`post_actions` for `xcschemes.autogeneration.profile` must use `xcschemes.pre_post_actions.launch_script`. +""") + + for action in pre_actions or []: + if action.for_build: + fail(""" +`pre_actions` for `xcschemes.autogeneration.profile` must use `xcschemes.pre_post_actions.launch_script`. +""") + + return struct( + post_actions = post_actions or [], + pre_actions = pre_actions or [], + ) + +def _autogeneration_run( + *, + post_actions = [], + pre_actions = [], + run_build_post_actions_on_failure = False): + """Creates a value for the `run` argument of `xcschemes.autogeneration_config`. + + Args: + post_actions: Build and run post-actions for autogenerated schemes. + + Defaults to `[]`. + pre_actions: Build and run pre-actions for autogenerated schemes. + + Defaults to `[]`. + run_build_post_actions_on_failure: Whether autogenerated build + post-actions should run even when the build fails. + + Returns: + An opaque value for the + [`run`](user-content-xcschemes.autogeneration_config-run) + argument of `xcschemes.autogeneration_config`. + """ + return struct( + post_actions = post_actions or [], + pre_actions = pre_actions or [], + run_build_post_actions_on_failure = ( + TRUE_ARG if run_build_post_actions_on_failure else FALSE_ARG + ), + ) + +def _autogeneration_test(*, options = None, post_actions = [], pre_actions = []): """Creates a value for the `test` argument of `xcschemes.autogeneration_config`. Args: options: Test options for autogeneration. Defaults to `None`. + post_actions: Test post-actions for autogenerated schemes. + + Defaults to `[]`. + pre_actions: Test pre-actions for autogenerated schemes. + + Defaults to `[]`. Returns: An opaque value for the [`test`](user-content-xcschemes.autogeneration_config-test) argument of `xcschemes.autogeneration_config`. """ + for action in post_actions or []: + if action.for_build: + fail(""" +`post_actions` for `xcschemes.autogeneration.test` must use `xcschemes.pre_post_actions.launch_script`. +""") + + for action in pre_actions or []: + if action.for_build: + fail(""" +`pre_actions` for `xcschemes.autogeneration.test` must use `xcschemes.pre_post_actions.launch_script`. +""") return struct( + post_actions = post_actions or [], + pre_actions = pre_actions or [], test_options = options, ) -def _autogeneration_config(*, scheme_name_exclude_patterns = None, test = None): +def _autogeneration_config( + *, + profile = None, + run = None, + scheme_name_exclude_patterns = None, + test = None): """Creates a value for the [`scheme_autogeneration_config`](xcodeproj-scheme_autogeneration_config) attribute of `xcodeproj`. Args: + profile: Options to use for the profile action. + + Example: + + ```starlark + xcodeproj( + ... + scheme_autogeneration_config = xcschemes.autogeneration_config( + profile = xcschemes.autogeneration.profile( + pre_actions = [ + xcschemes.pre_post_actions.launch_script( + title = "Profile Start", + script_text = "echo profile", + ), + ], + ), + ), + ) + ``` + + run: Options to use for the build and run actions. + + Example: + + ```starlark + xcodeproj( + ... + scheme_autogeneration_config = xcschemes.autogeneration_config( + run = xcschemes.autogeneration.run( + pre_actions = [ + xcschemes.pre_post_actions.build_script( + title = "Build Start", + script_text = "echo build start", + ), + xcschemes.pre_post_actions.launch_script( + title = "Run Start", + script_text = "echo run", + ), + ], + ), + ), + ) + ``` scheme_name_exclude_patterns: A `list` of regex patterns used to skip creating matching autogenerated schemes. @@ -1293,11 +1429,92 @@ def _autogeneration_config(*, scheme_name_exclude_patterns = None, test = None): Returns: An opaque value for the [`scheme_autogeneration_config`](xcodeproj-scheme_autogeneration_config) attribute of `xcodeproj`. """ - d = {} + d = { + "build_post_actions": [], + "build_pre_actions": [], + "build_run_post_actions_on_failure": [FALSE_ARG], + "profile_post_actions": [], + "profile_pre_actions": [], + "run_post_actions": [], + "run_pre_actions": [], + "scheme_name_exclude_patterns": [], + "test_options": ["", "", FALSE_ARG], + "test_post_actions": [], + "test_pre_actions": [], + } + if run: + d["build_run_post_actions_on_failure"] = [ + run.run_build_post_actions_on_failure, + ] + d["build_post_actions"] = [ + field + for action in run.post_actions + if action.for_build + for field in [ + action.title, + action.script_text, + str(action.order) if action.order != None else "", + ] + ] + d["build_pre_actions"] = [ + field + for action in run.pre_actions + if action.for_build + for field in [ + action.title, + action.script_text, + str(action.order) if action.order != None else "", + ] + ] + d["run_post_actions"] = [ + field + for action in run.post_actions + if not action.for_build + for field in [ + action.title, + action.script_text, + str(action.order) if action.order != None else "", + ] + ] + d["run_pre_actions"] = [ + field + for action in run.pre_actions + if not action.for_build + for field in [ + action.title, + action.script_text, + str(action.order) if action.order != None else "", + ] + ] + + for action_name, action in ( + ("profile", profile), + ("test", test), + ): + if action: + d["{}_post_actions".format(action_name)] = [ + field + for pre_post_action in action.post_actions + for field in [ + pre_post_action.title, + pre_post_action.script_text, + str(pre_post_action.order) if pre_post_action.order != None else "", + ] + ] + d["{}_pre_actions".format(action_name)] = [ + field + for pre_post_action in action.pre_actions + for field in [ + pre_post_action.title, + pre_post_action.script_text, + str(pre_post_action.order) if pre_post_action.order != None else "", + ] + ] + if scheme_name_exclude_patterns: d["scheme_name_exclude_patterns"] = scheme_name_exclude_patterns - if test: + if test and test.test_options: d["test_options"] = [ test.test_options.app_language or "", test.test_options.app_region or "", @@ -1311,6 +1528,8 @@ def _autogeneration_config(*, scheme_name_exclude_patterns = None, test = None): xcschemes = struct( arg = _arg, autogeneration = struct( + profile = _autogeneration_profile, + run = _autogeneration_run, test = _autogeneration_test, ), autogeneration_config = _autogeneration_config,