In this tutorial, you’ll learn how to create build scripts in Swift and create operations for your build process. You’ll create four different scripts that cover a wide variety of operations you can perform on your projects. For example:
- Creating a basic script and executing it as you build the project
- Configuring input and output files for scripts
- Reading the project’s settings and adding custom values yourself
- Altering project resources during build time
- Executing command-line operations from your code
- Loading remote data and interrupting the build process
Increment Build Version
import Foundation
@main
enum IncBuildNumber {
static func main() {
// 1
guard let infoFile = ProcessInfo.processInfo
.environment["INFOPLIST_FILE"]
else {
return
}
guard let projectDir = ProcessInfo.processInfo.environment["SRCROOT"] else {
return
}
// 2
if var dict = NSDictionary(contentsOfFile:
projectDir + "/" + infoFile) as? [String: Any] {
guard
let currentVersionString = dict["CFBundleShortVersionString"]
as? String,
let currentBuildNumberString = dict["CFBundleVersion"] as? String,
let currentBuildNumber = Int(currentBuildNumberString)
else {
return
}
// 3
dict["CFBundleVersion"] = "\(currentBuildNumber + 1)"
// 4
if ProcessInfo.processInfo.environment["CONFIGURATION"] == "Release" {
var versionComponents = currentVersionString
.components(separatedBy: ".")
let lastComponent = (Int(versionComponents.last ?? "1") ?? 1)
versionComponents[versionComponents.endIndex - 1] =
"\(lastComponent + 1)"
dict["CFBundleShortVersionString"] = versionComponents
.joined(separator: ".")
}
// 5
(dict as NSDictionary).write(
toFile: projectDir + "/" + infoFile,
atomically: true)
}
}
}
If you leave the phases with their default name Run Script, it will be confusing if there are several. You can
rename
them by clicking on the title to make it editable.
When Xcode executes any run script phase, it shares all its build settings through environment variables.
Note: Reordering this script is important because you want to update the version numbers before Xcode reads the values from the plist file.
Change App Icons
import Foundation
@main
enum AppIconOverlay {
static func main() {
// 1
guard
let srcRoot = ProcessInfo.processInfo.environment["SRCROOT"],
let appIconName = ProcessInfo.processInfo
.environment["ASSETCATALOG_COMPILER_APPICON_NAME"],
let targetName = ProcessInfo.processInfo.environment["TARGET_NAME"]
else {
return
}
// 2
let appIconsPath =
"\(srcRoot)/\(targetName)/Assets.xcassets/\(appIconName).appiconset"
let assetsPath =
"\(srcRoot)/\(targetName)/Assets.xcassets/"
let sourcePath =
"\(srcRoot)/Scripts/AppIcon.appiconset"
// 3
_ = shell("rm -r \(appIconsPath)")
_ = shell("cp -r \(sourcePath) \(assetsPath)")
// 4
guard let images =
try? FileManager.default.contentsOfDirectory(atPath: appIconsPath)
else {
return
}
// 5
let config = ProcessInfo.processInfo.environment["CONFIGURATION"] ?? ""
// 6
for imageFile in images {
if imageFile.hasSuffix(".png") {
let fileURL = URL(fileURLWithPath: appIconsPath + "/" + imageFile)
addOverlay(imagePath: fileURL, text: "\(config.prefix(1))")
}
}
}
}
Add Run Scripts:
xcrun --sdk macosx swiftc
-parse-as-library $SCRIPT_INPUT_FILE_0 Scripts/Shell.swift
Scripts/OverlayLabel.swift Scripts/ImageOverlay.swift -o CompiledScript
./CompiledScript
Note:
With Xcode 13.0, you’ll have to delete the app from the simulator to see the icon change.
Combine Publisher
var cancelHandler: AnyCancellable?
let group = DispatchGroup()
// 1
guard let projectDir = ProcessInfo.processInfo.environment["SRCROOT"] else {
return
}
let configURL = URL(fileURLWithPath: "\(projectDir)/AllowedWarnings.json")
// 2
let publisher = URLSession.shared.dataTaskPublisher(for: configURL)
let configPublisher = publisher
.map(\.data)
.decode(type: LintConfig.self, decoder: JSONDecoder())
.eraseToAnyPublisher()
// 3
group.enter()
cancelHandler = configPublisher.sink { completion in
// 4
switch completion {
case .failure(let error):
print("\(error)")
group.leave()
exit(1)
case .finished:
print("Linting Config Loaded")
}
} receiveValue: { value in
// 5
startLinting(allowedWarnings: value.allowedWarnings)
}
// 6
group.wait()
cancelHandler?.cancel()
The sky is the limit for what you can do in the build phases with Swift. You can download resources used by the project from a remote server, you can validate more things in your project directly from Xcode or you can automate configuration changes or even upload the compiled binary yourself. You can literally program your own CI/CD if you want to.
Reference
https://www.raywenderlich.com/25816315-using-swift-scripts-with-xcode
网友评论