Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Automatically detect signing certificate #252

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions Sources/VariantsCore/Factory/iOS/XCConfigFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -207,11 +207,15 @@ class XCConfigFactory: XCFactory {
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))"

if signing.autoDetectSigningIdentity,
let fetchedSigningIdentity = signing.codeSigningIdentity {
signingSettings[PListKey.codeSignIdentity] = fetchedSigningIdentity
} else {
signingSettings[PListKey.codeSignIdentity] = "Apple \(exportMethod.certType): \(teamName) (\(teamID))"
}
}

let xcodeFactory = XcodeProjFactory()
Expand Down Expand Up @@ -248,10 +252,12 @@ class XCConfigFactory: XCFactory {
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))"

if signing.autoDetectSigningIdentity, let fetchedSigningIdentity = signing.codeSigningIdentity {
signingSettings[PListKey.codeSignIdentity] = fetchedSigningIdentity
} else {
signingSettings[PListKey.codeSignIdentity] = "Apple \(exportMethod.certType): \(teamName) (\(teamID))"
}
}

let xcodeFactory = XcodeProjFactory()
Expand Down
59 changes: 56 additions & 3 deletions Sources/VariantsCore/Schemas/iOS/iOSSigning.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,19 @@ struct iOSSigning: Codable, Equatable {
let exportMethod: ExportMethod?
let matchURL: String?
let style: SigningStyle
let autoDetectSigningIdentity: Bool

var codeSigningIdentity: String? {
fetchSigningCertificate()
}

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

init(from decoder: any Decoder) throws {
Expand All @@ -30,14 +36,22 @@ struct iOSSigning: Codable, Equatable {
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
let signingIdentity = try container.decodeIfPresent(Bool.self, forKey: .autoDetectSigningIdentity)
self.autoDetectSigningIdentity = signingIdentity ?? true
}

init(teamName: String?, teamID: String?, exportMethod: ExportMethod?, matchURL: String?, style: SigningStyle) {

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

Expand All @@ -60,6 +74,14 @@ extension iOSSigning {
return "match InHouse"
}
}

var isDistribution: Bool {
self == .appstore || self == .enterprise
}

var certType: String {
isDistribution ? "Distribution" : "Development"
}
}

enum SigningStyle: String, Codable {
Expand Down Expand Up @@ -106,7 +128,8 @@ extension iOSSigning {
teamID: lhs.teamID ?? rhs?.teamID,
exportMethod: lhs.exportMethod ?? rhs?.exportMethod,
matchURL: lhs.matchURL ?? rhs?.matchURL,
style: lhs.style)
style: lhs.style,
autoDetectSigningIdentity: lhs.autoDetectSigningIdentity)

guard signing.teamName != nil else { throw iOSSigning.missingParameterError(CodingKeys.teamName) }
guard signing.teamID != nil else { throw iOSSigning.missingParameterError(CodingKeys.teamID) }
Expand All @@ -115,3 +138,33 @@ extension iOSSigning {
return signing
}
}

extension iOSSigning {
private func fetchSigningCertificate() -> String? {
guard let teamID else { return nil }

do {
let output = try Bash("security", arguments: "find-identity", "-v", "-p", "codesigning")
.capture()

guard let output else { return nil }
let lines = output.split(separator: "\n")

let matches = lines.compactMap { line -> String? in
guard line.contains(teamID) else { return nil }

if let teamName, !line.contains(teamName) { return nil }
if let certType = exportMethod?.certType.lowercased(),
!line.contains(certType) { return nil }

let components = line.split(separator: "\"", maxSplits: 2, omittingEmptySubsequences: false)
guard components.count > 1 else { return nil }

return String(components[1])
}
return matches.first
} catch {
return nil
}
}
}
14 changes: 12 additions & 2 deletions Templates/ios/variants-template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,12 @@ ios:
# match_url: "git@github.com:sample/match.git"
team_name: "iPhone Distribution"
team_id: "AB1234567D"


# Should Variant try to auto detect signing identity
# if set to true, Variant will use the team_id, team_name and export_method
# to detect code signing identity from the Keychain Access
# default value is `true`
auto_detect_signing_identity: true
#
# custom: - Not required.
#
Expand Down Expand Up @@ -100,7 +105,12 @@ ios:
team_name: "iPhone Distribution"
team_id: "AB1234567D"
export_method: "appstore"


