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 "",
)