Skip to content

Commit

Permalink
Feat: support file organization using folder reference (#249)
Browse files Browse the repository at this point in the history
* feat: remove multiple targets for iOS configuration

* fix: support file grouping in xcode

* fix: linter error

* chore: update docs
  • Loading branch information
GMinucci authored Jan 27, 2025
1 parent 63c41d6 commit 7e39737
Show file tree
Hide file tree
Showing 42 changed files with 2,182 additions and 584 deletions.
92 changes: 39 additions & 53 deletions Sources/VariantsCore/Custom Types/Project/iOSProject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,32 +81,25 @@ class iOSProject: Project {
private func switchTo(_ variant: iOSVariant, spec: String, configuration: iOSConfiguration) throws {
specHelper.logger.logInfo(item: "Found: \(variant.title)")

try configuration.targets
.map { (key: $0.key, value: $0.value)}
.forEach { namedTarget in

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

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 parametersFactory.createMatchFile(using: variant, target: namedTarget.value)
}
// Create 'variants.xcconfig' with parameters whose
// destination are set as '.project'
do {
try configFactory.createConfig(
for: configuration.target,
variant: variant,
xcodeProj: configuration.xcodeproj,
configPath: Path(spec).absolute().parent())
} catch {
Logger.shared.logFatal(item: error.localizedDescription)
}

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 parametersFactory.createMatchFile(for: variant, target: configuration.target)
}

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

private func createVariants(with configuration: iOSConfiguration, spec: String) throws {
try configuration.targets
.map { (key: $0.key, value: $0.value) }
.forEach { target in

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

// Create 'variants.xcconfig' with parameters whose
// destination are set as '.project'
let configPath = Path(spec).absolute().parent()
do {
try configFactory.createConfig(
with: target,
variant: defaultVariant,
xcodeProj: configuration.xcodeproj,
configPath: configPath,
addToXcodeProj: true)
} catch {
Logger.shared.logFatal(item: error.localizedDescription)
}
}
guard let defaultVariant = configuration.variants
.first(where: { $0.name.lowercased() == "default" }) else {
throw ValidationError("Variant 'default' not found.")
}

// 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,
configPath: configPath)
} catch {
Logger.shared.logFatal(item: error.localizedDescription)
}
}