# Should Variant try to auto detect signing identity
# if set to true, Variant will use the team_id, team_name and export_method
# to detect code signing identity from the Keychain Access
# default value is `true`
auto_detect_signing_identity: true
# ----------------------------------------------------------------------
# custom: - Not required.
#
Expand Down
7 changes: 6 additions & 1 deletion Tests/VariantsCoreTests/FastlaneParametersFactoryTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,12 @@ class FastlaneParametersFactoryTests: XCTestCase {
bundleID: nil,
globalCustomProperties: nil,
variantCustomProperties: nil,
globalSigning: iOSSigning(teamName: "", teamID: "", exportMethod: .appstore, matchURL: "", style: .manual),
globalSigning: iOSSigning(teamName: "",
teamID: "",
exportMethod: .appstore,
matchURL: "",
style: .manual,
autoDetectSigningIdentity: true),
debugSigning: nil,
releaseSigning: nil,
globalPostSwitchScript: "echo global",
Expand Down
7 changes: 6 additions & 1 deletion Tests/VariantsCoreTests/VariantsFileFactoryTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,12 @@ class VariantsFileFactoryTests: XCTestCase {
variantCustomProperties: [
CustomProperty(name: "PROPERTY_A", value: "VALUE_A", destination: .project),
CustomProperty(name: "PROPERTY_B", value: "VALUE_B", destination: .project)],
globalSigning: iOSSigning(teamName: "", teamID: "", exportMethod: .appstore, matchURL: "", style: .manual),
globalSigning: iOSSigning(teamName: "",
teamID: "",
exportMethod: .appstore,
matchURL: "",
style: .manual,
autoDetectSigningIdentity: true),
debugSigning: nil,
releaseSigning: nil,
globalPostSwitchScript: "echo global",
Expand Down
51 changes: 29 additions & 22 deletions Tests/VariantsCoreTests/iOSSigningTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ final class iOSSigningTests: XCTestCase {
teamID: nil,
exportMethod: .appstore,
matchURL: "url",
style: .manual)
style: .manual,
autoDetectSigningIdentity: true)
let signing1 = iOSSigning(teamName: nil,
teamID: "new id",
exportMethod: .development,
matchURL: nil,
style: .manual)
style: .manual,
autoDetectSigningIdentity: true)

do {
let result = try signing ~ signing1
Expand All @@ -48,12 +50,14 @@ final class iOSSigningTests: XCTestCase {
teamID: nil,
exportMethod: .appstore,
matchURL: "url",
style: .manual)
style: .manual,
autoDetectSigningIdentity: true)
let signing1 = iOSSigning(teamName: nil,
teamID: "new id",
exportMethod: .development,
matchURL: "new url",
style: .manual)
style: .manual,
autoDetectSigningIdentity: true)
let expectedError = RuntimeError("""
Missing: 'signing.team_name'
At least one variant doesn't contain 'signing.team_name' in its configuration.
Expand All @@ -74,12 +78,14 @@ final class iOSSigningTests: XCTestCase {
teamID: nil,
exportMethod: .appstore,
matchURL: "url",
style: .manual)
style: .manual,
autoDetectSigningIdentity: true)
let signing1 = iOSSigning(teamName: "Name",
teamID: nil,
exportMethod: .development,
matchURL: "new url",
style: .manual)
style: .manual,
autoDetectSigningIdentity: true)
let expectedError = RuntimeError("""
Missing: 'signing.team_id'
At least one variant doesn't contain 'signing.team_id' in its configuration.
Expand All @@ -100,7 +106,8 @@ final class iOSSigningTests: XCTestCase {
teamID: nil,
exportMethod: .enterprise,
matchURL: "url",
style: .manual)
style: .manual,
autoDetectSigningIdentity: true)

