A workflow for Swift compiler development
If you’re new to Swift compiler development, it can be helpful to look at a workflow of how development can happen.
In this post, I’ll be describing the parts of the workflow I use when working on compiler features. If you work on other parts of Swift (say the Swift standard library), or on other aspects of the compiler (like performance), this might not be helpful.
This post assumes that:
- The Swift source code repositories are checked out under
~/swift-source/
- The Swift repository is at
~/swift-source/swift
- The Swift repository has two remotes,
origin
andfork
:origin
points to Apple’s Swift repository on GitHub, andfork
points to our fork of that repository on GitHib - The
SWIFT_BUILD_DIR
environment variable is set to a Swift build directory
My previous post talks about one way to get a setup like that.
My workflow uses the following parts. The order of using these parts varies depending on what I work on.
- Starting off
- Debugging
- Making changes
- Syncing up with master
- Testing
- Open a pull request
- Modify a pull request
Starting off
When starting to work on a fix or feature, we should create a new branch off master:
$ git checkout -b <feature-branch> master
Debugging
To debug a compilation of swiftc file.swift
, we can’t just run lldb
-- swiftc file.swift
because swiftc
invokes itself and other
executables as separate processes to do the actual compilation.
To see the underlying commands without running them, we can run:
$ swiftc file.swift -###
Typically, we want to run the debugger on the first command printed, so we can say:
$ lldb -- `swiftc file.swift -### | head -n 1`
The DebuggingTheCompiler.rst document in the Swift repository has some helpful information on debugging. I’m keeping some notes as well.
Making changes
When committing, we should follow the Swift commit message guidelines.
To rebuild Swift after making changes, we can run:
$ cd $SWIFT_BUILD_DIR
$ ninja swift
If there were no changes to header files, rebuilding Swift would typically take only a few minutes.
Syncing up with master
If we’ve been working on our feature branch for a while (say a week), and if we have commits as work-in-progress on our feature branch, it would be a good idea to rebase our feature branch on top of the latest commits in master.
Assuming we’re on the feature branch:
$ git fetch origin master
$ git rebase origin/master
If the are conflicts, they would have to be resolved during the rebase.
After rebasing to master, we’ll have to rebuid Swift again.
$ cd $SWIFT_BUILD_DIR
$ ninja swift
We’re rebuilding only Swift, while the changes we fetched on master could have included changes to other parts of the project, like llvm and the Swift standard library.
-
In case the previously built version of Swift standard library is out of date, we’ll get an error like this when running
swift
orswiftc
:error: compiled module was created by an older version of the compiler; rebuild 'Swift' and try again
Then we should rebuild the standard library as well, like:
$ cd $SWIFT_BUILD_DIR $ ninja swift swift-stdlib
-
In case the previously built version of llvm is out of date, we will get errors referring to llvm header files while compiling Swift. In that case, we should rerun the build script.
First, we update all the repositories:
$ cd ~/swift-source/swift $ git checkout master $ cd ~/swift-source $ ./swift/utils/update-checkout
If there are commits in our feature branch, we can switch to that branch now:
$ cd ~/swift-source/swift $ git checkout <feature-branch> # rebase again onto master if reqd.
We can now run
build-script
with the same options as the initial build. Incremental runs ofbuild-script
take lesser time than clean runs.
Testing
Run the testsuite
To make sure our changes haven’t affected working parts of the compiler, we should test our version of Swift against the testsuite. The expected output is embedded inside the test file, and the output is verified using llvm’s FileCheck utility.
We use the lit.py
script for that. The following commands assume that
lit.py
is available in our PATH
.
By default, lit.py
prints one of “PASS:”, “FAIL:” or “XFAIL:” (which
means expected failure) for every testcase. Passing -s
suppresses
that.
If we pass -v
, lit.py
shall print the commands it invoked for
failing tests, so we can try to run it ourselves.
Typically, we know our changes are likely to affect only certain aspects of the compiler, so we run only certain tests:
-
To run tests under a certain test directory:
$ lit.py -sv $SWIFT_BUILD_DIR/test-linux-x86_64/<test-directory>
where
<test-directory>
is a directory path under~/swift-source/swift/test/
. -
To run tests matching a filename pattern:
$ lit.py -sv $SWIFT_BUILD_DIR/test-linux-x86_64/<test-directory> --filter=<pattern>
If we’re about to open a pull request, we might want to run the full testsuite:
$ lit.py -s $SWIFT_BUILD_DIR/test-linux-x86_64/
Add to the testsuite
Most changes to the compiler will require that the change be tested. In that case, the pull request should include an addition to the testsuite to verify the change.
More on the testsuite
- Testing.md in the Swift repository
- codafi’s post on the Swift forums
Open a pull request
To open a pull request, we should:
-
Push the feature branch to our fork
$ git push fork <feature-branch>
-
Go to your forked repository on GitHub on a web browser, where you should see an option to create a pull request
-
When opening the pull request, we should:
- include the bug number resolved by the pull request
- @-mention a potential reviewer, ideally from the Swift team
If it’s not obvious who should review something, we could look at CODE_OWNERS.TXT in the Swift repository.
Once we open a pull request, we should wait till a reviewer takes this up for review. Only those who have commit access can ask the @swift-ci build bot to invoke automated testing on the pull request.
Modify a pull request
We might have to change a pull request for different reasons:
- to make corrections in the implementation
- to take a different approach
- to address conflicts with changes in the master branch
If we add commits to the same branch and push that to the same branch on our fork, and they will be added to the pull request automatically.
$ git push fork <feature-branch>
If we end up with a different set of commits altogether (maybe we used interactive rebase to edit the commit history, or we rebased to a different base commit), we can force-push to the same branch on our fork, and the pull request will be updated automatically.
$ git push -f fork <feature-branch>
Force-pushing can sometimes cause the @swift-ci build bot to report an error even when there’s no error. In case the build bot reports an error, we should take a look at its logs to check if it’s a genuine error or not.
If you have questions, corrections, or feedback on this workflow, please let me know.