diff --git a/CHANGELOG.md b/CHANGELOG.md index b7459d2c01..84b54890fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ END_UNRELEASED_TEMPLATE * Added `--@rules_xcodeproj//xcodeproj:separate_index_build_output_base` flag to configure the generator to use a separate output base for index builds: [#3243](https://github.com/MobileNativeFoundation/rules_xcodeproj/pull/3243) * Add support for viewing and edit xcmappingmodel files: [#3242](https://github.com/MobileNativeFoundation/rules_xcodeproj/pull/3242) +* Added support for StoreKit configuration files to `xcschemes.run`, for use with [StoreKit Testing](https://developer.apple.com/documentation/xcode/setting-up-storekit-testing-in-xcode). [#3242](https://github.com/MobileNativeFoundation/rules_xcodeproj/pull/3245) ### Adjusted diff --git a/docs/bazel.md b/docs/bazel.md index 857cdff3d7..a10e43af27 100755 --- a/docs/bazel.md +++ b/docs/bazel.md @@ -464,7 +464,7 @@ Defines the Profile action.
 xcschemes.run(args, build_targets, diagnostics, env, env_include_defaults, launch_target,
-              xcode_configuration)
+              storekit_configuration, xcode_configuration)
 
Defines the Run action. @@ -480,6 +480,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` | +| 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/examples/integration/Lib/BUILD b/examples/integration/Lib/BUILD index cea6e5930f..0339833f20 100644 --- a/examples/integration/Lib/BUILD +++ b/examples/integration/Lib/BUILD @@ -7,7 +7,10 @@ load("@build_bazel_rules_apple//apple:tvos.bzl", "tvos_framework") load("@build_bazel_rules_apple//apple:watchos.bzl", "watchos_framework") load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") -exports_files(["README.md"]) +exports_files([ + "README.md", + "Resources/Configuration.storekit", +]) exports_files( ["Info.plist"], diff --git a/examples/integration/Lib/Resources/Configuration.storekit b/examples/integration/Lib/Resources/Configuration.storekit new file mode 100644 index 0000000000..de07ac2b89 --- /dev/null +++ b/examples/integration/Lib/Resources/Configuration.storekit @@ -0,0 +1,29 @@ +{ + "appPolicies" : { + "eula" : "", + "policies" : [ + { + "locale" : "en_US", + "policyText" : "", + "policyURL" : "" + } + ] + }, + "identifier" : "", + "nonRenewingSubscriptions" : [ + + ], + "products" : [ + + ], + "settings" : { + "_failTransactionsEnabled" : false + }, + "subscriptionGroups" : [ + + ], + "version" : { + "major" : 4, + "minor" : 0 + } +} diff --git a/examples/integration/xcodeproj_targets.bzl b/examples/integration/xcodeproj_targets.bzl index 3f13c5f7c8..af69993543 100644 --- a/examples/integration/xcodeproj_targets.bzl +++ b/examples/integration/xcodeproj_targets.bzl @@ -228,6 +228,7 @@ XCSCHEMES = [ ], ), ], + storekit_configuration = "//Lib:Resources/Configuration.storekit", ), ), ] diff --git a/test/internal/xcschemes/fixture.storekit b/test/internal/xcschemes/fixture.storekit new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/test/internal/xcschemes/fixture.storekit @@ -0,0 +1 @@ +{} diff --git a/test/internal/xcschemes/info_constructors_tests.bzl b/test/internal/xcschemes/info_constructors_tests.bzl index 3036f69e82..5f82e28b2e 100644 --- a/test/internal/xcschemes/info_constructors_tests.bzl +++ b/test/internal/xcschemes/info_constructors_tests.bzl @@ -589,6 +589,7 @@ def info_constructors_test_suite(name): env = None, env_include_defaults = "1", launch_target = xcscheme_infos_testable.make_launch_target(), + storekit_configuration = "", xcode_configuration = "", ), ) @@ -621,6 +622,7 @@ def info_constructors_test_suite(name): }, env_include_defaults = "0", launch_target = xcscheme_infos_testable.make_launch_target("L"), + storekit_configuration = "", xcode_configuration = "Run", ), @@ -651,6 +653,7 @@ def info_constructors_test_suite(name): launch_target = xcscheme_infos_testable.make_launch_target( id = "L", ), + storekit_configuration = "", xcode_configuration = "Run", ), ) diff --git a/test/internal/xcschemes/infos_from_json_tests.bzl b/test/internal/xcschemes/infos_from_json_tests.bzl index ae1331bc25..261e4bb8a0 100644 --- a/test/internal/xcschemes/infos_from_json_tests.bzl +++ b/test/internal/xcschemes/infos_from_json_tests.bzl @@ -42,6 +42,7 @@ def _infos_from_json_test_impl(ctx): infos = xcscheme_infos.from_json( ctx.attr.json_str, default_xcode_configuration = ctx.attr.default_xcode_configuration, + storekit_configurations_map = ctx.attr.storekit_configurations_map, top_level_deps = _json_to_top_level_deps(ctx.attr.top_level_deps), ) @@ -62,7 +63,9 @@ infos_from_json_test = unittest.make( attrs = { # Inputs "default_xcode_configuration": attr.string(mandatory = True), + "install_path": attr.string(mandatory = True), "json_str": attr.string(mandatory = True), + "storekit_configurations_map": attr.string_dict(mandatory = True), "top_level_deps": attr.string(mandatory = True), # Expected @@ -86,7 +89,9 @@ def infos_from_json_test_suite(name): # Inputs default_xcode_configuration, + install_path, json_str, + storekit_configurations_map, top_level_deps, # Expected @@ -97,7 +102,9 @@ def infos_from_json_test_suite(name): # Inputs default_xcode_configuration = default_xcode_configuration, + install_path = install_path, json_str = json_str, + storekit_configurations_map = storekit_configurations_map, top_level_deps = json.encode(top_level_deps), # Expected @@ -149,6 +156,7 @@ def infos_from_json_test_suite(name): }, } + install_path = "test/internal/InfosFromJSONTests.xcodeproj" full_args = [ "-a\nnewline", xcscheme_infos_testable.make_arg( @@ -390,7 +398,9 @@ def infos_from_json_test_suite(name): # Inputs default_xcode_configuration = "AppStore", + install_path = install_path, json_str = json.encode([]), + storekit_configurations_map = {}, top_level_deps = {}, # Expected @@ -404,6 +414,7 @@ def infos_from_json_test_suite(name): # Inputs default_xcode_configuration = "AppStore", + install_path = install_path, json_str = json.encode([ { "name": "A scheme", @@ -418,6 +429,7 @@ def infos_from_json_test_suite(name): "test": None, }, ]), + storekit_configurations_map = {}, top_level_deps = {}, # Expected @@ -438,6 +450,7 @@ def infos_from_json_test_suite(name): # Inputs default_xcode_configuration = "custom", + install_path = install_path, json_str = json.encode([ { "name": "A scheme", @@ -485,6 +498,7 @@ def infos_from_json_test_suite(name): "test": None, }, ]), + storekit_configurations_map = {}, top_level_deps = top_level_deps, # Expected @@ -514,6 +528,7 @@ def infos_from_json_test_suite(name): # Inputs default_xcode_configuration = "AppStore", + install_path = install_path, json_str = json.encode([ { "name": "A scheme", @@ -530,6 +545,7 @@ def infos_from_json_test_suite(name): "test": None, }, ]), + storekit_configurations_map = {}, top_level_deps = top_level_deps, # Expected @@ -557,6 +573,7 @@ def infos_from_json_test_suite(name): # Inputs default_xcode_configuration = "AppStore", + install_path = install_path, json_str = json.encode([ { "name": "A scheme", @@ -565,6 +582,7 @@ def infos_from_json_test_suite(name): "test": None, }, ]), + storekit_configurations_map = {}, top_level_deps = {}, # Expected @@ -581,6 +599,7 @@ def infos_from_json_test_suite(name): # Inputs default_xcode_configuration = "AppStore", + install_path = install_path, json_str = json.encode([ { "name": "A scheme", @@ -594,11 +613,13 @@ def infos_from_json_test_suite(name): env = {"A": "B"}, env_include_defaults = "1", launch_target = full_launch_target, + storekit_configuration = "", xcode_configuration = "custom", ), "test": None, }, ]), + storekit_configurations_map = {}, top_level_deps = top_level_deps, # Expected @@ -626,6 +647,7 @@ def infos_from_json_test_suite(name): env = {"A": xcscheme_infos_testable.make_env("B")}, env_include_defaults = "1", launch_target = expected_full_launch_target, + storekit_configuration = "", xcode_configuration = "custom", ), ), @@ -639,6 +661,7 @@ def infos_from_json_test_suite(name): # Inputs default_xcode_configuration = "custom", + install_path = install_path, json_str = json.encode([ { "name": "A scheme", @@ -681,11 +704,13 @@ def infos_from_json_test_suite(name): target_environment = "", working_directory = "", ), + storekit_configuration = "", xcode_configuration = "", ), "test": None, }, ]), + storekit_configurations_map = {}, top_level_deps = top_level_deps, # Expected @@ -744,6 +769,7 @@ def infos_from_json_test_suite(name): # Inputs default_xcode_configuration = "AppStore", + install_path = install_path, json_str = json.encode([ { "name": "A scheme", @@ -781,11 +807,13 @@ def infos_from_json_test_suite(name): ], working_directory = "wd", ), + storekit_configuration = "", xcode_configuration = "custom", ), "test": None, }, ]), + storekit_configurations_map = {}, top_level_deps = top_level_deps, # Expected @@ -821,6 +849,7 @@ def infos_from_json_test_suite(name): # Inputs default_xcode_configuration = "AppStore", + install_path = install_path, json_str = json.encode([ { "name": "A scheme", @@ -839,11 +868,15 @@ def infos_from_json_test_suite(name): env_include_defaults = "0", launch_target = full_launch_target, use_run_args_and_env = "0", + storekit_configuration = "//test/internal/xcschemes:fixture.storekit", xcode_configuration = "custom", ), "test": None, }, ]), + storekit_configurations_map = { + "//test/internal/xcschemes:fixture.storekit": "test/internal/xcschemes/fixture.storekit", + }, top_level_deps = top_level_deps, # Expected @@ -866,6 +899,9 @@ def infos_from_json_test_suite(name): env = expected_full_env, env_include_defaults = "0", launch_target = expected_full_launch_target, + # from the install path and two levels inside the project, up to the execution root + # /xcshareddata/xcschemes + storekit_configuration = "test/internal/xcschemes/fixture.storekit", xcode_configuration = "custom", ), ), @@ -879,6 +915,7 @@ def infos_from_json_test_suite(name): # Inputs default_xcode_configuration = "custom", + install_path = install_path, json_str = json.encode([ { "name": "A scheme", @@ -913,6 +950,7 @@ def infos_from_json_test_suite(name): ), }, ]), + storekit_configurations_map = {}, top_level_deps = top_level_deps, # Expected @@ -938,6 +976,7 @@ def infos_from_json_test_suite(name): # Inputs default_xcode_configuration = "AppStore", + install_path = install_path, json_str = json.encode([ { "name": "A scheme", @@ -1009,6 +1048,7 @@ def infos_from_json_test_suite(name): ), }, ]), + storekit_configurations_map = {}, top_level_deps = top_level_deps, # Expected diff --git a/test/internal/xcschemes/utils.bzl b/test/internal/xcschemes/utils.bzl index 0aa2fff8be..9c4a92654d 100644 --- a/test/internal/xcschemes/utils.bzl +++ b/test/internal/xcschemes/utils.bzl @@ -64,6 +64,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"]), + 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 35b920991a..45cbd7884f 100644 --- a/test/internal/xcschemes/write_schemes_tests.bzl +++ b/test/internal/xcschemes/write_schemes_tests.bzl @@ -552,6 +552,7 @@ def write_schemes_test_suite(name): ], working_directory = "run working dir", ), + storekit_configuration = "StoreKitConfig", xcode_configuration = "Run", ), test = xcscheme_infos_testable.make_test( @@ -1110,6 +1111,8 @@ def write_schemes_test_suite(name): "1", # - test - enableThreadPerformanceChecker "1", + # - run - storekitConfiguration + "", # - run - xcodeConfiguration "", # - run - launchTarget - isPath @@ -1236,6 +1239,8 @@ def write_schemes_test_suite(name): "1", # - test - enableThreadPerformanceChecker "1", + # - run - storekitConfiguration + "StoreKitConfig", # - run - xcodeConfiguration "Run", # - run - launchTarget - isPath @@ -1340,6 +1345,8 @@ def write_schemes_test_suite(name): "1", # - test - enableThreadPerformanceChecker "1", + # - run - storekitConfiguration + "", # - run - xcodeConfiguration "", # - run - launchTarget - isPath diff --git a/tools/generators/lib/XCScheme/src/CreateLaunchAction.swift b/tools/generators/lib/XCScheme/src/CreateLaunchAction.swift index a569187035..28ef7f7982 100644 --- a/tools/generators/lib/XCScheme/src/CreateLaunchAction.swift +++ b/tools/generators/lib/XCScheme/src/CreateLaunchAction.swift @@ -21,7 +21,8 @@ public struct CreateLaunchAction { environmentVariables: [EnvironmentVariable], postActions: [ExecutionAction], preActions: [ExecutionAction], - runnable: Runnable? + runnable: Runnable?, + storeKitConfiguration: String? ) -> String { return callable( /*buildConfiguration:*/ buildConfiguration, @@ -35,7 +36,8 @@ public struct CreateLaunchAction { /*environmentVariables:*/ environmentVariables, /*postActions:*/ postActions, /*preActions:*/ preActions, - /*runnable:*/ runnable + /*runnable:*/ runnable, + /*storeKitConfiguration:*/ storeKitConfiguration ) } } @@ -55,7 +57,8 @@ extension CreateLaunchAction { _ environmentVariables: [EnvironmentVariable], _ postActions: [ExecutionAction], _ preActions: [ExecutionAction], - _ runnable: Runnable? + _ runnable: Runnable?, + _ storeKitConfiguration: String? ) -> String public static func defaultCallable( @@ -70,7 +73,8 @@ extension CreateLaunchAction { environmentVariables: [EnvironmentVariable], postActions: [ExecutionAction], preActions: [ExecutionAction], - runnable: Runnable? + runnable: Runnable?, + storeKitConfiguration: String? ) -> String { // 3 spaces for indentation is intentional @@ -202,12 +206,24 @@ ignoresPersistentStateOnLaunch = "NO" runnableString = "" } + let storeKitConfigurationString = if let storeKitConfiguration, !storeKitConfiguration.isEmpty && storeKitConfiguration != "None" { + #""" + + + +"""# + } else { + "" + } + return #""" \#(preActions.preActionsString)\# \#(postActions.postActionsString)\# \#(runnableString)\# +\#(storeKitConfigurationString)\# \#(commandLineArguments.commandLineArgumentsString)\# \#(environmentVariables.environmentVariablesString)\# diff --git a/tools/generators/lib/XCScheme/test/CreateLaunchActionTests.swift b/tools/generators/lib/XCScheme/test/CreateLaunchActionTests.swift index 4cfb8368af..a9082f70a3 100644 --- a/tools/generators/lib/XCScheme/test/CreateLaunchActionTests.swift +++ b/tools/generators/lib/XCScheme/test/CreateLaunchActionTests.swift @@ -594,7 +594,8 @@ private func createLaunchActionWithDefaults( environmentVariables: [EnvironmentVariable] = [], postActions: [ExecutionAction] = [], preActions: [ExecutionAction] = [], - runnable: Runnable? = nil + runnable: Runnable? = nil, + storeKitConfiguration: String? = nil ) -> String { return CreateLaunchAction.defaultCallable( buildConfiguration: buildConfiguration, @@ -608,6 +609,7 @@ private func createLaunchActionWithDefaults( environmentVariables: environmentVariables, postActions: postActions, preActions: preActions, - runnable: runnable + runnable: runnable, + storeKitConfiguration: storeKitConfiguration ) } diff --git a/tools/generators/xcschemes/src/Generator/CreateAutomaticSchemeInfo.swift b/tools/generators/xcschemes/src/Generator/CreateAutomaticSchemeInfo.swift index ea1dcc5072..1b33204734 100644 --- a/tools/generators/xcschemes/src/Generator/CreateAutomaticSchemeInfo.swift +++ b/tools/generators/xcschemes/src/Generator/CreateAutomaticSchemeInfo.swift @@ -139,6 +139,7 @@ extension Generator.CreateAutomaticSchemeInfo { enableThreadPerformanceChecker: false, environmentVariables: runEnvironmentVariables, launchTarget: launchTarget, + storeKitConfiguration: nil, xcodeConfiguration: nil ), profile: .init( diff --git a/tools/generators/xcschemes/src/Generator/CreateCustomSchemeInfos.swift b/tools/generators/xcschemes/src/Generator/CreateCustomSchemeInfos.swift index 56996b2938..1d1e181d87 100644 --- a/tools/generators/xcschemes/src/Generator/CreateCustomSchemeInfos.swift +++ b/tools/generators/xcschemes/src/Generator/CreateCustomSchemeInfos.swift @@ -23,7 +23,9 @@ extension Generator { environmentVariables: [TargetID: [EnvironmentVariable]], executionActionsFile: URL, extensionHostIDs: [TargetID: [TargetID]], - targetsByID: [TargetID: Target] + schemesDirectory: URL, + targetsByID: [TargetID: Target], + workspace: URL ) async throws -> [SchemeInfo] { try await callable( /*commandLineArguments:*/ commandLineArguments, @@ -31,7 +33,9 @@ extension Generator { /*environmentVariables:*/ environmentVariables, /*executionActionsFile:*/ executionActionsFile, /*extensionHostIDs:*/ extensionHostIDs, - /*targetsByID:*/ targetsByID + /*schemesDirectory:*/ schemesDirectory, + /*targetsByID:*/ targetsByID, + /*workspace:*/ workspace ) } } @@ -46,7 +50,9 @@ extension Generator.CreateCustomSchemeInfos { _ environmentVariables: [TargetID: [EnvironmentVariable]], _ executionActionsFile: URL, _ extensionHostIDs: [TargetID: [TargetID]], - _ targetsByID: [TargetID: Target] + _ schemesDirectory: URL, + _ targetsByID: [TargetID: Target], + _ workspace: URL ) async throws -> [SchemeInfo] static func defaultCallable( @@ -55,7 +61,9 @@ extension Generator.CreateCustomSchemeInfos { environmentVariables: [TargetID: [EnvironmentVariable]], executionActionsFile: URL, extensionHostIDs: [TargetID: [TargetID]], - targetsByID: [TargetID: Target] + schemesDirectory: URL, + targetsByID: [TargetID: Target], + workspace: URL ) async throws -> [SchemeInfo] { let executionActions: [String: [SchemeInfo.ExecutionAction]] = try await .parse( @@ -93,9 +101,11 @@ extension Generator.CreateCustomSchemeInfos { allTargetIDs: &allTargetIDs, extensionHostIDs: extensionHostIDs, name: name, + schemesDirectory: schemesDirectory, targetCommandLineArguments: commandLineArguments, targetEnvironmentVariables: environmentVariables, - targetsByID: targetsByID + targetsByID: targetsByID, + workspace: workspace ) let profile = try rawArgs.consumeArg( @@ -450,9 +460,11 @@ set allTargetIDs: inout Set, extensionHostIDs: [TargetID: [TargetID]], name: String, + schemesDirectory: URL, targetCommandLineArguments: [TargetID: [CommandLineArgument]], targetEnvironmentVariables: [TargetID: [EnvironmentVariable]], targetsByID: [TargetID: Target], + workspace: URL, file: StaticString = #filePath, line: UInt = #line ) throws -> SchemeInfo.Run { @@ -499,6 +511,13 @@ set as: Bool.self, in: url ) + let storeKitConfiguration = + (try consumeArg("run-storekit-configuration", as: String?.self, in: url)).flatMap { + // Relativize the StoreKit Testing configuration file against + // the scheme directory within the install path. + URL(filePath: $0, relativeTo: workspace) + .relativize(from: schemesDirectory) + } let xcodeConfiguration = try consumeArg("run-xcode-configuration", as: String?.self, in: url) @@ -545,6 +564,7 @@ set enableThreadPerformanceChecker: enableThreadPerformanceChecker, environmentVariables: environmentVariables, launchTarget: launchTarget, + storeKitConfiguration: storeKitConfiguration, xcodeConfiguration: xcodeConfiguration ) } @@ -802,3 +822,33 @@ private extension SchemeInfo.LaunchTarget { } } } + +extension URL { + func relativize(from source: URL) -> String? { + if self == source { + return self.path + } + + let sourceComponents = source.deletingLastPathComponent().pathComponents + let destComponents = self.pathComponents + + // Find common prefix + var commonPrefixCount = 0 + while commonPrefixCount < sourceComponents.count && + commonPrefixCount < destComponents.count && + sourceComponents[commonPrefixCount] == destComponents[commonPrefixCount] { + commonPrefixCount += 1 + } + + // Build relative path + var result = [String]() + + // Add "../" for each level to go up + result.append(contentsOf: Array(repeating: "..", count: sourceComponents.count - commonPrefixCount)) + + // Add remaining destination components + result.append(contentsOf: destComponents[commonPrefixCount...]) + + return result.joined(separator: "/") + } +} diff --git a/tools/generators/xcschemes/src/Generator/CreateScheme.swift b/tools/generators/xcschemes/src/Generator/CreateScheme.swift index 0725e0c238..0ab1671f00 100644 --- a/tools/generators/xcschemes/src/Generator/CreateScheme.swift +++ b/tools/generators/xcschemes/src/Generator/CreateScheme.swift @@ -400,7 +400,8 @@ extension Generator.CreateScheme { preActions: launchPreActions .sorted(by: compareExecutionActions) .map(\.action), - runnable: launchRunnable + runnable: launchRunnable, + storeKitConfiguration: schemeInfo.run.storeKitConfiguration ), profileAction: createProfileAction( buildConfiguration: schemeInfo.profile.xcodeConfiguration ?? diff --git a/tools/generators/xcschemes/src/Generator/Generator.swift b/tools/generators/xcschemes/src/Generator/Generator.swift index b7ffd0c674..b81526184e 100644 --- a/tools/generators/xcschemes/src/Generator/Generator.swift +++ b/tools/generators/xcschemes/src/Generator/Generator.swift @@ -1,3 +1,4 @@ +import Foundation import PBXProj import ToolCommon import XCScheme @@ -39,13 +40,23 @@ struct Generator { from: arguments.autogenerationConfigFile ) + let workspace = URL(filePath: arguments.workspace, directoryHint: .isDirectory) + let installPath = if arguments.installPath.isEmpty { + workspace + } else { + URL(filePath: arguments.installPath, directoryHint: .isDirectory, relativeTo: workspace) + } + let schemesDirectory = URL(filePath: "xcshareddata/xcschemes", directoryHint: .isDirectory, relativeTo: installPath) + let customSchemeInfos = try await environment.createCustomSchemeInfos( commandLineArguments: commandLineArguments, customSchemesFile: arguments.customSchemesFile, environmentVariables: environmentVariables, executionActionsFile: arguments.executionActionsFile, extensionHostIDs: extensionHostIDs, - targetsByID: targetsByID + schemesDirectory: schemesDirectory, + targetsByID: targetsByID, + workspace: workspace ) let automaticSchemeInfos = try environment.createAutomaticSchemeInfos( diff --git a/tools/generators/xcschemes/src/Generator/SchemeInfo.swift b/tools/generators/xcschemes/src/Generator/SchemeInfo.swift index b9b05e1714..99ab59dadc 100644 --- a/tools/generators/xcschemes/src/Generator/SchemeInfo.swift +++ b/tools/generators/xcschemes/src/Generator/SchemeInfo.swift @@ -70,6 +70,7 @@ struct SchemeInfo: Equatable { let enableThreadPerformanceChecker: Bool let environmentVariables: [EnvironmentVariable] let launchTarget: LaunchTarget? + let storeKitConfiguration: String? let xcodeConfiguration: String? } diff --git a/tools/generators/xcschemes/test/CreateAutomaticSchemeInfoTests.swift b/tools/generators/xcschemes/test/CreateAutomaticSchemeInfoTests.swift index 95aff2f5a8..b04aab718a 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 ), + storeKitConfiguration: nil, xcodeConfiguration: nil ), profile: .init( @@ -237,6 +238,7 @@ final class CreateAutomaticSchemeInfoTests: XCTestCase { primary: launchable, extensionHost: extensionHost ), + storeKitConfiguration: nil, xcodeConfiguration: nil ), profile: .init( @@ -315,6 +317,7 @@ final class CreateAutomaticSchemeInfoTests: XCTestCase { primary: launchable, extensionHost: nil ), + storeKitConfiguration: nil, xcodeConfiguration: nil ), profile: .init( @@ -393,6 +396,7 @@ final class CreateAutomaticSchemeInfoTests: XCTestCase { primary: launchable, extensionHost: nil ), + storeKitConfiguration: nil, xcodeConfiguration: nil ), profile: .init( @@ -465,6 +469,7 @@ final class CreateAutomaticSchemeInfoTests: XCTestCase { enableThreadPerformanceChecker: false, environmentVariables: baseEnvironmentVariables, launchTarget: nil, + storeKitConfiguration: nil, xcodeConfiguration: nil ), profile: .init( @@ -533,6 +538,7 @@ final class CreateAutomaticSchemeInfoTests: XCTestCase { enableThreadPerformanceChecker: false, environmentVariables: [], launchTarget: nil, + storeKitConfiguration: nil, xcodeConfiguration: nil ), profile: .init( @@ -604,6 +610,7 @@ final class CreateAutomaticSchemeInfoTests: XCTestCase { enableThreadPerformanceChecker: false, environmentVariables: [], launchTarget: nil, + storeKitConfiguration: nil, xcodeConfiguration: nil ), profile: .init( @@ -676,6 +683,7 @@ final class CreateAutomaticSchemeInfoTests: XCTestCase { enableThreadPerformanceChecker: false, environmentVariables: [], launchTarget: nil, + storeKitConfiguration: nil, xcodeConfiguration: nil ), profile: .init( @@ -743,6 +751,7 @@ final class CreateAutomaticSchemeInfoTests: XCTestCase { enableThreadPerformanceChecker: false, environmentVariables: [], launchTarget: nil, + storeKitConfiguration: nil, xcodeConfiguration: nil ), profile: .init( diff --git a/tools/generators/xcschemes/test/CreateCustomSchemeInfosTests.swift b/tools/generators/xcschemes/test/CreateCustomSchemeInfosTests.swift new file mode 100644 index 0000000000..0d179d919f --- /dev/null +++ b/tools/generators/xcschemes/test/CreateCustomSchemeInfosTests.swift @@ -0,0 +1,33 @@ +import XCTest + +@testable import xcschemes + +final class CreateCustomSchemeInfosTests: XCTestCase { + func test_url_relativize() { + typealias TestCase = (dest: URL, source: URL, expected: String?) + let testCases: [TestCase] = [ + // Common root + (URL(filePath: "/path/to/my/file.txt"), URL(filePath: "/path/to/your/dir"), "../my/file.txt"), + // No common root + (URL(filePath: "/path/to/my/file.txt"), URL(filePath: "/home/from/your/dir"), "../../../path/to/my/file.txt"), + // Both empty paths (implied to be /private/tmp in Bazel) + (URL(filePath: ""), URL(filePath: ""), "/private/tmp"), + // Empty destination path, absolute source path + (URL(filePath: ""), URL(filePath: "/path"), "private/tmp"), + // Absolute destination path, empty source path + (URL(filePath: "/path"), URL(filePath: ""), "../path"), + // Relative destination path (implied to be relative to /private/tmp in Bazel), absolute source path + (URL(filePath: "path/to/file.txt"), URL(filePath: "/path/to/dir"), "../../private/tmp/path/to/file.txt"), + // Absolute destination path, relative source path + (URL(filePath: "/path/to/file.txt"), URL(filePath: "path/to/dir"), "../../../../path/to/file.txt"), + // Weird relative destination path + (URL(filePath: "../../file.txt"), URL(filePath: "/path/to/dir"), "../../file.txt"), + // Absolute destination path, weird relative source path + (URL(filePath: "/path/to/file.txt"), URL(filePath: "../../to/dir"), "../path/to/file.txt"), + ] + for (dest, source, expected) in testCases { + let actual = dest.relativize(from: source) + XCTAssertEqual(expected, actual) + } + } +} diff --git a/tools/generators/xcschemes/test/SchemeInfo+Testing.swift b/tools/generators/xcschemes/test/SchemeInfo+Testing.swift index 9868cd1bd9..67df19bdf3 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, + storeKitConfiguration: String? = nil, xcodeConfiguration: String? = nil ) -> Self { return Self( @@ -67,6 +68,7 @@ extension SchemeInfo.Run { enableThreadPerformanceChecker: enableThreadPerformanceChecker, environmentVariables: environmentVariables, launchTarget: launchTarget, + storeKitConfiguration: storeKitConfiguration, xcodeConfiguration: xcodeConfiguration ) } diff --git a/xcodeproj/internal/templates/generator.BUILD.bazel b/xcodeproj/internal/templates/generator.BUILD.bazel index 300098e355..57f6154331 100644 --- a/xcodeproj/internal/templates/generator.BUILD.bazel +++ b/xcodeproj/internal/templates/generator.BUILD.bazel @@ -32,6 +32,7 @@ xcodeproj( scheme_autogeneration_mode = "%scheme_autogeneration_mode%", scheme_autogeneration_config = %scheme_autogeneration_config%, separate_index_build_output_base = %separate_index_build_output_base%, + storekit_configurations_map = %storekit_configurations_map%, tags = %tags%, target_name_mode = "%target_name_mode%", top_level_device_targets = %top_level_device_targets%, diff --git a/xcodeproj/internal/xcodeproj_rule.bzl b/xcodeproj/internal/xcodeproj_rule.bzl index bbe87196fe..c08fe8d23b 100644 --- a/xcodeproj/internal/xcodeproj_rule.bzl +++ b/xcodeproj/internal/xcodeproj_rule.bzl @@ -523,6 +523,7 @@ def _write_schemes( infos, install_path, name, + storekit_configurations_map, top_level_deps, workspace_directory, xcschemes_generator, @@ -542,6 +543,7 @@ def _write_schemes( xcscheme_infos = xcscheme_infos_module.from_json( xcschemes_json, default_xcode_configuration = default_xcode_configuration, + storekit_configurations_map = storekit_configurations_map, top_level_deps = top_level_deps, ) @@ -729,6 +731,7 @@ Are you using an `alias`? `xcodeproj.focused_targets` and \ infos = infos, install_path = install_path, name = name, + storekit_configurations_map = ctx.attr.storekit_configurations_map, top_level_deps = top_level_deps, workspace_directory = workspace_directory, xcschemes_generator = ctx.executable._xcschemes_generator, @@ -822,6 +825,11 @@ def _xcodeproj_attrs( "scheme_autogeneration_config": attr.string_list_dict(mandatory = True), "scheme_autogeneration_mode": attr.string(mandatory = True), "separate_index_build_output_base": attr.bool(mandatory = True), + "storekit_configurations_map": attr.string_dict( + mandatory = True, + doc = """\ +A dict mapping of Labels for StoreKit Testing configuration files to their File paths.""", + ), "target_name_mode": attr.string(mandatory = True), "top_level_device_targets": attr.label_list( cfg = target_transitions.device, diff --git a/xcodeproj/internal/xcodeproj_runner.bzl b/xcodeproj/internal/xcodeproj_runner.bzl index f4e500cda7..8ffdfda3ad 100644 --- a/xcodeproj/internal/xcodeproj_runner.bzl +++ b/xcodeproj/internal/xcodeproj_runner.bzl @@ -13,6 +13,22 @@ def _process_extra_flags(*, attr, content, setting, config, config_suffix): "common:{}{} {}".format(config, config_suffix, extra_flags), ) +def _resolve_storekit_configurations_map(storekit_configurations_map): + file_paths = {} + for scheme_name, target in storekit_configurations_map.items(): + if target.label in file_paths: + continue + files = target.files.to_list() + if not files: + continue + elif len(files) > 1: + fail("""\ +Scheme `{scheme_name}` declares a `storekit_configuration` on its `run` action \ +that is composed of multiple files. The `storekit_configuration` for a scheme \ +must be a single file.""".format(scheme_name = scheme_name)) + file_paths[str(target.label)] = files[0].path + return file_paths + def _serialize_nullable_string(value): if not value: return "None" @@ -185,6 +201,7 @@ def _write_generator_build_file( name, runner_label, repo, + storekit_configurations_map, template): output = actions.declare_file("{}.generator.BUILD.bazel".format(name)) @@ -220,6 +237,7 @@ def _write_generator_build_file( "%scheme_autogeneration_config%": str(attr.scheme_autogeneration_config), "%scheme_autogeneration_mode%": attr.scheme_autogeneration_mode, "%separate_index_build_output_base%": str(attr._separate_index_build_output_base[BuildSettingInfo].value), + "%storekit_configurations_map%": str(storekit_configurations_map), "%tags%": tags, "%target_name_mode%": attr.target_name_mode, "%testonly%": str(attr.testonly), @@ -403,6 +421,7 @@ def _xcodeproj_runner_impl(ctx): name = name, runner_label = runner_label, repo = repo, + storekit_configurations_map = _resolve_storekit_configurations_map(attr.storekit_configurations_map), template = build_file_template, ) @@ -469,6 +488,15 @@ xcodeproj_runner = rule( default = "auto", values = ["auto", "none", "all"], ), + "storekit_configurations_map": attr.string_keyed_label_dict( + allow_files = True, + mandatory = True, + doc = """\ +A dict mapping xcscheme names to Labels of StoreKit Testing configuration files. + +While the attr allows for Labels that make up multiple files, the Label must point +to a single file.""", + ), "target_name_mode": attr.string( default = "auto", values = ["auto", "label"], diff --git a/xcodeproj/internal/xcschemes/xcscheme_infos.bzl b/xcodeproj/internal/xcschemes/xcscheme_infos.bzl index afe7ae7e2e..d28e0039db 100644 --- a/xcodeproj/internal/xcschemes/xcscheme_infos.bzl +++ b/xcodeproj/internal/xcschemes/xcscheme_infos.bzl @@ -143,6 +143,7 @@ def _make_run( env = None, env_include_defaults = TRUE_ARG, launch_target = _make_launch_target(), + storekit_configuration = EMPTY_STRING, xcode_configuration = EMPTY_STRING): return struct( args = args, @@ -151,6 +152,7 @@ def _make_run( env = env, env_include_defaults = env_include_defaults, launch_target = launch_target, + storekit_configuration = storekit_configuration, xcode_configuration = xcode_configuration, ) @@ -322,6 +324,17 @@ def _env_infos_from_dict(env): for key, value in env.items() } +def _storekit_configuration_info(label, storekit_configurations_map): + """ + Extract the full path (from the execution root) for a StoreKit Testing \ + configuration file from the `storekit_configurations_map`. + + Args: + label: A Label to a StoreKit Testing configuration file. + storekit_configurations_map: A dict of Labels to File paths. + """ + return storekit_configurations_map.get(label, "") + def _get_library_target_id(label, *, scheme_name, target_ids): target_id = target_ids.get(label) if not target_id: @@ -582,6 +595,7 @@ def _run_info_from_dict( *, default_xcode_configuration, scheme_name, + storekit_configurations_map, top_level_deps): if not run: return _make_run() @@ -619,6 +633,10 @@ def _run_info_from_dict( env = _env_infos_from_dict(run["env"]), env_include_defaults = run["env_include_defaults"], launch_target = launch_target, + storekit_configuration = _storekit_configuration_info( + run["storekit_configuration"], + storekit_configurations_map, + ), xcode_configuration = xcode_configuration, ) @@ -729,6 +747,7 @@ def _scheme_info_from_dict( scheme, *, default_xcode_configuration, + storekit_configurations_map, top_level_deps): name = scheme["name"] @@ -736,6 +755,7 @@ def _scheme_info_from_dict( scheme["run"], default_xcode_configuration = default_xcode_configuration, scheme_name = name, + storekit_configurations_map = storekit_configurations_map, top_level_deps = top_level_deps, ) @@ -759,11 +779,17 @@ def _scheme_info_from_dict( # API -def _from_json(json_str, *, default_xcode_configuration, top_level_deps): +def _from_json( + json_str, + *, + default_xcode_configuration, + storekit_configurations_map, + top_level_deps): return [ _scheme_info_from_dict( scheme, default_xcode_configuration = default_xcode_configuration, + storekit_configurations_map = storekit_configurations_map, top_level_deps = top_level_deps, ) for scheme in json.decode(json_str) diff --git a/xcodeproj/internal/xcschemes/xcscheme_labels.bzl b/xcodeproj/internal/xcschemes/xcscheme_labels.bzl index bdc4617fc4..0962fac645 100644 --- a/xcodeproj/internal/xcschemes/xcscheme_labels.bzl +++ b/xcodeproj/internal/xcschemes/xcscheme_labels.bzl @@ -111,6 +111,7 @@ def _resolve_run_labels(run): env = run.env, env_include_defaults = run.env_include_defaults, launch_target = _resolve_launch_target_labels(run.launch_target), + 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 8c2d05f1de..131cb7b49c 100644 --- a/xcodeproj/internal/xcschemes/xcschemes_execution.bzl +++ b/xcodeproj/internal/xcschemes/xcschemes_execution.bzl @@ -341,6 +341,7 @@ def _write_schemes( _add_env(info.run.env) custom_scheme_args.add(info.run.env_include_defaults) _add_diagnostics(info.run.diagnostics) + custom_scheme_args.add(info.run.storekit_configuration) custom_scheme_args.add(info.run.xcode_configuration) _add_launch_target( diff --git a/xcodeproj/xcodeproj.bzl b/xcodeproj/xcodeproj.bzl index 3901f9b831..ad60147a01 100644 --- a/xcodeproj/xcodeproj.bzl +++ b/xcodeproj/xcodeproj.bzl @@ -461,6 +461,12 @@ configuration alphabetically ("{default}"). target = bazel_labels.normalize_string(name), )) + storekit_configurations_map = { + scheme.name: scheme.run.storekit_configuration + for scheme in xcschemes + if scheme.run and scheme.run.storekit_configuration + } + xcschemes_json = json.encode( xcscheme_labels.resolve_labels(xcschemes), ) @@ -534,6 +540,7 @@ for {configuration} ({new_keys}) do not match keys of other configurations \ project_options = project_options, scheme_autogeneration_mode = scheme_autogeneration_mode, scheme_autogeneration_config = scheme_autogeneration_config, + storekit_configurations_map = storekit_configurations_map, target_name_mode = target_name_mode, testonly = testonly, top_level_device_targets = top_level_device_targets, diff --git a/xcodeproj/xcschemes.bzl b/xcodeproj/xcschemes.bzl index c43793736a..d0550bc664 100644 --- a/xcodeproj/xcschemes.bzl +++ b/xcodeproj/xcschemes.bzl @@ -212,6 +212,7 @@ def _run( env = "inherit", env_include_defaults = True, launch_target = None, + storekit_configuration = None, xcode_configuration = None): """Defines the Run action. @@ -335,6 +336,10 @@ 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`). + 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. 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 @@ -351,6 +356,7 @@ def _run( env = env or {}, env_include_defaults = TRUE_ARG if env_include_defaults else FALSE_ARG, launch_target = launch_target, + storekit_configuration = storekit_configuration, xcode_configuration = xcode_configuration or "", )