// swiftlint:disable:next function_body_length
Expand All @@ -152,7 +139,7 @@ class iOSProject: Project {
try Bash("cp", arguments: "-R", "\(path.absolute())/ios/_fastlane/", ".")
.run()

let projectSourceFolder = configuration.targets.first?.value.source.path ?? "{{ SOURCE_PATH }}"
let projectSourceFolder = configuration.target.source.path
let baseSetupCompletedMessage =
"""
✅ Your variants configuration was setup
Expand Down Expand Up @@ -181,8 +168,7 @@ class iOSProject: Project {
if StaticPath.Fastlane.baseFolder.isDirectory {

guard let defaultVariant = configuration.variants
.first(where: { $0.name.lowercased() == "default" }),
let namedTarget = configuration.targets.first
.first(where: { $0.name.lowercased() == "default" })
else {
throw ValidationError("Variant 'default' not found.")
}
Expand All @@ -193,7 +179,7 @@ class iOSProject: Project {
// destination are set as '.fastlane'
try storeFastlaneParams(customProperties)

try parametersFactory.createMatchFile(using: defaultVariant, target: namedTarget.value)
try parametersFactory.createMatchFile(for: defaultVariant, target: configuration.target)

setupCompleteMessage =
"""
Expand Down
2 changes: 2 additions & 0 deletions Sources/VariantsCore/Custom Types/TemplateDirectory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ struct TemplateDirectory {

init(
directories: [String] = [
// Uncomment below line while in development to read from local template files
"../../../Templates",
"~/.local/lib/variants/templates",
"./Templates"
]
Expand Down
4 changes: 2 additions & 2 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(using variant: iOSVariant, target: iOSTarget) throws
func createMatchFile(for variant: iOSVariant, target: iOSTarget) 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(using variant: iOSVariant, target: iOSTarget) throws {
func createMatchFile(for variant: iOSVariant, target: iOSTarget) throws {
// Return immediately if folder 'fastlane/' doesn't exist.
guard StaticPath.Fastlane.baseFolder.exists && StaticPath.Fastlane.baseFolder.isDirectory
else { return }
Expand Down
40 changes: 18 additions & 22 deletions Sources/VariantsCore/Factory/iOS/XCConfigFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@ 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(with target: NamedTarget,
func createConfig(for target: iOSTarget,
variant: iOSVariant,
xcodeProj: String?,
configPath: Path,
addToXcodeProj: Bool?) throws
configPath: Path) throws
}

class XCConfigFactory: XCFactory {
Expand Down Expand Up @@ -63,19 +62,18 @@ class XCConfigFactory: XCFactory {
}
}

func createConfig(with target: NamedTarget,
func createConfig(for target: iOSTarget,
variant: iOSVariant,
xcodeProj: String?,
configPath: Path,
addToXcodeProj: Bool? = true) throws {
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.value.source.config
let configString = target.source.config

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

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

/*
* If template files should be added to Xcode Project
*/
if addToXcodeProj ?? false {
addToXcode(xcodeConfigPath, toProject: xcodeProjPath, sourceRoot: configPath, target: target, variant: variant)
}
addToXcode(xcodeConfigPath, toProject: xcodeProjPath, sourceRoot: configPath, target: target, variant: variant)

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

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

/*
* Add custom properties whose values should be read from environment variables
Expand All @@ -125,7 +121,7 @@ class XCConfigFactory: XCFactory {
private func addToXcode(_ xcConfigFile: Path,
toProject projectPath: Path,
sourceRoot: Path,
target: NamedTarget,
target: iOSTarget,
variant: iOSVariant) {
let variantsFile = Path("\(xcConfigFile.parent().absolute().description)/Variants.swift")
do {
Expand All @@ -143,23 +139,23 @@ class XCConfigFactory: XCFactory {
"PRODUCT_NAME": "$(V_APP_NAME)",
"ASSETCATALOG_COMPILER_APPICON_NAME": "$(V_APP_ICON)"
]
xcodeFactory.modify(mainTargetSettings, in: projectPath, target: target.value)
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.value,
target: target,
asTestSettings: true)
} catch {
logger.logError("", item: "Failed to add Variants.swift to Xcode Project")
}
}

private func populateConfig(with target: NamedTarget, configFile: Path, variant: iOSVariant) {
private func populateConfig(for target: iOSTarget, configFile: Path, variant: iOSVariant) {
logger.logInfo("Populating: ", item: "'\(configFile.lastComponent)'")
variant.getDefaultValues(for: target.value).forEach { (key, value) in
variant.getDefaultValues(for: target).forEach { (key, value) in
let stringContent = "\(key) = \(value)"
logger.logDebug("Item: ", item: stringContent, indentationLevel: 1, color: .purple)

Expand All @@ -171,8 +167,8 @@ class XCConfigFactory: XCFactory {
}

private func updateSigningConfig(
for variant: iOSVariant,
inTarget target: NamedTarget,
for target: iOSTarget,
variant: iOSVariant,
projectPath: Path
) {
guard
Expand All @@ -193,7 +189,7 @@ class XCConfigFactory: XCFactory {
"CODE_SIGN_STYLE": "Manual",
"CODE_SIGN_IDENTITY": "Apple \(certType): \(teamName) (\(teamID))"
]
xcodeFactory.modify(mainTargetSettings, in: projectPath, target: target.value)
xcodeFactory.modify(mainTargetSettings, in: projectPath, target: target)
}

private func updateInfoPlist(with target: iOSTarget, configFile: Path, variant: iOSVariant) {
Expand Down
Loading

0 comments on commit 7e39737

Please sign in to comment.