Using Swift Scripts with Xcode
Learn how to run Swift scripts as part of the Xcode build phase, giving you control to configure or validate your app while building your project. By Ehab Amer.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Using Swift Scripts with Xcode
25 mins
Building apps with Swift is a lot of fun — and it’s also fun to use it in the build process itself.
Xcode lets you run your own scripts as part of the build phases, but instead of limiting yourself to shell scripts only, you can leverage your knowledge and expertise in Swift and do more with less effort.
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
Getting Started
Download the starter project by clicking the Download Materials button at the top or bottom of the tutorial. Unzip it and open HelloXcode.xcodeproj in the starter folder.
The app itself doesn’t do much. Build and run. You’ll see a view controller with some text:
For this tutorial, you’ll work in the Scripts folder.
Writing Hello Xcode
Create a new Swift file under Scripts and name it HelloXcode.swift. Add the following code:
import Foundation
@main
enum MyScript {
static func main() {
print("Hello Xcode")
}
}
The code above prints “Hello Xcode” in the console.
Then, open the terminal, navigate to the Scripts folder and execute this command:
xcrun swiftc -parse-as-library HelloXcode.swift
cd
followed by the path you want to reach. For example cd /Users/your_user_name/SwiftBuildPhase/Starter/Scripts/
This will compile your Swift file and create an executable binary with the same name.
Next, run this command:
./HelloXcode
This will print Hello Xcode in your terminal window.
You just created a very basic application that does nothing except print the text Hello Xcode. You compiled this application and executed it. If you double-click the compiled file, it’ll open a new terminal window with some more messages before and after Hello Xcode.
........Scripts/HelloXcode ; exit;
Hello Xcode
logout
Saving session...
...copying shared history...
...saving history...truncating history files...
...completed.
[Process completed]
xcrun
can take several parameters one of them is -parse-as-library
which specifies to treat this file as a library otherwise, it’ll complain about the @main
attribute.
You can also specify what kind of SDK you want to compile the code against. Ideally, since you’re going to execute them from Xcode, it makes sense to use the macOS SDK. You can also rename the output file to something specific using the -o
attribute.
Your build command should be:
xcrun --sdk macosx swiftc -parse-as-library HelloXcode.swift -o CompiledScript
This command compiles HelloXcode.swift using the macOS SDK. The output file will be named CompiledScript.
Understanding Build Phases
Compiling your Swift file and executing it from Xcode is as easy as doing it from the terminal. All you need to do is define a New Run Script Phase and add the commands you executed on the terminal into it.
From the Project navigator, select the project file. Then in Build Phases add a new phase and select New Run Script Phase from the drop-down menu.
The Build Phases tab is the central point for Xcode’s build operation. When you build any project, Xcode does a number of steps in order:
- Identifies the dependencies and compiles them.
- Compiles the source files.
- Links the compiled files with their compiled dependencies.
- Copies resources to the bundle.
However, you may want to add your own operations at specific moments. For this tutorial, you’ll add new operations to the beginning — so when you add a new run script phase, drag it to the top of the list.
Going back to the new phase you added a moment ago, delete the commented line and add these commands:
xcrun --sdk macosx swiftc -parse-as-library Scripts/HelloXcode.swift \
-o CompiledScript
./CompiledScript
The difference between this and what you did before on the terminal is that Xcode executes these scripts on the path of the project file — that’s why adding Scripts/ is important.
Build the project to try out the new script and build phase you just added, then open the build log when it finishes. You’ll see the message you printed logged directly in Xcode’s log.
Exploring Input and Output Files
Xcode’s run phase allows you to specify files as configuration instead of having them explicit in the script. Think of it as sending a file as a parameter to a function. This way, your scripts can be more dynamic and portable to use.
In the run phase you added, add to the Input Files list $(SRCROOT)/Scripts/HelloXcode.swift and update the script to:
xcrun --sdk macosx swiftc -parse-as-library $SCRIPT_INPUT_FILE_0 \
-o CompiledScript
./CompiledScript
This doesn’t cause any changes to the execution of the scripts, but it makes Xcode more aware of the files you will change or use in your scripts. It will validate the existence of the input files and will make sure that files that are an output of a script then an input of another are used in the correct order.
Accessing Environment Variables
Sending file paths as parameters can be useful, but it’s not enough. Sometimes you’ll need to read information related to the project itself — like, is this a release or debug build? What is the version of the project? Or the name of the project?
When Xcode executes any run script phase, it shares all its build settings through environment variables. Think of environment variables as global variables. You can still read them from your Swift code. In HelloXcode.swift, add the following in main()
:
if let value = ProcessInfo.processInfo.environment["PRODUCT_NAME"] {
print("Product Name is: \(value)")
}
if let value = ProcessInfo.processInfo.environment["CONFIGURATION"] {
print("Configuration is: \(value)")
}
The code above reads the values for the environment variables “PRODUCT_NAME” and “CONFIGURATION” and prints them to Xcode’s log.
Build and open the build log. You’ll see the two values printed in the log:
When you open the details of the run script phase from the log, you’ll see all the build settings exported. You can read any of those values the same way you did with PRODUCT_NAME and CONFIGURATION. You can also read more to understand what each stands for in Apple’s Build Settings Reference.
In some cases, you’ll want to add your own custom settings for the project. This won’t make any difference for Xcode or its build process, but it could make a huge difference for your own custom scripts. Select the project file and go to Build Settings. Click on the + at the top, select Add User-Defined Setting and name the setting CUSTOM_VALUE. Enter This is a test value! as its value.
To read the new value you just added, add the following to HelloXcode.swift:
if let value = ProcessInfo.processInfo.environment["CUSTOM_VALUE"] {
print("Custom Value is: \(value)")
}
Build and you’ll see the new value you wrote printed in the log.