Skip to content

Commit 63c41d6

Browse files
authored
Feat: Propagate global properties into variants (#248)
* fix: remove path config from old installation method * feat: make custom global properties available to all variants * chore: bump nokogiri version * fix: unit tests * fix: linter errors * chore: add unit test for global and variant custom properties
1 parent 7b40591 commit 63c41d6

24 files changed

+253
-245
lines changed

Gemfile.lock

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,9 +189,15 @@ GEM
189189
nanaimo (0.4.0)
190190
naturally (2.2.1)
191191
nkf (0.2.0)
192-
nokogiri (1.15.7)
192+
nokogiri (1.18.2)
193193
mini_portile2 (~> 2.8.2)
194194
racc (~> 1.4)
195+
nokogiri (1.18.2-arm64-darwin)
196+
racc (~> 1.4)
197+
nokogiri (1.18.2-x86_64-darwin)
198+
racc (~> 1.4)
199+
nokogiri (1.18.2-x86_64-linux-gnu)
200+
racc (~> 1.4)
195201
optparse (0.6.0)
196202
os (1.1.4)
197203
plist (3.7.2)

Sources/VariantsCore/Custom Types/Project/iOSProject.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,6 @@ class iOSProject: Project {
101101

102102
var customProperties: [CustomProperty] = (variant.custom ?? []) + (configuration.custom ?? [])
103103
customProperties.append(variant.destinationProperty)
104-
105104
// Create 'variants_params.rb' with parameters whose
106105
// destination are set as '.fastlane'
107106
try? storeFastlaneParams(customProperties)
@@ -129,11 +128,12 @@ class iOSProject: Project {
129128
// destination are set as '.project'
130129
let configPath = Path(spec).absolute().parent()
131130
do {
132-
try configFactory.createConfig(with: target,
133-
variant: defaultVariant,
134-
xcodeProj: configuration.xcodeproj,
135-
configPath: configPath,
136-
addToXcodeProj: true)
131+
try configFactory.createConfig(
132+
with: target,
133+
variant: defaultVariant,
134+
xcodeProj: configuration.xcodeproj,
135+
configPath: configPath,
136+
addToXcodeProj: true)
137137
} catch {
138138
Logger.shared.logFatal(item: error.localizedDescription)
139139
}

Sources/VariantsCore/Custom Types/TemplateDirectory.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ struct TemplateDirectory {
1515

1616
init(
1717
directories: [String] = [
18-
"/usr/local/lib/variants/templates",
1918
"~/.local/lib/variants/templates",
2019
"./Templates"
2120
]

Sources/VariantsCore/Custom Types/UtilsDirectory.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ struct UtilsDirectory {
1515

1616
init(
1717
directories: [String] = [
18-
"/usr/local/lib/variants/utils",
1918
"~/.local/lib/variants/utils",
2019
"./utils"
2120
]

Sources/VariantsCore/Factory/iOS/VariantsFileFactory.swift

Lines changed: 9 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,15 @@ class VariantsFileFactory {
2424
let path = try TemplateDirectory().path
2525
guard let variantsGybTemplatePath = try? path.safeJoin(path: Path("ios/"))
2626
else { return }
27-
let secrets = variant.custom?.secrets() ?? []
28-
let configurationValues = variant.custom?.configurationValues() ?? []
27+
2928
let context = [
30-
"secrets": secrets,
31-
"configurationValues": configurationValues
29+
"secrets": variant.custom?.projectSecretConfigurationValues ?? [],
30+
"configurationValues": variant.custom?.projectConfigurationValues ?? []
3231
] as [String: Any]
3332
let environment = Environment(loader: FileSystemLoader(paths: [variantsGybTemplatePath.absolute()]))
34-
let rendered = try environment.renderTemplate(name: StaticPath.Template.variantsSwiftGybFileName,
33+
let content = try environment.renderTemplate(name: StaticPath.Template.variantsSwiftGybFileName,
3534
context: context)
36-
// Replace multiple empty lines by one only
37-
let lines = rendered.split(whereSeparator: \.isNewline)
38-
let content = lines.joined(separator: "\n")
39-
35+
4036
try write(Data(content.utf8), using: configFilePath.parent().absolute())
4137
let variantsGybFile = try configFilePath.parent().absolute()
4238
.safeJoin(path: Path(StaticPath.Xcode.variantsGybFileName))
@@ -71,17 +67,17 @@ class VariantsFileFactory {
7167
fileContent == data
7268
else { return }
7369

70+
let variantsOutputFilePath = "\(variantsGybFile.parent().absolute().string)/Variants.swift"
7471
let gybStdErr = try Bash(gybExecutablePath.absolute().description,
7572
arguments:
7673
"--line-directive",
7774
"",
7875
"-o",
79-
"Variants.swift",
76+
variantsOutputFilePath,
8077
variantsGybFile.absolute().description
8178
).capture(stream: .stderr)
82-
let variantsFilePath = "\(variantsGybFile.parent().abbreviate().string)/Variants.swift"
83-
handleGybErrors(message: gybStdErr, variantsFilePath: variantsFilePath)
84-
logger.logInfo("⚙️ ", item: "'\(variantsFilePath)' has been generated with success", color: .green)
79+
handleGybErrors(message: gybStdErr, variantsFilePath: variantsOutputFilePath)
80+
logger.logInfo("⚙️ ", item: "'\(variantsOutputFilePath)' has been generated with success", color: .green)
8581
}
8682

8783
private func handleGybErrors(message: String?, variantsFilePath: String) {
@@ -118,20 +114,3 @@ class VariantsFileFactory {
118114

119115
let logger: Logger
120116
}
121-
122-
fileprivate extension Sequence where Iterator.Element == CustomProperty {
123-
func secrets() -> [CustomProperty] {
124-
return self
125-
.filter({ $0.destination == .project && $0.isEnvironmentVariable })
126-
.map { (property) -> CustomProperty in
127-
return CustomProperty(name: property.name,
128-
value: "os.environ.get('"+property.environmentValue+"')",
129-
destination: property.destination)
130-
}
131-
}
132-
133-
func configurationValues() -> [CustomProperty] {
134-
return self
135-
.filter({ $0.destination == .project && !$0.isEnvironmentVariable })
136-
}
137-
}

Sources/VariantsCore/Factory/iOS/XCConfigFactory.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ class XCConfigFactory: XCFactory {
111111
let infoPath = target.value.source.info
112112
let infoPlistPath = Path("\(configPath)/\(infoPath)")
113113
updateInfoPlist(with: target.value, configFile: infoPlistPath, variant: variant)
114-
114+
115115
/*
116116
* Add custom properties whose values should be read from environment variables
117117
* to `Variants.Secret` as encrypted secrets.

Sources/VariantsCore/Schemas/Configuration.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,19 @@ extension CustomProperty {
7575
}
7676
}
7777
}
78+
79+
extension Sequence where Iterator.Element == CustomProperty {
80+
var projectConfigurationValues: [CustomProperty] {
81+
self.filter({ $0.destination == .project && !$0.isEnvironmentVariable })
82+
}
83+
84+
var projectSecretConfigurationValues: [CustomProperty] {
85+
self
86+
.filter({ $0.destination == .project && $0.isEnvironmentVariable })
87+
.map {
88+
CustomProperty(name: $0.name,
89+
value: "os.environ.get('" + $0.environmentValue + "')",
90+
destination: $0.destination)
91+
}
92+
}
93+
}

Sources/VariantsCore/Schemas/iOS/iOSConfiguration.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,24 +23,28 @@ public struct iOSConfiguration: Codable {
2323
private let signing: iOSSigning?
2424

2525
var pbxproj: String {
26-
return xcodeproj+"/project.pbxproj"
26+
return xcodeproj + "/project.pbxproj"
2727
}
2828

2929
public init(from decoder: Decoder) throws {
3030
let container = try decoder.container(keyedBy: CodingKeys.self)
3131

3232
self.xcodeproj = try container.decode(String.self, forKey: .xcodeproj)
3333
self.targets = try container.decode([String: iOSTarget].self, forKey: .targets)
34-
self.custom = try? container.decode([CustomProperty].self, forKey: .custom)
35-
34+
35+
let globalCustomProperties = try? container.decode([CustomProperty].self, forKey: .custom)
36+
self.custom = globalCustomProperties
37+
3638
let globalPostSwitchScript = try container.decodeIfPresent(String.self, forKey: .postSwitchScript)
3739
let globalSigning = try container.decodeIfPresent(iOSSigning.self, forKey: .signing)
3840
let variantsDict = try container.decode([String: UnnamediOSVariant].self, forKey: .variants)
3941

4042
self.postSwitchScript = globalPostSwitchScript
4143
self.signing = globalSigning
4244
self.variants = try variantsDict
43-
.map { try iOSVariant(from: $1, name: $0, globalSigning: globalSigning, globalPostSwitchScript: globalPostSwitchScript) }
45+
.map {
46+
try iOSVariant(from: $1, name: $0, globalCustomProperties: globalCustomProperties,
47+
globalSigning: globalSigning, globalPostSwitchScript: globalPostSwitchScript) }
4448
}
4549
}
4650

Sources/VariantsCore/Schemas/iOS/iOSVariant.swift

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public struct iOSVariant: Variant {
2121
let postSwitchScript: String?
2222

2323
private let bundleNamingOption: BundleNamingOption
24-
24+
2525
public var title: String { name }
2626

2727
var configName: String {
@@ -38,8 +38,10 @@ public struct iOSVariant: Variant {
3838
}
3939

4040
init(
41-
name: String, versionName: String, versionNumber: Int, appIcon: String?, appName: String?, storeDestination: String?,
42-
custom: [CustomProperty]?, idSuffix: String?, bundleID: String?, variantSigning: iOSSigning?, globalSigning: iOSSigning?,
41+
name: String, versionName: String, versionNumber: Int, appIcon: String?, appName: String?,
42+
storeDestination: String?, idSuffix: String?, bundleID: String?,
43+
globalCustomProperties: [CustomProperty]?, variantCustomProperties: [CustomProperty]?,
44+
globalSigning: iOSSigning?, variantSigning: iOSSigning?,
4345
globalPostSwitchScript: String?, variantPostSwitchScript: String?)
4446
throws {
4547
self.name = name
@@ -49,7 +51,7 @@ public struct iOSVariant: Variant {
4951
self.appName = appName
5052
self.storeDestination = try Self.parseDestination(name: name, destination: storeDestination) ?? .appStore
5153
self.signing = try Self.parseSigning(name: name, variantSigning: variantSigning, globalSigning: globalSigning)
52-
self.custom = custom
54+
self.custom = Self.parseCustomProperties(variantCustom: variantCustomProperties, globalCustom: globalCustomProperties)
5355
self.bundleNamingOption = try Self.parseBundleConfiguration(name: name, idSuffix: idSuffix, bundleID: bundleID)
5456
self.postSwitchScript = Self.parsePostSwitchScript(globalScript: globalPostSwitchScript,
5557
variantScript: variantPostSwitchScript)
@@ -78,14 +80,11 @@ public struct iOSVariant: Variant {
7880
if signing?.matchURL != nil, let exportMethod = signing?.exportMethod {
7981
customDictionary["V_MATCH_PROFILE"] = "\(exportMethod.prefix) \(makeBundleID(for: target))"
8082
}
81-
82-
custom?
83-
.filter { $0.destination == .project && !$0.isEnvironmentVariable }
84-
.forEach { customDictionary[$0.name] = $0.value }
85-
83+
(custom?.projectConfigurationValues ?? []).forEach { customDictionary[$0.name] = $0.value }
84+
8685
return customDictionary.sorted(by: {$0.key < $1.key})
8786
}
88-
87+
8988
private static func parseDestination(name: String, destination: String?) throws -> Destination? {
9089
guard let destinationString = destination else { return nil }
9190

@@ -116,7 +115,13 @@ public struct iOSVariant: Variant {
116115
return nil
117116
}
118117
}
119-
118+
119+
private static func parseCustomProperties(variantCustom: [CustomProperty]?, globalCustom: [CustomProperty]?) -> [CustomProperty] {
120+
let variantCustomProperties = variantCustom ?? []
121+
let globalMinusOverrideProperties = (globalCustom ?? []).filter { !variantCustomProperties.contains($0) }
122+
return globalMinusOverrideProperties + variantCustomProperties
123+
}
124+
120125
private static func parsePostSwitchScript(globalScript: String?, variantScript: String?) -> String? {
121126
if let globalScript = globalScript, let variantScript = variantScript {
122127
return "\(globalScript) && \(variantScript)"
@@ -207,19 +212,22 @@ extension UnnamediOSVariant {
207212
}
208213

209214
extension iOSVariant {
210-
init(from unnamediOSVariant: UnnamediOSVariant, name: String, globalSigning: iOSSigning?, globalPostSwitchScript: String?) throws {
215+
init(from unnamediOSVariant: UnnamediOSVariant, name: String, globalCustomProperties: [CustomProperty]?,
216+
globalSigning: iOSSigning?, globalPostSwitchScript: String?)
217+
throws {
211218
try self.init(
212219
name: name,
213220
versionName: unnamediOSVariant.versionName,
214221
versionNumber: unnamediOSVariant.versionNumber,
215222
appIcon: unnamediOSVariant.appIcon,
216223
appName: unnamediOSVariant.appName,
217224
storeDestination: unnamediOSVariant.storeDestination,
218-
custom: unnamediOSVariant.custom,
219225
idSuffix: unnamediOSVariant.idSuffix,
220226
bundleID: unnamediOSVariant.bundleID,
221-
variantSigning: unnamediOSVariant.signing,
227+
globalCustomProperties: globalCustomProperties,
228+
variantCustomProperties: unnamediOSVariant.custom,
222229
globalSigning: globalSigning,
230+
variantSigning: unnamediOSVariant.signing,
223231
globalPostSwitchScript: globalPostSwitchScript,
224232
variantPostSwitchScript: unnamediOSVariant.postSwitchScript)
225233
}

Templates/ios/Variants.swift.template.gyb

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ def encode(string, cipher):
88
bytes = string.encode("UTF-8")
99
return [ord(bytes[i]) ^ cipher[i % len(cipher)] for i in range(0, len(bytes))]
1010
}%
11-
1211
//
1312
// Variants
1413
//
@@ -29,15 +28,14 @@ public struct Variants {
2928
// MARK: - ConfigurationValueKey
3029
/// Custom configuration values coming from variants.yml as enum cases
3130

32-
public enum ConfigurationValueKey: String {
33-
{% for confValue in configurationValues %}
31+
public enum ConfigurationValueKey: String { {% for confValue in configurationValues %}
3432
case {{ confValue.name }}{% endfor %}
3533
}
3634

3735
static func configurationValue(for key: ConfigurationValueKey) -> Any? {
3836
return Self.configuration[key.rawValue]
39-
}{% endif %}
40-
{% if secrets %}
37+
}
38+
{% endif %}{% if secrets %}
4139
// MARK: - Secrets
4240
/// Encrypted secrets coming from variants.yml as environment variables
4341

@@ -49,8 +47,7 @@ public struct Variants {
4947
${"".join(["0x%02x, " % byte for byte in chunk])}
5048
% end
5149
]
52-
53-
{% for secret in secrets %}
50+
{% for secret in secrets %}
5451
static var {{ secret.name }}: String {
5552
let encoded: [UInt8] = [
5653
% for chunk in chunks(encode({{ secret.value }}, salt), 8):
@@ -60,13 +57,11 @@ public struct Variants {
6057

6158
return decode(encoded, cipher: salt)
6259
}
63-
{% endfor %}
64-
60+
{% endfor %}
6561
private static func decode(_ encoded: [UInt8], cipher: [UInt8]) -> String {
6662
String(decoding: encoded.enumerated().map { (offset, element) in
6763
element ^ cipher[offset % cipher.count]
6864
}, as: UTF8.self)
6965
}
70-
}
71-
{% endif %}
66+
}{% endif %}
7267
}

0 commit comments

Comments
 (0)