Skip to content

Commit 6aab40a

Browse files
authored
Resolve conflicting environment variables among test targets in custom schemes (#3284)
Fixes #3283 This takes a similar approach to how rules_apple handles infoplist merging (take the union of top-level key/values, throw an error if a key matches but a value does not). Signed-off-by: Aaron Sky <aaronsky@skyaaron.com>
1 parent 25d6bc1 commit 6aab40a

4 files changed

Lines changed: 106 additions & 24 deletions

File tree

tools/generators/lib/XCScheme/src/EnvironmentVariables.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
public struct EnvironmentVariable: Equatable {
2-
let key: String
3-
let value: String
4-
let isEnabled: Bool
2+
public let key: String
3+
public let value: String
4+
public let isEnabled: Bool
55

66
public init(key: String, value: String, isEnabled: Bool = true) {
77
self.key = key

tools/generators/xcschemes/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ macos_unit_test(
7979
],
8080
deps = [
8181
":xcschemes_tests.library",
82+
"//tools/generators/lib/PBXProj",
83+
"//tools/generators/lib/XCScheme",
8284
],
8385
)
8486

tools/generators/xcschemes/src/Generator/CreateCustomSchemeInfos.swift

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -702,27 +702,8 @@ set
702702
var environmentVariables: [EnvironmentVariable]
703703
if let specifiedEnvironmentVariables {
704704
environmentVariables = specifiedEnvironmentVariables
705-
} else if let firstTestTargetID, let aEnvironmentVariables =
706-
targetEnvironmentVariables[firstTestTargetID]
707-
{
708-
// If the custom scheme inherits environment variables, and every
709-
// test target defines the same env, then use them
710-
var allEnvironmentVariablesTheSame = true
711-
for testTarget in testTargets {
712-
let id = testTarget.target.key.sortedIds.first!
713-
guard aEnvironmentVariables ==
714-
targetEnvironmentVariables[id]
715-
else {
716-
allEnvironmentVariablesTheSame = false
717-
break
718-
}
719-
}
720-
721-
if allEnvironmentVariablesTheSame {
722-
environmentVariables = aEnvironmentVariables
723-
} else {
724-
environmentVariables = []
725-
}
705+
} else if let firstTestTargetID, targetEnvironmentVariables[firstTestTargetID] != nil {
706+
environmentVariables = try mergingEnvironmentVariables(targetEnvironmentVariables, in: testTargets)
726707
} else {
727708
environmentVariables = []
728709
}
@@ -753,6 +734,31 @@ set
753734
}
754735
}
755736

737+
func mergingEnvironmentVariables(
738+
_ targetEnvironmentVariables: [TargetID: [EnvironmentVariable]],
739+
in testTargets: [SchemeInfo.TestTarget]
740+
) throws -> [EnvironmentVariable] {
741+
var uniqueEnvironmentVariables: [String: EnvironmentVariable] = [:]
742+
743+
for testTarget in testTargets {
744+
let id = testTarget.target.key.sortedIds.first!
745+
746+
for variable in targetEnvironmentVariables[id, default: []] {
747+
if let storedVariable = uniqueEnvironmentVariables[variable.key],
748+
storedVariable.value != variable.value {
749+
throw UsageError(message: """
750+
(\(id)) found key "\(variable.key)" in two environment variable \
751+
declarations with different values: "\(storedVariable.value)" != "\(variable.value)"
752+
""")
753+
}
754+
755+
uniqueEnvironmentVariables[variable.key] = variable
756+
}
757+
}
758+
759+
return uniqueEnvironmentVariables.values.sorted { $0.key < $1.key }
760+
}
761+
756762
private extension Dictionary where
757763
Key == String, Value == [SchemeInfo.ExecutionAction]
758764
{

tools/generators/xcschemes/test/CreateCustomSchemeInfosTests.swift

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,82 @@
1+
import PBXProj
2+
import XCScheme
13
import XCTest
24

35
@testable import xcschemes
46

57
final class CreateCustomSchemeInfosTests: XCTestCase {
8+
func test_merging_environment_variables() throws {
9+
let targets: [SchemeInfo.TestTarget] = [
10+
.init(
11+
target: .init(
12+
key: .init([.init("target1")]),
13+
productType: .unitTestBundle,
14+
buildableReference: .init(
15+
blueprintIdentifier: "",
16+
buildableName: "",
17+
blueprintName: "",
18+
referencedContainer: "",
19+
)
20+
),
21+
isEnabled: true
22+
),
23+
.init(
24+
target: .init(
25+
key: .init([.init("target2")]),
26+
productType: .unitTestBundle,
27+
buildableReference: .init(
28+
blueprintIdentifier: "",
29+
buildableName: "",
30+
blueprintName: "",
31+
referencedContainer: "",
32+
)
33+
),
34+
isEnabled: true
35+
),
36+
]
37+
38+
// No environment variables
39+
try XCTAssert(mergingEnvironmentVariables([:], in: []).isEmpty)
40+
41+
// Environment variables with no overlap
42+
try XCTAssertEqual(
43+
mergingEnvironmentVariables(
44+
[
45+
"target1": [EnvironmentVariable(key: "VAR1", value: "value1", isEnabled: true)],
46+
"target2": [EnvironmentVariable(key: "VAR2", value: "value2", isEnabled: true)],
47+
],
48+
in: targets
49+
),
50+
[
51+
EnvironmentVariable(key: "VAR1", value: "value1", isEnabled: true),
52+
EnvironmentVariable(key: "VAR2", value: "value2", isEnabled: true),
53+
]
54+
)
55+
56+
// Environment variables with overlap (target1 and target2 both have VAR1, and the output should contain VAR1 because the values match.
57+
try XCTAssertEqual(
58+
mergingEnvironmentVariables(
59+
[
60+
"target1": [EnvironmentVariable(key: "VAR1", value: "value1", isEnabled: true)],
61+
"target2": [EnvironmentVariable(key: "VAR1", value: "value1", isEnabled: true)],
62+
],
63+
in: targets
64+
),
65+
[EnvironmentVariable(key: "VAR1", value: "value1", isEnabled: true)]
66+
)
67+
68+
// Environment variables with overlap but different values (target1 and target2 both have VAR1, but the values differ, so the output should be empty because there is no consistent value for VAR1).
69+
try XCTAssertThrowsError(
70+
mergingEnvironmentVariables(
71+
[
72+
"target1": [EnvironmentVariable(key: "VAR1", value: "value1", isEnabled: true)],
73+
"target2": [EnvironmentVariable(key: "VAR1", value: "value2", isEnabled: true)],
74+
],
75+
in: targets
76+
)
77+
)
78+
}
79+
680
func test_url_relativize() {
781
typealias TestCase = (dest: URL, source: URL, expected: String?)
882
let testCases: [TestCase] = [

0 commit comments

Comments
 (0)