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: Handling signing of other targets (extensions) #250

Merged
merged 6 commits into from
Jan 27, 2025
Merged
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
10 changes: 2 additions & 8 deletions Sources/VariantsCore/Custom Types/Project/AndroidProject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,7 @@ class AndroidProject: Project {
}

private func createVariants(with configuration: AndroidConfiguration, spec: String) throws {
guard let defaultVariant = configuration.variants
.first(where: { $0.name.lowercased() == "default" }) else {
throw ValidationError("Variant 'default' not found.")
}
let defaultVariant = try configuration.defaultVariant
try gradleFactory.createScript(with: configuration, variant: defaultVariant)
}

Expand Down Expand Up @@ -138,10 +135,7 @@ class AndroidProject: Project {
"""

if StaticPath.Fastlane.baseFolder.isDirectory {
guard let defaultVariant = configuration.variants
.first(where: { $0.name.lowercased() == "default" }) else {
throw ValidationError("Variant 'default' not found.")
}
let defaultVariant = try configuration.defaultVariant

// Create 'variants_params.rb' with parameters whose
// destination are set as '.fastlane'
Expand Down
39 changes: 15 additions & 24 deletions Sources/VariantsCore/Custom Types/Project/iOSProject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,23 +83,23 @@ class iOSProject: Project {

// Create 'variants.xcconfig' with parameters whose
// destination are set as '.project'
let configPath = Path(spec).absolute().parent()
do {
try configFactory.createConfig(
for: configuration.target,
variant: variant,
xcodeProj: configuration.xcodeproj,
configPath: Path(spec).absolute().parent())
for: variant,
configuration: configuration,
configPath: configPath)
} catch {
Logger.shared.logFatal(item: error.localizedDescription)
}

// Update `variants_params.rb` with custom fastlane properties
var customProperties: [CustomProperty] = (variant.custom ?? []) + (configuration.custom ?? [])
customProperties.append(variant.destinationProperty)
// Create 'variants_params.rb' with parameters whose
// destination are set as '.fastlane'
try? storeFastlaneParams(customProperties)
try storeFastlaneParams(customProperties)

try parametersFactory.createMatchFile(for: variant, target: configuration.target)
// Update `Matchfile` with signing configurations
try parametersFactory.createMatchFile(for: variant, configuration: configuration)
}

private func runPostSwitchScript(_ script: String) throws {
Expand All @@ -108,19 +108,15 @@ class iOSProject: Project {
}

private func createVariants(with configuration: iOSConfiguration, spec: String) throws {
guard let defaultVariant = configuration.variants
.first(where: { $0.name.lowercased() == "default" }) else {
throw ValidationError("Variant 'default' not found.")
}
let defaultVariant = try configuration.defaultVariant

// Create 'variants.xcconfig' with parameters whose
// destination are set as '.project'
let configPath = Path(spec).absolute().parent()
do {
try configFactory.createConfig(
for: configuration.target,
variant: defaultVariant,
xcodeProj: configuration.xcodeproj,
for: defaultVariant,
configuration: configuration,
configPath: configPath)
} catch {
Logger.shared.logFatal(item: error.localizedDescription)
Expand Down Expand Up @@ -166,20 +162,15 @@ class iOSProject: Project {
"""

if StaticPath.Fastlane.baseFolder.isDirectory {
let defaultVariant = try configuration.defaultVariant

guard let defaultVariant = configuration.variants
.first(where: { $0.name.lowercased() == "default" })
else {
throw ValidationError("Variant 'default' not found.")
}
// Update `variants_params.rb` with custom fastlane properties
var customProperties: [CustomProperty] = (defaultVariant.custom ?? []) + (configuration.custom ?? [])
customProperties.append(defaultVariant.destinationProperty)

// Create 'variants_params.rb' with parameters whose
// destination are set as '.fastlane'
try storeFastlaneParams(customProperties)

try parametersFactory.createMatchFile(for: defaultVariant, target: configuration.target)
// Update `Matchfile` with signing configurations
try parametersFactory.createMatchFile(for: defaultVariant, configuration: configuration)

setupCompleteMessage =
"""
Expand Down
13 changes: 9 additions & 4 deletions Sources/VariantsCore/Factory/FastlaneParametersFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import PathKit

protocol ParametersFactory {
func createParametersFile(in file: Path, renderTemplate: String, with parameters: [CustomProperty]) throws
func createMatchFile(for variant: iOSVariant, target: iOSTarget) throws
func createMatchFile(for variant: iOSVariant, configuration: iOSConfiguration) throws
func render(context: [String: Any], renderTemplate: String) throws -> Data?
func write(_ data: Data, using parametersFile: Path) throws
}
Expand All @@ -31,7 +31,7 @@ class FastlaneParametersFactory: ParametersFactory {
try write(data, using: file)
}

func createMatchFile(for variant: iOSVariant, target: iOSTarget) throws {
func createMatchFile(for variant: iOSVariant, configuration: iOSConfiguration) throws {
// Return immediately if folder 'fastlane/' doesn't exist.
guard StaticPath.Fastlane.baseFolder.exists && StaticPath.Fastlane.baseFolder.isDirectory
else { return }
Expand All @@ -43,9 +43,14 @@ class FastlaneParametersFactory: ParametersFactory {
with: parameters)

// Populate 'fastlane/Matchfile' from template
var context = [
let extensionBundleIDs = configuration.extensions
.filter { $0.signed }
.map { $0.makeBundleID(variant: variant, target: configuration.target) }
.reduce(into: [], { $0.append($1) })
let appBundleID = [variant.makeBundleID(for: configuration.target)]
var context: [String: Any] = [
"export_method": (variant.signing?.exportMethod ?? .appstore).rawValue,
"bundle_id": variant.makeBundleID(for: target)
"app_identifiers": appBundleID + extensionBundleIDs
]

if let matchURL = variant.signing?.matchURL {
Expand Down
120 changes: 76 additions & 44 deletions Sources/VariantsCore/Factory/iOS/XCConfigFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
// Created by Arthur Alves
//

// swiftlint:disable file_length

import Foundation
import ArgumentParser
import PathKit
Expand All @@ -15,10 +17,7 @@ public typealias DoesFileExist = (exists: Bool, path: Path?)
protocol XCFactory {
func write(_ stringContent: String, toFile file: Path, force: Bool) -> (Bool, Path?)
func writeJSON<T>(_ encodableObject: T, toFile file: Path) -> (Bool, Path?) where T: Encodable
func createConfig(for target: iOSTarget,
variant: iOSVariant,
xcodeProj: String?,
configPath: Path) throws
func createConfig(for variant: iOSVariant, configuration: iOSConfiguration, configPath: Path) throws
}

class XCConfigFactory: XCFactory {
Expand Down Expand Up @@ -62,19 +61,11 @@ class XCConfigFactory: XCFactory {
}
}

func createConfig(for target: iOSTarget,
variant: iOSVariant,
xcodeProj: String?,
configPath: Path) throws {

func createConfig(for variant: iOSVariant, configuration: iOSConfiguration, configPath: Path) throws {
let logger = Logger.shared
guard let xcodeProj = xcodeProj
else {
throw RuntimeError("Attempting to create \(xcconfigFileName) - Path to Xcode Project not found")
}
let xcodeProjPath = Path(xcodeProj)
let configString = target.source.config

let xcodeProjPath = Path(configuration.xcodeproj)
let configString = configuration.target.source.config

logger.logInfo("Checking if \(xcconfigFileName) exists", item: "")
let xcodeConfigFolder = Path("\(configPath)/\(configString)")
guard xcodeConfigFolder.isDirectory else {
Expand All @@ -89,24 +80,25 @@ class XCConfigFactory: XCFactory {

_ = write("", toFile: xcodeConfigPath, force: true)
logger.logInfo("Created file: ", item: "'\(xcconfigFileName)' at \(xcodeConfigPath.parent().abbreviate().description)")
populateConfig(for: target, configFile: xcodeConfigPath, variant: variant)
populateConfig(for: configuration.target, configFile: xcodeConfigPath, variant: variant)

/*
* If template files should be added to Xcode Project
*/
addToXcode(xcodeConfigPath, toProject: xcodeProjPath, sourceRoot: configPath, target: target, variant: variant)
addToXcode(xcodeConfigPath, toProject: xcodeProjPath, sourceRoot: configPath, variant: variant, configuration: configuration)

/*
* Adjust signing configuration in project.pbxproj
*/
updateSigningConfig(for: target, variant: variant, projectPath: xcodeProjPath)
updateSigningConfig(for: variant, configuration: configuration, projectPath: xcodeProjPath)
updateSigningConfigForExtensions(for: variant, configuration: configuration, projectPath: xcodeProjPath)

/*
* INFO.plist
*/
let infoPath = target.source.info
let infoPath = configuration.target.source.info
let infoPlistPath = Path("\(configPath)/\(infoPath)")
updateInfoPlist(with: target, configFile: infoPlistPath, variant: variant)
updateInfoPlist(with: configuration.target, configFile: infoPlistPath, variant: variant)

/*
* Add custom properties whose values should be read from environment variables
Expand All @@ -121,8 +113,8 @@ class XCConfigFactory: XCFactory {
private func addToXcode(_ xcConfigFile: Path,
toProject projectPath: Path,
sourceRoot: Path,
target: iOSTarget,
variant: iOSVariant) {
variant: iOSVariant,
configuration: iOSConfiguration) {
let variantsFile = Path("\(xcConfigFile.parent().absolute().description)/Variants.swift")
do {
let path = try TemplateDirectory().path
Expand All @@ -132,22 +124,31 @@ class XCConfigFactory: XCFactory {
).run()

let xcodeFactory = XcodeProjFactory()
xcodeFactory.add([xcConfigFile, variantsFile], toProject: projectPath, sourceRoot: sourceRoot, target: target)

xcodeFactory.add([xcConfigFile, variantsFile], toProject: projectPath, sourceRoot: sourceRoot, target: configuration.target)

// Update main target
let mainTargetSettings = [
"PRODUCT_BUNDLE_IDENTIFIER": "$(V_BUNDLE_ID)",
"PRODUCT_NAME": "$(V_APP_NAME)",
"ASSETCATALOG_COMPILER_APPICON_NAME": "$(V_APP_ICON)"
]
xcodeFactory.modify(mainTargetSettings, in: projectPath, target: target)

xcodeFactory.modify(
[
"TEST_HOST": "$(BUILT_PRODUCTS_DIR)/$(V_APP_NAME).app/$(V_APP_NAME)"
],
in: projectPath,
target: target,
asTestSettings: true)
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)"
]
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)"
]
xcodeFactory.modify(extensionSettings, in: projectPath, targetName: targetExtension.name)
}

} catch {
logger.logError("❌ ", item: "Failed to add Variants.swift to Xcode Project")
}
Expand All @@ -167,8 +168,8 @@ class XCConfigFactory: XCFactory {
}

private func updateSigningConfig(
for target: iOSTarget,
variant: iOSVariant,
for variant: iOSVariant,
configuration: iOSConfiguration,
projectPath: Path
) {
guard
Expand All @@ -179,19 +180,48 @@ class XCConfigFactory: XCFactory {
!teamName.isEmpty
else { return }

let xcodeFactory = XcodeProjFactory()
var certType = "Development"
if exportMethod == .appstore || exportMethod == .enterprise {
certType = "Distribution"
}
let mainTargetSettings = [
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))"
]
xcodeFactory.modify(mainTargetSettings, in: projectPath, target: target)

let xcodeFactory = XcodeProjFactory()
xcodeFactory.modify(signingSettings, in: projectPath, targetName: configuration.target.source.info)
}


private func updateSigningConfigForExtensions(
for variant: iOSVariant,
configuration: iOSConfiguration,
projectPath: Path
) {
let targetExtensions = configuration.extensions.filter({ $0.signed })
guard
!targetExtensions.isEmpty,
let exportMethod = variant.signing?.exportMethod,
let teamName = variant.signing?.teamName,
let teamID = variant.signing?.teamID,
!teamID.isEmpty,
!teamName.isEmpty
else { return }

let isDistribution = exportMethod == .appstore || exportMethod == .enterprise
let certType = isDistribution ? "Distribution" : "Development"

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)
}
}

private func updateInfoPlist(with target: iOSTarget, configFile: Path, variant: iOSVariant) {
let configFilePath = configFile.absolute().description
do {
Expand Down Expand Up @@ -227,3 +257,5 @@ class XCConfigFactory: XCFactory {
let xcconfigFileName: String = "variants.xcconfig"
let logger: Logger
}

// swiftlint:enable file_length
11 changes: 5 additions & 6 deletions Sources/VariantsCore/Factory/iOS/XcodeProjFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -151,21 +151,20 @@ struct XcodeProjFactory {
/// - Parameters:
/// - keyValue: Key/value pair to be modified
/// - projectPath: Path to Xcode project
/// - target: iOSTarget on which the `buildSettings` should be changed.
/// - 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,
target: iOSTarget,
targetName: String,
asTestSettings: Bool = false,
silent: Bool = false) {
do {
let project = try XcodeProj(path: projectPath)
logger.logInfo("Updating: ", item: projectPath)

let matchingKey = asTestSettings ? target.testTarget : target.source.info

project.pbxproj.buildConfigurations
.filter({ ($0.buildSettings["INFOPLIST_FILE"] as? String)?.contains(matchingKey) ?? false })
.filter({ ($0.buildSettings["INFOPLIST_FILE"] as? String)?.contains(targetName) ?? false })
.forEach { conf in
keyValue.forEach { (key, value) in
Logger.shared.logDebug("Item: ", item: "\(key) = \(value)",
Expand All @@ -191,7 +190,7 @@ private extension XcodeProjFactory {
target: iOSTarget
) throws -> PBXGroup? {
let groupName = "Variants"
let currentVariantsGroup = project.pbxproj.groups.first(where: { $0.name == groupName })
let currentVariantsGroup = project.pbxproj.groups.first(where: { $0.path == groupName || $0.name == groupName })

guard currentVariantsGroup == nil else { return currentVariantsGroup }
let sourceGroup = project.pbxproj.groups.first(where: { $0.path == target.name })
Expand Down
Loading
Loading