let expected = [CustomProperty(name: "TEAMNAME", value: "NAME", destination: .fastlane),
CustomProperty(name: "EXPORTMETHOD", value: "match InHouse", destination: .fastlane),
Expand All @@ -121,7 +128,7 @@ final class iOSSigningTests: XCTestCase {
}

func testOnlyGlobalSigning() {
let globalSigning = iOSSigning(teamName: "global team name", teamID: "global_team_id", exportMethod: .appstore, matchURL: "global match url", style: .manual)
let globalSigning = iOSSigning(teamName: "global team name", teamID: "global_team_id", exportMethod: .appstore, matchURL: "global match url", style: .manual, autoDetectSigningIdentity: true)
let unnamedVariant = makeUnnamedVariant(signing: nil, debugSigning: nil, releaseSigning: nil)
guard
let variant = try? iOSVariant(from: unnamedVariant, name: "", globalCustomProperties: nil, globalSigning: globalSigning, globalPostSwitchScript: nil)
Expand All @@ -132,8 +139,8 @@ final class iOSSigningTests: XCTestCase {
}

func testGlobalAndVariantSigning() {
let globalSigning = iOSSigning(teamName: "global team name", teamID: "global_team_id", exportMethod: .appstore, matchURL: "global match url", style: .manual)
let variantSigning = iOSSigning(teamName: "variant team name", teamID: "variant_team_id", exportMethod: .appstore, matchURL: "variant match url", style: .manual)
let globalSigning = iOSSigning(teamName: "global team name", teamID: "global_team_id", exportMethod: .appstore, matchURL: "global match url", style: .manual, autoDetectSigningIdentity: true)
let variantSigning = iOSSigning(teamName: "variant team name", teamID: "variant_team_id", exportMethod: .appstore, matchURL: "variant match url", style: .manual, autoDetectSigningIdentity: false)
let unnamedVariant = makeUnnamedVariant(signing: variantSigning, debugSigning: nil, releaseSigning: nil)
guard
let variant = try? iOSVariant(from: unnamedVariant, name: "", globalCustomProperties: nil, globalSigning: globalSigning, globalPostSwitchScript: nil)
Expand All @@ -144,8 +151,8 @@ final class iOSSigningTests: XCTestCase {
}

func testGlobalAndVariantReleaseSigning() {
let globalSigning = iOSSigning(teamName: "global team name", teamID: "global_team_id", exportMethod: .appstore, matchURL: "global match url", style: .manual)
let variantReleaseSigning = iOSSigning(teamName: "variant team name", teamID: "variant_team_id", exportMethod: .appstore, matchURL: "variant match url", style: .manual)
let globalSigning = iOSSigning(teamName: "global team name", teamID: "global_team_id", exportMethod: .appstore, matchURL: "global match url", style: .manual, autoDetectSigningIdentity: true)
let variantReleaseSigning = iOSSigning(teamName: "variant team name", teamID: "variant_team_id", exportMethod: .appstore, matchURL: "variant match url", style: .manual, autoDetectSigningIdentity: false)
let unnamedVariant = makeUnnamedVariant(signing: nil, debugSigning: nil, releaseSigning: variantReleaseSigning)
guard
let variant = try? iOSVariant(from: unnamedVariant, name: "", globalCustomProperties: nil, globalSigning: globalSigning, globalPostSwitchScript: nil)
Expand All @@ -156,8 +163,8 @@ final class iOSSigningTests: XCTestCase {
}

func testGlobalAndVariantDebugSigning() {
let globalSigning = iOSSigning(teamName: "global team name", teamID: "global_team_id", exportMethod: .appstore, matchURL: "global match url", style: .manual)
let variantDebugSigning = iOSSigning(teamName: "variant team name", teamID: "variant_team_id", exportMethod: .appstore, matchURL: "variant match url", style: .manual)
let globalSigning = iOSSigning(teamName: "global team name", teamID: "global_team_id", exportMethod: .appstore, matchURL: "global match url", style: .manual, autoDetectSigningIdentity: true)
let variantDebugSigning = iOSSigning(teamName: "variant team name", teamID: "variant_team_id", exportMethod: .appstore, matchURL: "variant match url", style: .manual, autoDetectSigningIdentity: false)
let unnamedVariant = makeUnnamedVariant(signing: nil, debugSigning: variantDebugSigning, releaseSigning: nil)
guard
let variant = try? iOSVariant(from: unnamedVariant, name: "", globalCustomProperties: nil, globalSigning: globalSigning, globalPostSwitchScript: nil)
Expand All @@ -168,11 +175,11 @@ final class iOSSigningTests: XCTestCase {
}

func testGlobalAndVariantReleaseDebugSigning() {
let globalSigning = iOSSigning(teamName: "global team name", teamID: "global_team_id", exportMethod: .appstore, matchURL: "global match url", style: .manual)
let variantDebugSigning = iOSSigning(teamName: "variant debug team name", teamID: "variant_debug_team_id",
exportMethod: .appstore, matchURL: "variant match url", style: .manual)
let variantReleaseSigning = iOSSigning(teamName: "variant release team name", teamID: "variant_release_team_id",
exportMethod: .appstore, matchURL: "variant match url", style: .manual)
let globalSigning = iOSSigning(teamName: "global team name", teamID: "global_team_id", exportMethod: .appstore, matchURL: "global match url", style: .manual, autoDetectSigningIdentity: true)
let variantDebugSigning = iOSSigning(teamName: "variant debug team name", teamID: "variant_debug_team_id",
exportMethod: .appstore, matchURL: "variant match url", style: .manual, autoDetectSigningIdentity: true)
let variantReleaseSigning = iOSSigning(teamName: "variant release team name", teamID: "variant_release_team_id",
exportMethod: .appstore, matchURL: "variant match url", style: .manual, autoDetectSigningIdentity: false)
let unnamedVariant = makeUnnamedVariant(signing: nil, debugSigning: variantDebugSigning, releaseSigning: variantReleaseSigning)
guard
let variant = try? iOSVariant(from: unnamedVariant, name: "", globalCustomProperties: nil, globalSigning: globalSigning, globalPostSwitchScript: nil)
Expand All @@ -183,9 +190,9 @@ final class iOSSigningTests: XCTestCase {
}

func testGlobalAndVariantSigningAndDebugSigning() {
let globalSigning = iOSSigning(teamName: "global team name", teamID: "global_team_id", exportMethod: .appstore, matchURL: "global match url", style: .manual)
let variantSigning = iOSSigning(teamName: "variant team name", teamID: "variant_team_id", exportMethod: .appstore, matchURL: "variant match url", style: .manual)
let variantDebugSigning = iOSSigning(teamName: "variant debug team name", teamID: "variant_debug_team_id", exportMethod: .appstore, matchURL: "variant match url", style: .manual)
let globalSigning = iOSSigning(teamName: "global team name", teamID: "global_team_id", exportMethod: .appstore, matchURL: "global match url", style: .manual, autoDetectSigningIdentity: true)
let variantSigning = iOSSigning(teamName: "variant team name", teamID: "variant_team_id", exportMethod: .appstore, matchURL: "variant match url", style: .manual, autoDetectSigningIdentity: true)
let variantDebugSigning = iOSSigning(teamName: "variant debug team name", teamID: "variant_debug_team_id", exportMethod: .appstore, matchURL: "variant match url", style: .manual, autoDetectSigningIdentity: true)
let unnamedVariant = makeUnnamedVariant(signing: variantSigning, debugSigning: variantDebugSigning, releaseSigning: nil)
guard
let variant = try? iOSVariant(from: unnamedVariant, name: "", globalCustomProperties: nil, globalSigning: globalSigning, globalPostSwitchScript: nil)
Expand Down
2 changes: 1 addition & 1 deletion Tests/VariantsCoreTests/iOSTargetExtensionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import XCTest
@testable import VariantsCore

class iOSTargetExtensionTests: XCTestCase {
private let validSigning = iOSSigning(teamName: "Signing Team Name", teamID: "AB12345CD", exportMethod: .appstore, matchURL: "git@github.com:sample/match.git", style: .manual)
private let validSigning = iOSSigning(teamName: "Signing Team Name", teamID: "AB12345CD", exportMethod: .appstore, matchURL: "git@github.com:sample/match.git", style: .manual, autoDetectSigningIdentity: true)
private let target = iOSTarget(name: "Target Name", app_icon: "AppIcon", bundleId: "com.Company.ValidName", testTarget: "ValidNameTests", source: iOSSource(path: "", info: "", config: ""))

func testTargetExtensionCreationWithBundleSuffix() {
Expand Down
Loading
Loading