Skip to content

Commit

Permalink
Feat: Allow specific signing for debug and release (#251)
Browse files Browse the repository at this point in the history
* feat: allow selecting signing for release and debug independently

* feat: add unit tests

* chore: update docs

* chore: improve plist parameter value declaration

* fix: linter errors
  • Loading branch information
GMinucci authored Jan 28, 2025
1 parent 559a21f commit 5b0f7a4
Show file tree
Hide file tree
Showing 15 changed files with 455 additions and 163 deletions.
6 changes: 3 additions & 3 deletions Sources/VariantsCore/Factory/FastlaneParametersFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class FastlaneParametersFactory: ParametersFactory {
else { return }

// Populate 'fastlane/parameters/match_params.rb' from template
let parameters: [CustomProperty] = variant.signing?.customProperties() ?? []
let parameters: [CustomProperty] = variant.releaseSigning?.customProperties() ?? []
try? createParametersFile(in: StaticPath.Fastlane.matchParametersFile,
renderTemplate: StaticPath.Template.matchParametersFileName,
with: parameters)
Expand All @@ -49,11 +49,11 @@ class FastlaneParametersFactory: ParametersFactory {
.reduce(into: [], { $0.append($1) })
let appBundleID = [variant.makeBundleID(for: configuration.target)]
var context: [String: Any] = [
"export_method": (variant.signing?.exportMethod ?? .appstore).rawValue,
"export_method": (variant.releaseSigning?.exportMethod ?? .appstore).rawValue,
"app_identifiers": appBundleID + extensionBundleIDs
]

if let matchURL = variant.signing?.matchURL {
if let matchURL = variant.releaseSigning?.matchURL {
context["git_url"] = matchURL
} else {
Logger.shared.logWarning(item:
Expand Down
123 changes: 85 additions & 38 deletions Sources/VariantsCore/Factory/iOS/XCConfigFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ protocol XCFactory {
}

class XCConfigFactory: XCFactory {
private enum PListKey {
static let productBundleID = "PRODUCT_BUNDLE_IDENTIFIER"
static let productName = "PRODUCT_NAME"
static let assetCatalogAppIconName = "ASSETCATALOG_COMPILER_APPICON_NAME"
static let testHost = "TEST_HOST"
static let provisioningProfile = "PROVISIONING_PROFILE_SPECIFIER"
static let codeSignStyle = "CODE_SIGN_STYLE"
static let codeSignIdentity = "CODE_SIGN_IDENTITY"
static let developmentTeam = "DEVELOPMENT_TEAM"
}

init(logger: Logger = Logger(verbose: false)) {
self.logger = logger
}
Expand Down Expand Up @@ -90,8 +101,15 @@ class XCConfigFactory: XCFactory {
/*
* Adjust signing configuration in project.pbxproj
*/
updateSigningConfig(for: variant, configuration: configuration, projectPath: xcodeProjPath)
updateSigningConfigForExtensions(for: variant, configuration: configuration, projectPath: xcodeProjPath)
updateSigning(using: variant.releaseSigning, targetName: configuration.target.source.info,
isRelease: true, projectPath: xcodeProjPath)
updateSigning(using: variant.debugSigning, targetName: configuration.target.source.info,
isRelease: false, projectPath: xcodeProjPath)

updateSigningConfigForExtensions(signing: variant.releaseSigning, variant: variant, configuration: configuration,
isRelease: true, projectPath: xcodeProjPath)
updateSigningConfigForExtensions(signing: variant.debugSigning, variant: variant, configuration: configuration,
isRelease: false, projectPath: xcodeProjPath)

/*
* INFO.plist
Expand Down Expand Up @@ -128,23 +146,23 @@ class XCConfigFactory: XCFactory {

// Update main target
let mainTargetSettings = [
"PRODUCT_BUNDLE_IDENTIFIER": "$(V_BUNDLE_ID)",
"PRODUCT_NAME": "$(V_APP_NAME)",
"ASSETCATALOG_COMPILER_APPICON_NAME": "$(V_APP_ICON)"
PListKey.productBundleID: "$(V_BUNDLE_ID)",
PListKey.productName: "$(V_APP_NAME)",
PListKey.assetCatalogAppIconName: "$(V_APP_ICON)"
]
xcodeFactory.modify(mainTargetSettings, in: projectPath, targetName: configuration.target.source.info)

// Update test target
let testTargetSettings = [
"TEST_HOST": "$(BUILT_PRODUCTS_DIR)/$(V_APP_NAME).app/$(V_APP_NAME)"
PListKey.testHost: "$(BUILT_PRODUCTS_DIR)/$(V_APP_NAME).app/$(V_APP_NAME)"
]
xcodeFactory.modify(testTargetSettings, in: projectPath, targetName: configuration.target.testTarget)

// Update extensions
for targetExtension in configuration.extensions.filter({ $0.signed }) {
let bundleID = targetExtension.makeBundleID(variant: variant, target: configuration.target)
let extensionSettings = [
"PRODUCT_BUNDLE_IDENTIFIER": "\(bundleID)"
PListKey.productBundleID: "\(bundleID)"
]
xcodeFactory.modify(extensionSettings, in: projectPath, targetName: targetExtension.name)
}
Expand All @@ -167,58 +185,87 @@ class XCConfigFactory: XCFactory {
}
}

private func updateSigningConfig(
for variant: iOSVariant,
configuration: iOSConfiguration,
private func updateSigning(
using signing: iOSSigning?,
targetName: String,
isRelease: Bool,
projectPath: Path
) {
guard
let exportMethod = variant.signing?.exportMethod,
let teamName = variant.signing?.teamName,
let teamID = variant.signing?.teamID,
!teamID.isEmpty,
!teamName.isEmpty
let signing,
let teamID = signing.teamID, !teamID.isEmpty
else { return }

let isDistribution = exportMethod == .appstore || exportMethod == .enterprise
let certType = isDistribution ? "Distribution" : "Development"
let signingSettings = [
"PROVISIONING_PROFILE_SPECIFIER": "$(V_MATCH_PROFILE)",
"CODE_SIGN_STYLE": "Manual",
"CODE_SIGN_IDENTITY": "Apple \(certType): \(teamName) (\(teamID))"
var signingSettings = [
PListKey.provisioningProfile: "",
PListKey.codeSignStyle: "\(signing.style.rawValue.capitalized)",
PListKey.developmentTeam: "\(teamID)"
]

if signing.style == .manual {
guard
let exportMethod = signing.exportMethod,
let teamName = signing.teamName, !teamName.isEmpty
else { return }

let isDistribution = exportMethod == .appstore || exportMethod == .enterprise
let certType = isDistribution ? "Distribution" : "Development"
signingSettings[PListKey.provisioningProfile] = "$(V_MATCH_PROFILE)"
signingSettings[PListKey.codeSignIdentity] = "Apple \(certType): \(teamName) (\(teamID))"
}

let xcodeFactory = XcodeProjFactory()
xcodeFactory.modify(signingSettings, in: projectPath, targetName: configuration.target.source.info)
xcodeFactory.modify(
signingSettings,
in: projectPath,
targetName: targetName,
configurationTypes: [isRelease ? .release : .debug])
}

private func updateSigningConfigForExtensions(
for variant: iOSVariant,
signing: iOSSigning?,
variant: iOSVariant,
configuration: iOSConfiguration,
isRelease: Bool,
projectPath: Path
) {
let targetExtensions = configuration.extensions.filter({ $0.signed })
guard

guard
!targetExtensions.isEmpty,
let exportMethod = variant.signing?.exportMethod,
let teamName = variant.signing?.teamName,
let teamID = variant.signing?.teamID,
!teamID.isEmpty,
!teamName.isEmpty
let signing,
let teamID = signing.teamID, !teamID.isEmpty
else { return }

let isDistribution = exportMethod == .appstore || exportMethod == .enterprise
let certType = isDistribution ? "Distribution" : "Development"
var signingSettings = [
PListKey.provisioningProfile: "",
PListKey.codeSignStyle: "\(signing.style.rawValue.capitalized)",
PListKey.developmentTeam: "\(teamID)"
]

if signing.style == .manual {
guard
let exportMethod = signing.exportMethod,
let teamName = signing.teamName, !teamName.isEmpty
else { return }

let isDistribution = exportMethod == .appstore || exportMethod == .enterprise
let certType = isDistribution ? "Distribution" : "Development"
signingSettings[PListKey.codeSignIdentity] = "Apple \(certType): \(teamName) (\(teamID))"
}

let xcodeFactory = XcodeProjFactory()
for targetExtension in targetExtensions {
let bundleID = targetExtension.makeBundleID(variant: variant, target: configuration.target)
let signingSettings = [
"PROVISIONING_PROFILE_SPECIFIER": "\(exportMethod.prefix) \(bundleID)",
"CODE_SIGN_STYLE": "Manual",
"CODE_SIGN_IDENTITY": "Apple \(certType): \(teamName) (\(teamID))"
]
xcodeFactory.modify(signingSettings, in: projectPath, targetName: targetExtension.name)
if signing.style == .manual, let exportMethod = signing.exportMethod {
let bundleID = targetExtension.makeBundleID(variant: variant, target: configuration.target)
signingSettings[PListKey.provisioningProfile] = "\(exportMethod.prefix) \(bundleID)"
}

xcodeFactory.modify(
signingSettings,
in: projectPath,
targetName: targetExtension.name,
configurationTypes: [isRelease ? .release : .debug])
}
}

Expand Down
17 changes: 12 additions & 5 deletions Sources/VariantsCore/Factory/iOS/XcodeProjFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@ import XcodeProj
import PathKit

struct XcodeProjFactory {
enum BuildConfigType: String, CaseIterable {
case debug, release
}

private let logger: Logger

init(enableVerboseLog: Bool = false) {
init(enableVerboseLog: Bool = true) {
logger = Logger(verbose: enableVerboseLog)
}

Expand Down Expand Up @@ -152,23 +156,26 @@ struct XcodeProjFactory {
/// - keyValue: Key/value pair to be modified
/// - projectPath: Path to Xcode project
/// - targetName: Name of the target on which the `buildSettings` should be changed.
/// - asTestSettings: If true, add configuraiton to test/non-host targets.
/// - silent: Flag to determine if final logs are necessary
func modify(_ keyValue: [String: String],
in projectPath: Path,
targetName: String,
asTestSettings: Bool = false,
configurationTypes: [BuildConfigType] = BuildConfigType.allCases,
silent: Bool = false) {
do {
let project = try XcodeProj(path: projectPath)
let configTypeNames = configurationTypes.map { $0.rawValue.lowercased() }
logger.logInfo("Updating: ", item: projectPath)

project.pbxproj.buildConfigurations
.filter({ ($0.buildSettings["INFOPLIST_FILE"] as? String)?.contains(targetName) ?? false })
.filter({ configTypeNames.contains($0.name.lowercased()) })
.forEach { conf in
logger.logDebug(
"Build configuration type: ", item: conf.name, indentationLevel: 1, color: .blue)
keyValue.forEach { (key, value) in
Logger.shared.logDebug("Item: ", item: "\(key) = \(value)",
indentationLevel: 1, color: .purple)
logger.logDebug(
"Item: ", item: "\(key) = \(value)", indentationLevel: 2, color: .purple)
conf.buildSettings[key] = value
}
}
Expand Down
56 changes: 40 additions & 16 deletions Sources/VariantsCore/Schemas/iOS/iOSSigning.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,41 @@
import Foundation

// swiftlint:disable:next type_name
struct iOSSigning: Codable {
struct iOSSigning: Codable, Equatable {
let teamName: String?
let teamID: String?
let exportMethod: Type?
let exportMethod: ExportMethod?
let matchURL: String?

let style: SigningStyle

enum CodingKeys: String, CodingKey {
case teamName = "team_name"
case teamID = "team_id"
case exportMethod = "export_method"
case matchURL = "match_url"
case style
}

init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.teamName = try container.decodeIfPresent(String.self, forKey: .teamName)
self.teamID = try container.decodeIfPresent(String.self, forKey: .teamID)
self.exportMethod = try container.decodeIfPresent(ExportMethod.self, forKey: .exportMethod)
self.matchURL = try container.decodeIfPresent(String.self, forKey: .matchURL)
self.style = try container.decodeIfPresent(iOSSigning.SigningStyle.self, forKey: .style) ?? .manual
}

init(teamName: String?, teamID: String?, exportMethod: ExportMethod?, matchURL: String?, style: SigningStyle) {
self.teamName = teamName
self.teamID = teamID
self.exportMethod = exportMethod
self.matchURL = matchURL
self.style = style
}
}

extension iOSSigning {
enum `Type`: String, Codable {
enum ExportMethod: String, Codable {
case appstore
case development
case adhoc
Expand All @@ -42,6 +61,11 @@ extension iOSSigning {
}
}
}

enum SigningStyle: String, Codable {
case automatic
case manual
}
}

extension iOSSigning {
Expand All @@ -62,7 +86,7 @@ extension iOSSigning {
for property in mirroredObject.children {
if let label = property.label {
let stringValue = property.value as? String
let typeValue = (property.value as? Type)?.rawValue
let typeValue = (property.value as? ExportMethod)?.rawValue
if let value = stringValue ?? typeValue {
customProperties.append(CustomProperty(name: label.uppercased(),
value: value,
Expand All @@ -77,17 +101,17 @@ extension iOSSigning {
infix operator ~: AdditionPrecedence
extension iOSSigning {
static func ~ (lhs: iOSSigning, rhs: iOSSigning?) throws -> iOSSigning {
let signing = iOSSigning(teamName: lhs.teamName ?? rhs?.teamName,
teamID: lhs.teamID ?? rhs?.teamID,
exportMethod: lhs.exportMethod ?? rhs?.exportMethod,
matchURL: lhs.matchURL ?? rhs?.matchURL)
if signing.teamName == nil {
throw iOSSigning.missingParameterError(CodingKeys.teamName)
} else if signing.teamID == nil {
throw iOSSigning.missingParameterError(CodingKeys.teamID)
} else if signing.exportMethod == nil {
throw iOSSigning.missingParameterError(CodingKeys.exportMethod)
}
let signing = iOSSigning(
teamName: lhs.teamName ?? rhs?.teamName,
teamID: lhs.teamID ?? rhs?.teamID,
exportMethod: lhs.exportMethod ?? rhs?.exportMethod,
matchURL: lhs.matchURL ?? rhs?.matchURL,
style: lhs.style)

guard signing.teamName != nil else { throw iOSSigning.missingParameterError(CodingKeys.teamName) }
guard signing.teamID != nil else { throw iOSSigning.missingParameterError(CodingKeys.teamID) }
guard signing.exportMethod != nil else { throw iOSSigning.missingParameterError(CodingKeys.exportMethod) }

return signing
}
}
Loading

0 comments on commit 5b0f7a4

Please sign in to comment.